Skip to content

Commit

Permalink
fix: fix pydantic response models to match actual response data
Browse files Browse the repository at this point in the history
fix: modify the existing pydantic response models based on actual response data
test: add more integration tests
  • Loading branch information
salimhamed committed Mar 20, 2024
1 parent 125b271 commit 054795b
Show file tree
Hide file tree
Showing 26 changed files with 451 additions and 165 deletions.
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ profile = "black"
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
env_override_existing_values = 1
env_files = [".env", ".env.test"]
markers = [
"integration: mark a test as an integration test",
Expand Down
18 changes: 14 additions & 4 deletions src/junglescout/models/responses/api_response.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
from typing import Generic, TypeVar
from typing import Generic, List, Optional, TypeVar

from pydantic import BaseModel, Field

DataT = TypeVar("DataT")


class APIResponseError(BaseModel):
"""Represents an error in a response from the Jungle Scout API."""

title: str = Field(default=..., description="The title of the error.")
detail: str = Field(default=..., description="The detail of the error.")
code: str = Field(default=..., description="The code of the error.")
status: str = Field(default=..., description="The status code for the error.")


class APIResponseLink(BaseModel):
"""Represents the links for a response from the Jungle Scout API."""

self: str = Field(default=..., description="The current link.")
next: str = Field(default=..., description="The next link.")
self: Optional[str] = Field(default=None, description="The current link.")
next: Optional[str] = Field(default=None, description="The next link.")


class APIResponseMeta(BaseModel):
"""Represents additional metadata for a response from the Jungle Scout API."""

total_items: int = Field(default=..., description="The total number of items in the response.")
total_items: Optional[int] = Field(default=None, description="The total number of items in the response.")
errors: Optional[List[APIResponseError]] = Field(default=None, description="The errors in the response.")


class APIResponse(BaseModel, Generic[DataT]):
Expand Down
40 changes: 25 additions & 15 deletions src/junglescout/models/responses/keyword_by_asin.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,58 @@
from datetime import datetime
from typing import List, Optional

from pydantic import BaseModel, Field, field_serializer

from .serializer_helpers import serialize_datetime


class CompetitorRank(BaseModel):
"""Represents a competitor's rank for a specific keyword."""

asin: str = Field(default=..., description="The ASIN of the competitor.")
organic_rank: int = Field(default=..., description="The organic rank of the competitor.")


class KeywordAttributes(BaseModel):
"""Keyword attributes for a specific ASIN."""

country: str = Field(default=..., description="The country of the keyword.")
name: str = Field(default=..., description="The name of the keyword.")
primary_asin: str = Field(default=..., description="The primary ASIN of the keyword.")
monthly_trend: int = Field(default=..., description="The monthly trend of the keyword.")
monthly_trend: float = Field(default=..., description="The monthly trend of the keyword.")
monthly_search_volume_exact: int = Field(default=..., description="The monthly search volume exact of the keyword.")
quarterly_trend: int = Field(default=..., description="The quarterly trend of the keyword.")
quarterly_trend: float = Field(default=..., description="The quarterly trend of the keyword.")
monthly_search_volume_broad: int = Field(default=..., description="The monthly search volume broad of the keyword.")
dominant_category: str = Field(default=..., description="The dominant category of the keyword.")
recommended_promotions: str = Field(default=..., description="The recommended promotions of the keyword.")
sp_brand_ad_bid: int = Field(default=..., description="The SP brand ad bid of the keyword.")
ppc_bid_broad: int = Field(default=..., description="The PPC bid broad of the keyword.")
ppc_bid_exact: int = Field(default=..., description="The PPC bid exact of the keyword.")
recommended_promotions: int = Field(default=..., description="The recommended promotions of the keyword.")
sp_brand_ad_bid: Optional[float] = Field(default=None, description="The SP brand ad bid of the keyword.")
ppc_bid_broad: Optional[float] = Field(default=None, description="The PPC bid broad of the keyword.")
ppc_bid_exact: Optional[float] = Field(default=None, description="The PPC bid exact of the keyword.")
ease_of_ranking_score: int = Field(default=..., description="The ease of ranking score of the keyword.")
relevancy_score: int = Field(default=..., description="The relevancy score of the keyword.")
organic_product_count: int = Field(default=..., description="The organic product count of the keyword.")
sponsored_product_count: int = Field(default=..., description="The sponsored product count of the keyword.")
updated_at: datetime = Field(default=..., description="The date the keyword was last updated.")
organic_rank: int = Field(default=..., description="The organic rank of the keyword.")
sponsored_rank: int = Field(default=..., description="The sponsored rank of the keyword.")
overall_rank: int = Field(default=..., description="The overall rank of the keyword.")
sponsored_rank: Optional[int] = Field(default=None, description="The sponsored rank of the keyword.")
overall_rank: Optional[int] = Field(default=None, description="The overall rank of the keyword.")
organic_ranking_asins_count: int = Field(default=..., description="The organic ranking ASINs count of the keyword.")
sponsored_ranking_asins_count: int = Field(
default=..., description="The sponsored ranking ASINs count of the keyword."
)
avg_competitor_organic_rank: int = Field(
default=..., description="The average competitor organic rank of the keyword."
avg_competitor_organic_rank: Optional[int] = Field(
default=None, description="The average competitor organic rank of the keyword."
)
avg_competitor_sponsored_rank: int = Field(
default=..., description="The average competitor sponsored rank of the keyword."
avg_competitor_sponsored_rank: Optional[int] = Field(
default=None, description="The average competitor sponsored rank of the keyword."
)
relative_organic_position: int = Field(default=..., description="The relative organic position of the keyword.")
relative_sponsored_position: int = Field(default=..., description="The relative sponsored position of the keyword.")
competitor_organic_rank: int = Field(default=..., description="The competitor organic rank of the keyword.")
variation_lowest_organic_rank: int = Field(
default=..., description="The variation lowest organic rank of the keyword."
competitor_organic_rank: List[CompetitorRank] = Field(
default=..., description="The competitor organic rank of the keyword."
)
variation_lowest_organic_rank: Optional[int] = Field(
default=None, description="The variation lowest organic rank of the keyword."
)

@field_serializer("updated_at")
Expand Down
34 changes: 21 additions & 13 deletions src/junglescout/models/responses/keyword_by_keyword.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Optional

from pydantic import BaseModel, Field


Expand All @@ -6,19 +8,25 @@ class KeywordByKeywordAttributes(BaseModel):

country: str = Field(default=..., description="The country of the keyword.")
name: str = Field(default=..., description="The name of the keyword.")
monthly_trend: int = Field(default=..., description="The monthly trend of the keyword.")
monthly_search_volume_exact: int = Field(default=..., description="The monthly search volume exact of the keyword.")
quarterly_trend: int = Field(default=..., description="The quarterly trend of the keyword.")
monthly_search_volume_broad: int = Field(default=..., description="The monthly search volume broad of the keyword.")
dominant_category: str = Field(default=..., description="The dominant category of the keyword.")
recommended_promotions: str = Field(default=..., description="The recommended promotions of the keyword.")
sp_brand_ad_bid: int = Field(default=..., description="The SP brand ad bid of the keyword.")
ppc_bid_broad: int = Field(default=..., description="The PPC bid broad of the keyword.")
ppc_bid_exact: int = Field(default=..., description="The PPC bid exact of the keyword.")
ease_of_ranking_score: int = Field(default=..., description="The ease of ranking score of the keyword.")
relevancy_score: int = Field(default=..., description="The relevancy score of the keyword.")
organic_product_count: int = Field(default=..., description="The organic product count of the keyword.")
sponsored_product_count: int = Field(default=..., description="The sponsored product count of the keyword.")
monthly_trend: Optional[float] = Field(default=None, description="The monthly trend of the keyword.")
monthly_search_volume_exact: Optional[int] = Field(
default=None, description="The monthly search volume exact of the keyword."
)
quarterly_trend: Optional[float] = Field(default=None, description="The quarterly trend of the keyword.")
monthly_search_volume_broad: Optional[int] = Field(
default=None, description="The monthly search volume broad of the keyword."
)
dominant_category: Optional[str] = Field(default=None, description="The dominant category of the keyword.")
recommended_promotions: Optional[int] = Field(default=..., description="The recommended promotions of the keyword.")
sp_brand_ad_bid: Optional[float] = Field(default=None, description="The SP brand ad bid of the keyword.")
ppc_bid_broad: Optional[float] = Field(default=None, description="The PPC bid broad of the keyword.")
ppc_bid_exact: Optional[float] = Field(default=None, description="The PPC bid exact of the keyword.")
ease_of_ranking_score: Optional[int] = Field(default=None, description="The ease of ranking score of the keyword.")
relevancy_score: Optional[int] = Field(default=None, description="The relevancy score of the keyword.")
organic_product_count: Optional[int] = Field(default=None, description="The organic product count of the keyword.")
sponsored_product_count: Optional[int] = Field(
default=None, description="The sponsored product count of the keyword."
)


class KeywordByKeyword(BaseModel):
Expand Down
90 changes: 50 additions & 40 deletions src/junglescout/models/responses/product_database.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from datetime import datetime
from typing import List
from typing import List, Optional

from pydantic import BaseModel, Field, field_serializer

Expand All @@ -9,61 +9,71 @@
class ProductDatabaseSubcategoryRanks(BaseModel):
"""Represents a response object containing subcategory ranks."""

subcategory: int = Field(default=..., description="The subcategory of the product.")
subcategory: str = Field(default=..., description="The subcategory of the product.")
rank: int = Field(default=..., description="The rank of the product.")


class ProductDatabaseFeeBreakdown(BaseModel):
"""Represents a response object containing fee breakdown."""

fba_fee: int = Field(default=..., description="The FBA fee of the product.")
referral_fee: int = Field(default=..., description="The referral fee of the product.")
variable_closing_fee: int = Field(default=..., description="The variable closing fee of the product.")
total_fees: int = Field(default=..., description="The total fees of the product.")
fba_fee: float = Field(default=..., description="The FBA fee of the product.")
referral_fee: float = Field(default=..., description="The referral fee of the product.")
variable_closing_fee: float = Field(default=..., description="The variable closing fee of the product.")
total_fees: float = Field(default=..., description="The total fees of the product.")


class ProductDatabaseAttributes(BaseModel):
"""Product database attributes."""

title: str = Field(default=..., description="The title of the product.")
price: float = Field(default=..., description="The price of the product.")
reviews: int = Field(default=..., description="The number of reviews for the product.")
category: str = Field(default=..., description="The category of the product.")
rating: float = Field(default=..., description="The rating of the product.")
image_url: str = Field(default=..., description="The image URL of the product.")
parent_asin: str = Field(default=..., description="The parent ASIN of the product.")
is_variant: bool = Field(default=..., description="Whether the product is a variant.")
seller_type: str = Field(default=..., description="The type of the seller.")
variants: int = Field(default=..., description="The number of variants for the product.")
is_standalone: bool = Field(default=..., description="Whether the product is standalone.")
is_parent: bool = Field(default=..., description="Whether the product is a parent.")
brand: str = Field(default=..., description="The brand of the product.")
product_rank: int = Field(default=..., description="The rank of the product.")
weight_value: float = Field(default=..., description="The weight value of the product.")
weight_unit: str = Field(default=..., description="The weight unit of the product.")
length_value: float = Field(default=..., description="The length value of the product.")
width_value: float = Field(default=..., description="The width value of the product.")
height_value: float = Field(default=..., description="The height value of the product.")
dimensions_unit: str = Field(default=..., description="The dimensions unit of the product.")
listing_quality_score: float = Field(default=..., description="The listing quality score of the product.")
number_of_sellers: int = Field(default=..., description="The number of sellers for the product.")
buy_box_owner: str = Field(default=..., description="The buy box owner of the product.")
buy_box_owner_seller_id: str = Field(default=..., description="The buy box owner seller ID of the product.")
date_first_available: datetime = Field(default=..., description="The date the product was first available.")
date_first_available_is_estimated: bool = Field(
default=..., description="Whether the date the product was first available is estimated."
price: Optional[float] = Field(default=None, description="The price of the product.")
reviews: Optional[int] = Field(default=None, description="The number of reviews for the product.")
category: Optional[str] = Field(default=None, description="The category of the product.")
rating: Optional[float] = Field(default=None, description="The rating of the product.")
image_url: Optional[str] = Field(default=None, description="The image URL of the product.")
parent_asin: Optional[str] = Field(default=None, description="The parent ASIN of the product.")
is_variant: Optional[bool] = Field(default=None, description="Whether the product is a variant.")
seller_type: Optional[str] = Field(default=None, description="The type of the seller.")
variants: Optional[List[str]] = Field(default=None, description="The number of variants for the product.")
is_standalone: Optional[bool] = Field(default=None, description="Whether the product is standalone.")
is_parent: Optional[bool] = Field(default=None, description="Whether the product is a parent.")
brand: Optional[str] = Field(default=None, description="The brand of the product.")
product_rank: Optional[int] = Field(default=None, description="The rank of the product.")
weight_value: Optional[float] = Field(default=None, description="The weight value of the product.")
weight_unit: Optional[str] = Field(default=None, description="The weight unit of the product.")
length_value: Optional[float] = Field(default=None, description="The length value of the product.")
width_value: Optional[float] = Field(default=None, description="The width value of the product.")
height_value: Optional[float] = Field(default=None, description="The height value of the product.")
dimensions_unit: Optional[str] = Field(default=None, description="The dimensions unit of the product.")
listing_quality_score: Optional[float] = Field(
default=None, description="The listing quality score of the product."
)
approximate_30_day_revenue: float = Field(default=..., description="The approximate 30 day revenue of the product.")
approximate_30_day_units_sold: int = Field(
default=..., description="The approximate 30 day units sold of the product."
number_of_sellers: Optional[int] = Field(default=None, description="The number of sellers for the product.")
buy_box_owner: Optional[str] = Field(default=None, description="The buy box owner of the product.")
buy_box_owner_seller_id: Optional[str] = Field(
default=None, description="The buy box owner seller ID of the product."
)
ean_list: List[int] = Field(default=..., description="The EAN list of the product.")
variant_reviews: int = Field(default=..., description="The variant reviews of the product.")
date_first_available: Optional[datetime] = Field(
default=None, description="The date the product was first available."
)
date_first_available_is_estimated: Optional[bool] = Field(
default=None, description="Whether the date the product was first available is estimated."
)
approximate_30_day_revenue: Optional[float] = Field(
default=None, description="The approximate 30 day revenue of the product."
)
approximate_30_day_units_sold: Optional[int] = Field(
default=None, description="The approximate 30 day units sold of the product."
)
ean_list: Optional[List[int]] = Field(default=None, description="The EAN list of the product.")
variant_reviews: Optional[int] = Field(default=None, description="The variant reviews of the product.")
updated_at: datetime = Field(default=..., description="The date the product was last updated.")
subcategory_ranks: List[ProductDatabaseSubcategoryRanks] = Field(
default=..., description="The subcategory ranks of the product."
subcategory_ranks: Optional[List[ProductDatabaseSubcategoryRanks]] = Field(
default=None, description="The subcategory ranks of the product."
)
fee_breakdown: Optional[ProductDatabaseFeeBreakdown] = Field(
default=None, description="The fee breakdown of the product."
)
fee_breakdown: ProductDatabaseFeeBreakdown = Field(default=..., description="The fee breakdown of the product.")

@field_serializer("updated_at")
def _serialize_updated_at(self, v: datetime): # noqa: PLR6301
Expand Down
2 changes: 1 addition & 1 deletion src/junglescout/models/responses/sales_estimates.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class SalesEstimateAttributes(BaseModel):
default=..., description="A boolean indicating whether the ASIN is a standalone product."
)
parent_asin: str = Field(default=..., description="The parent ASIN associated with the sales estimate.")
variants: int = Field(default=..., description="The number of variants associated with the sales estimate.")
variants: List[str] = Field(default=..., description="The variant ASINs associated with the sales estimate.")
data: List[SalesEstimateData] = Field(default=..., description="The sales estimate data.")


Expand Down
Loading

0 comments on commit 054795b

Please sign in to comment.