From f41a8fa1ed56976a75d102ffd0906fa0bb668896 Mon Sep 17 00:00:00 2001 From: Amir Hasan Aref Asl Date: Tue, 5 Aug 2025 16:15:13 +0330 Subject: [PATCH] add new sections for company and profile data --- magicalapi/types/company_data.py | 70 ++++++++++++++++++++------- magicalapi/types/profile_data.py | 30 ++++++++++-- pyproject.toml | 2 +- tests/types/test_company_data_type.py | 53 ++++++++++++++++++++ tests/types/test_profile_data_type.py | 51 ++++++++++++++----- uv.lock | 2 +- 6 files changed, 173 insertions(+), 35 deletions(-) diff --git a/magicalapi/types/company_data.py b/magicalapi/types/company_data.py index 89c5a30..8319769 100644 --- a/magicalapi/types/company_data.py +++ b/magicalapi/types/company_data.py @@ -1,4 +1,5 @@ from datetime import datetime +from datetime import date from typing import TypeAlias from pydantic import BaseModel, HttpUrl, field_validator @@ -17,6 +18,51 @@ class Product(BaseModelValidated): customers: list[URL] +class Employee(BaseModelValidated): + title: str | None + subtitle: str | None + link: str | None + image_url: URL | None + + +class SimilarCompany(BaseModelValidated): + title: str | None + subtitle: str | None + location: str | None + link: str | None + + +class Post(BaseModelValidated): + text: str | None + + post_url: URL | None + post_id: str | None + time: str | None + videos: list[str] + images: list[str] + likes_count: int | None + comments_count: int | None + + +class Investor(BaseModelValidated): + name: str | None + link: str | None + image_url: URL | None + + +class FundingRound(BaseModelValidated): + date: date | None + type: str | None + raised_amount: str | None + + +class Funding(BaseModelValidated): + last_round: FundingRound | None + rounds_count: int | None + investors: list[Investor] + crunchbase_url: URL | None + + class Company(BaseModelValidated): """ The main type of company data service @@ -41,8 +87,13 @@ class Company(BaseModelValidated): foundedOn: str | None specialties: str | None # - locations: list[list[str]] products: list[Product] + locations: list[list[str]] + # + employees_at_linkedin: list[Employee] + similar_companies: list[SimilarCompany] + funding: Funding + posts: list[Post] @field_validator("crawled_at", mode="before") @classmethod @@ -52,20 +103,3 @@ def datetime_validator(cls, value: str) -> datetime: class CompanyDataResponse(BaseResponse): data: Company - - -class Language(BaseModel): - name: str - code: str - - -class Country(Language): - pass - - -class LanguagesResponse(BaseResponse): - data: list[Language] - - -class CountriesResponse(BaseResponse): - data: list[Country] diff --git a/magicalapi/types/profile_data.py b/magicalapi/types/profile_data.py index 96fb81c..589b7b9 100644 --- a/magicalapi/types/profile_data.py +++ b/magicalapi/types/profile_data.py @@ -7,8 +7,8 @@ class StartEndDate(BaseModelValidated): # Example: "Jan 2024" or "2024-01" or "Present" - start_date: str | None - end_date: str | None + start_date: int | str | None + end_date: int | str | None # validating the dates in format %b %Y ,Example : Jan 2024 # @field_validator("start_date", "end_date", mode="before") @@ -52,7 +52,7 @@ class StartEndDateEducation(StartEndDate): # raise e -class Duration(BaseModel): +class Duration(BaseModelValidated): years: int = 0 months: int @@ -143,6 +143,26 @@ def date_validator(cls, value: str) -> date | None: return datetime.strptime(value, "%b %Y").date() +class Activity(BaseModelValidated): + title: str | None + subtitle: str | None + image_url: str | None + link: str | None + + +class SimilarProfile(BaseModelValidated): + url: str | None + name: str | None + title: str | None + image_url: str | None + + +class Patent(BaseModelValidated): + title: str | None + patent_id: str | None + link: str | None + + class Profile(BaseModelValidated): """ The main type of linkedin profile data service @@ -166,6 +186,10 @@ class Profile(BaseModelValidated): courses: list[Course] honors_and_awards: list[HonorAndAward] + activities: list[Activity] + similar_profiles: list[SimilarProfile] + patents: list[Patent] + @field_validator("crawled_at", mode="before") @classmethod def datetime_validator(cls, value: str) -> datetime: diff --git a/pyproject.toml b/pyproject.toml index 0ce9e22..0848c17 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "magicalapi" -version = "1.3.0" +version = "1.4.0" description = "This is a Python client that provides easy access to the MagicalAPI.com services, fully type annotated, and asynchronous." authors = [ { name = "MagicalAPI", email = "info@magicalapi.com" } diff --git a/tests/types/test_company_data_type.py b/tests/types/test_company_data_type.py index ced7e0b..21c37dd 100644 --- a/tests/types/test_company_data_type.py +++ b/tests/types/test_company_data_type.py @@ -50,6 +50,59 @@ def company_data(): "customers": [fake.uri()], } ], + # + "employees_at_linkedin": [ + { + "title": fake.job(), + "subtitle": fake.text(max_nb_chars=30), + "link": f"https://linkedin.com/in/{fake.user_name()}/", + "image_url": fake.image_url(), + } + for _ in range(randint(1, 5)) + ], + "similar_companies": [ + { + "title": fake.company(), + "subtitle": fake.text(max_nb_chars=50), + "location": fake.city(), + "link": f"https://linkedin.com/company/{fake.slug()}/", + } + for _ in range(randint(1, 3)) + ], + "funding": { + "last_round": { + "date": fake.date_between( + start_date="-5y", end_date="today" + ).isoformat(), + "type": fake.random_element( + elements=("Seed", "Series A", "Series B", "Series C", "IPO") + ), + "raised_amount": f"${randint(1, 100)}M", + }, + "rounds_count": randint(1, 5), + "investors": [ + { + "name": fake.company(), + "link": f"https://linkedin.com/company/{fake.slug()}/", + "image_url": fake.image_url(), + } + for _ in range(randint(1, 4)) + ], + "crunchbase_url": f"https://crunchbase.com/organization/{fake.slug()}", + }, + "posts": [ + { + "text": fake.text(max_nb_chars=200), + "post_url": f"https://linkedin.com/posts/{fake.user_name()}_{fake.uuid4()}", + "post_id": fake.uuid4(), + "time": fake.date_time_this_month().strftime("%Y-%m-%d %H:%M:%S"), + "videos": [fake.url() for _ in range(randint(0, 2))], + "images": [fake.image_url() for _ in range(randint(0, 3))], + "likes_count": randint(0, 1000), + "comments_count": randint(0, 100), + } + for _ in range(randint(1, 3)) + ], } yield company diff --git a/tests/types/test_profile_data_type.py b/tests/types/test_profile_data_type.py index 793b62e..0c49209 100644 --- a/tests/types/test_profile_data_type.py +++ b/tests/types/test_profile_data_type.py @@ -13,7 +13,7 @@ @pytest.fixture() -def profile_dataprofile_data(): +def profile_data(): # create a sample profile data dictionary fake = Faker(locale="en_US") _username = fake.user_name() @@ -188,6 +188,33 @@ def profile_dataprofile_data(): "description": "", }, ], + # + "activities": [ + { + "title": fake.text(max_nb_chars=50), + "subtitle": fake.text(max_nb_chars=30), + "image_url": fake.image_url(), + "link": fake.uri(), + } + for _ in range(randint(1, 3)) + ], + "similar_profiles": [ + { + "url": f"https://linkedin.com/in/{fake.user_name()}/", + "name": fake.name(), + "title": fake.job(), + "image_url": fake.image_url(), + } + for _ in range(randint(1, 5)) + ], + "patents": [ + { + "title": fake.text(max_nb_chars=100), + "patent_id": f"US{randint(1000000, 9999999)}", + "link": f"https://patents.google.com/patent/US{randint(1000000, 9999999)}", + } + for _ in range(randint(0, 2)) + ], } yield profile @@ -195,24 +222,24 @@ def profile_dataprofile_data(): @pytest.mark.dependency() -def test_profile_data_type(profile_dataprofile_data: dict[str, Any]): +def test_profile_data_type(profile_data: dict[str, Any]): # check profile data validated successfull try: - Profile.model_validate(profile_dataprofile_data) + Profile.model_validate(profile_data) except ValidationError as exc: assert False, "validating profile data failed : " + str(exc) @pytest.mark.dependency() -def test_profile_data_type_failing(profile_dataprofile_data: dict[str, Any]): +def test_profile_data_type_failing(profile_data: dict[str, Any]): # validating profile data must fail - profile_dataprofile_data["experience"][0]["date"]["start_date"] = "none" - del profile_dataprofile_data["education"][0]["date"] - del profile_dataprofile_data["projects"][0]["date"]["end_date"] - profile_dataprofile_data["publications"][0]["publication_date"] = 12 - profile_dataprofile_data["honors_and_awards"][0]["issued_date"] = None + profile_data["experience"][0]["date"]["start_date"] = "none" + del profile_data["education"][0]["date"] + del profile_data["projects"][0]["date"]["end_date"] + profile_data["publications"][0]["publication_date"] = 12 + profile_data["honors_and_awards"][0]["issued_date"] = None try: - Profile.model_validate(profile_dataprofile_data) + Profile.model_validate(profile_data) except: pass else: @@ -223,10 +250,10 @@ def test_profile_data_type_failing(profile_dataprofile_data: dict[str, Any]): @pytest.mark.dependency( depends=["test_profile_data_type", "test_profile_data_type_failing"] ) -def test_profile_data_response_type(profile_dataprofile_data: dict[str, Any]): +def test_profile_data_response_type(profile_data: dict[str, Any]): try: response_schema = { - "data": profile_dataprofile_data, + "data": profile_data, "usage": {"credits": randint(1, 200)}, } ProfileDataResponse.model_validate(response_schema) diff --git a/uv.lock b/uv.lock index ccbbce5..ced0870 100644 --- a/uv.lock +++ b/uv.lock @@ -467,7 +467,7 @@ wheels = [ [[package]] name = "magicalapi" -version = "1.2.1" +version = "1.4.0" source = { editable = "." } dependencies = [ { name = "httpx" },