amazon-paapi5-python-sdk
Advanced tools
| """ | ||
| OffersV2 models for Amazon PA-API 5.0 | ||
| Provides improved reliability and data quality compared to Offers V1 | ||
| """ | ||
| from .money import Money | ||
| from .availability import Availability | ||
| from .condition import Condition | ||
| from .deal_details import DealDetails | ||
| from .loyalty_points import LoyaltyPoints | ||
| from .merchant_info import MerchantInfo | ||
| from .saving_basis import SavingBasis | ||
| from .savings import Savings | ||
| from .price import Price | ||
| from .offer_listing import OfferListing | ||
| from .offersv2 import OffersV2 | ||
| __all__ = [ | ||
| 'Money', | ||
| 'Availability', | ||
| 'Condition', | ||
| 'DealDetails', | ||
| 'LoyaltyPoints', | ||
| 'MerchantInfo', | ||
| 'SavingBasis', | ||
| 'Savings', | ||
| 'Price', | ||
| 'OfferListing', | ||
| 'OffersV2', | ||
| ] |
| """ | ||
| Availability model for OffersV2 | ||
| Specifies availability information about an offer | ||
| """ | ||
| from typing import Optional | ||
| from dataclasses import dataclass | ||
| @dataclass | ||
| class Availability: | ||
| """ | ||
| Availability information including stock status and order quantity limits | ||
| Valid Type values: | ||
| - AVAILABLE_DATE: Item available on a future date | ||
| - IN_STOCK: Item is in stock | ||
| - IN_STOCK_SCARCE: Item in stock but limited quantity | ||
| - LEADTIME: Item available after lead time | ||
| - OUT_OF_STOCK: Currently out of stock | ||
| - PREORDER: Available for pre-order | ||
| - UNAVAILABLE: Not available | ||
| - UNKNOWN: Unknown availability | ||
| """ | ||
| max_order_quantity: Optional[int] = None | ||
| message: Optional[str] = None | ||
| min_order_quantity: Optional[int] = None | ||
| type: Optional[str] = None | ||
| @classmethod | ||
| def from_dict(cls, data: Optional[dict]) -> Optional['Availability']: | ||
| """Create Availability instance from API response dict""" | ||
| if not data: | ||
| return None | ||
| return cls( | ||
| max_order_quantity=int(data['MaxOrderQuantity']) if 'MaxOrderQuantity' in data else None, | ||
| message=data.get('Message'), | ||
| min_order_quantity=int(data['MinOrderQuantity']) if 'MinOrderQuantity' in data else None, | ||
| type=data.get('Type') | ||
| ) | ||
| def to_dict(self) -> dict: | ||
| """Convert to dictionary""" | ||
| result = {} | ||
| if self.max_order_quantity is not None: | ||
| result['MaxOrderQuantity'] = self.max_order_quantity | ||
| if self.message is not None: | ||
| result['Message'] = self.message | ||
| if self.min_order_quantity is not None: | ||
| result['MinOrderQuantity'] = self.min_order_quantity | ||
| if self.type is not None: | ||
| result['Type'] = self.type | ||
| return result |
| """ | ||
| Condition model for OffersV2 | ||
| Specifies the condition of the offer | ||
| """ | ||
| from typing import Optional | ||
| from dataclasses import dataclass | ||
| @dataclass | ||
| class Condition: | ||
| """ | ||
| Product condition information | ||
| Valid Value values: New, Used, Refurbished, Unknown | ||
| Valid SubCondition values: LikeNew, Good, VeryGood, Acceptable, Refurbished, OEM, OpenBox, Unknown | ||
| Note: For offers with value "New", there will not be a specified ConditionNote | ||
| and SubCondition will be Unknown | ||
| """ | ||
| condition_note: Optional[str] = None | ||
| sub_condition: Optional[str] = None | ||
| value: Optional[str] = None | ||
| @classmethod | ||
| def from_dict(cls, data: Optional[dict]) -> Optional['Condition']: | ||
| """Create Condition instance from API response dict""" | ||
| if not data: | ||
| return None | ||
| return cls( | ||
| condition_note=data.get('ConditionNote'), | ||
| sub_condition=data.get('SubCondition'), | ||
| value=data.get('Value') | ||
| ) | ||
| def to_dict(self) -> dict: | ||
| """Convert to dictionary""" | ||
| result = {} | ||
| if self.condition_note is not None: | ||
| result['ConditionNote'] = self.condition_note | ||
| if self.sub_condition is not None: | ||
| result['SubCondition'] = self.sub_condition | ||
| if self.value is not None: | ||
| result['Value'] = self.value | ||
| return result |
| """ | ||
| DealDetails model for OffersV2 | ||
| Specifies deal information of the offer | ||
| """ | ||
| from typing import Optional | ||
| from dataclasses import dataclass | ||
| @dataclass | ||
| class DealDetails: | ||
| """ | ||
| Deal information including Lightning Deals and special offers | ||
| Valid AccessType values: | ||
| - ALL: Available to all customers | ||
| - PRIME_EARLY_ACCESS: Available to Prime members first, then all customers | ||
| - PRIME_EXCLUSIVE: Available only to Prime members | ||
| Badge Examples: "Limited Time Deal", "With Prime", "Black Friday Deal", "Ends In" | ||
| """ | ||
| access_type: Optional[str] = None | ||
| badge: Optional[str] = None | ||
| early_access_duration_in_milliseconds: Optional[int] = None | ||
| end_time: Optional[str] = None | ||
| percent_claimed: Optional[int] = None | ||
| start_time: Optional[str] = None | ||
| @classmethod | ||
| def from_dict(cls, data: Optional[dict]) -> Optional['DealDetails']: | ||
| """Create DealDetails instance from API response dict""" | ||
| if not data: | ||
| return None | ||
| return cls( | ||
| access_type=data.get('AccessType'), | ||
| badge=data.get('Badge'), | ||
| early_access_duration_in_milliseconds=int(data['EarlyAccessDurationInMilliseconds']) | ||
| if 'EarlyAccessDurationInMilliseconds' in data else None, | ||
| end_time=data.get('EndTime'), | ||
| percent_claimed=int(data['PercentClaimed']) if 'PercentClaimed' in data else None, | ||
| start_time=data.get('StartTime') | ||
| ) | ||
| def to_dict(self) -> dict: | ||
| """Convert to dictionary""" | ||
| result = {} | ||
| if self.access_type is not None: | ||
| result['AccessType'] = self.access_type | ||
| if self.badge is not None: | ||
| result['Badge'] = self.badge | ||
| if self.early_access_duration_in_milliseconds is not None: | ||
| result['EarlyAccessDurationInMilliseconds'] = self.early_access_duration_in_milliseconds | ||
| if self.end_time is not None: | ||
| result['EndTime'] = self.end_time | ||
| if self.percent_claimed is not None: | ||
| result['PercentClaimed'] = self.percent_claimed | ||
| if self.start_time is not None: | ||
| result['StartTime'] = self.start_time | ||
| return result |
| """ | ||
| LoyaltyPoints model for OffersV2 | ||
| Loyalty Points (Amazon Japan only) | ||
| """ | ||
| from typing import Optional | ||
| from dataclasses import dataclass | ||
| @dataclass | ||
| class LoyaltyPoints: | ||
| """ | ||
| Loyalty points associated with an offer | ||
| Currently only supported in the Japan marketplace | ||
| """ | ||
| points: Optional[int] = None | ||
| @classmethod | ||
| def from_dict(cls, data: Optional[dict]) -> Optional['LoyaltyPoints']: | ||
| """Create LoyaltyPoints instance from API response dict""" | ||
| if not data: | ||
| return None | ||
| return cls( | ||
| points=int(data['Points']) if 'Points' in data else None | ||
| ) | ||
| def to_dict(self) -> dict: | ||
| """Convert to dictionary""" | ||
| result = {} | ||
| if self.points is not None: | ||
| result['Points'] = self.points | ||
| return result |
| """ | ||
| MerchantInfo model for OffersV2 | ||
| Specifies merchant information of an offer | ||
| """ | ||
| from typing import Optional | ||
| from dataclasses import dataclass | ||
| @dataclass | ||
| class MerchantInfo: | ||
| """ | ||
| Merchant/seller information including ID and name | ||
| """ | ||
| id: Optional[str] = None | ||
| name: Optional[str] = None | ||
| @classmethod | ||
| def from_dict(cls, data: Optional[dict]) -> Optional['MerchantInfo']: | ||
| """Create MerchantInfo instance from API response dict""" | ||
| if not data: | ||
| return None | ||
| return cls( | ||
| id=data.get('Id'), | ||
| name=data.get('Name') | ||
| ) | ||
| def to_dict(self) -> dict: | ||
| """Convert to dictionary""" | ||
| result = {} | ||
| if self.id is not None: | ||
| result['Id'] = self.id | ||
| if self.name is not None: | ||
| result['Name'] = self.name | ||
| return result |
| """ | ||
| Money model for OffersV2 | ||
| Common struct used for representing money | ||
| """ | ||
| from typing import Optional | ||
| from dataclasses import dataclass | ||
| @dataclass | ||
| class Money: | ||
| """ | ||
| Money representation with amount, currency and display format | ||
| """ | ||
| amount: Optional[float] = None | ||
| currency: Optional[str] = None | ||
| display_amount: Optional[str] = None | ||
| @classmethod | ||
| def from_dict(cls, data: Optional[dict]) -> Optional['Money']: | ||
| """Create Money instance from API response dict""" | ||
| if not data: | ||
| return None | ||
| return cls( | ||
| amount=float(data['Amount']) if 'Amount' in data else None, | ||
| currency=data.get('Currency'), | ||
| display_amount=data.get('DisplayAmount') | ||
| ) | ||
| def to_dict(self) -> dict: | ||
| """Convert to dictionary""" | ||
| result = {} | ||
| if self.amount is not None: | ||
| result['Amount'] = self.amount | ||
| if self.currency is not None: | ||
| result['Currency'] = self.currency | ||
| if self.display_amount is not None: | ||
| result['DisplayAmount'] = self.display_amount | ||
| return result |
| """ | ||
| OfferListing model for OffersV2 | ||
| Specifies an individual offer listing for a product | ||
| """ | ||
| from typing import Optional | ||
| from dataclasses import dataclass | ||
| from .availability import Availability | ||
| from .condition import Condition | ||
| from .deal_details import DealDetails | ||
| from .loyalty_points import LoyaltyPoints | ||
| from .merchant_info import MerchantInfo | ||
| from .price import Price | ||
| @dataclass | ||
| class OfferListing: | ||
| """ | ||
| Individual offer listing with complete details | ||
| Valid Type values: | ||
| - LIGHTNING_DEAL | ||
| - SUBSCRIBE_AND_SAVE | ||
| - None (for regular listings) | ||
| """ | ||
| availability: Optional[Availability] = None | ||
| condition: Optional[Condition] = None | ||
| deal_details: Optional[DealDetails] = None | ||
| is_buy_box_winner: Optional[bool] = None | ||
| loyalty_points: Optional[LoyaltyPoints] = None | ||
| merchant_info: Optional[MerchantInfo] = None | ||
| price: Optional[Price] = None | ||
| type: Optional[str] = None | ||
| violates_map: Optional[bool] = None | ||
| @classmethod | ||
| def from_dict(cls, data: Optional[dict]) -> Optional['OfferListing']: | ||
| """Create OfferListing instance from API response dict""" | ||
| if not data: | ||
| return None | ||
| return cls( | ||
| availability=Availability.from_dict(data.get('Availability')), | ||
| condition=Condition.from_dict(data.get('Condition')), | ||
| deal_details=DealDetails.from_dict(data.get('DealDetails')), | ||
| is_buy_box_winner=data.get('IsBuyBoxWinner'), | ||
| loyalty_points=LoyaltyPoints.from_dict(data.get('LoyaltyPoints')), | ||
| merchant_info=MerchantInfo.from_dict(data.get('MerchantInfo')), | ||
| price=Price.from_dict(data.get('Price')), | ||
| type=data.get('Type'), | ||
| violates_map=data.get('ViolatesMAP') | ||
| ) | ||
| def to_dict(self) -> dict: | ||
| """Convert to dictionary""" | ||
| result = {} | ||
| if self.availability is not None: | ||
| result['Availability'] = self.availability.to_dict() | ||
| if self.condition is not None: | ||
| result['Condition'] = self.condition.to_dict() | ||
| if self.deal_details is not None: | ||
| result['DealDetails'] = self.deal_details.to_dict() | ||
| if self.is_buy_box_winner is not None: | ||
| result['IsBuyBoxWinner'] = self.is_buy_box_winner | ||
| if self.loyalty_points is not None: | ||
| result['LoyaltyPoints'] = self.loyalty_points.to_dict() | ||
| if self.merchant_info is not None: | ||
| result['MerchantInfo'] = self.merchant_info.to_dict() | ||
| if self.price is not None: | ||
| result['Price'] = self.price.to_dict() | ||
| if self.type is not None: | ||
| result['Type'] = self.type | ||
| if self.violates_map is not None: | ||
| result['ViolatesMAP'] = self.violates_map | ||
| return result | ||
| def has_deal(self) -> bool: | ||
| """Check if this listing has an active deal""" | ||
| return self.deal_details is not None | ||
| def is_lightning_deal(self) -> bool: | ||
| """Check if this is a Lightning Deal""" | ||
| return self.type == 'LIGHTNING_DEAL' |
| """ | ||
| OffersV2 model | ||
| Main container for offer listings | ||
| """ | ||
| from typing import List, Optional | ||
| from dataclasses import dataclass, field | ||
| from .offer_listing import OfferListing | ||
| @dataclass | ||
| class OffersV2: | ||
| """ | ||
| Main OffersV2 container with various resources related to offer listings | ||
| Provides improved reliability and data quality compared to Offers V1. | ||
| All new Item Offer features will be added to OffersV2 only. | ||
| """ | ||
| listings: List[OfferListing] = field(default_factory=list) | ||
| @classmethod | ||
| def from_dict(cls, data: Optional[dict]) -> Optional['OffersV2']: | ||
| """Create OffersV2 instance from API response dict""" | ||
| if not data: | ||
| return None | ||
| listings = [] | ||
| if 'Listings' in data and isinstance(data['Listings'], list): | ||
| for listing_data in data['Listings']: | ||
| listing = OfferListing.from_dict(listing_data) | ||
| if listing: | ||
| listings.append(listing) | ||
| return cls(listings=listings) | ||
| def get_buy_box_winner(self) -> Optional[OfferListing]: | ||
| """ | ||
| Get the BuyBox winner listing (if exists) | ||
| This is the best offer recommended by Amazon | ||
| """ | ||
| for listing in self.listings: | ||
| if listing.is_buy_box_winner: | ||
| return listing | ||
| return None | ||
| def get_deal_listings(self) -> List[OfferListing]: | ||
| """ | ||
| Get all listings that have active deals | ||
| """ | ||
| return [listing for listing in self.listings if listing.has_deal()] | ||
| def get_lightning_deals(self) -> List[OfferListing]: | ||
| """ | ||
| Get all Lightning Deal listings | ||
| """ | ||
| return [listing for listing in self.listings if listing.is_lightning_deal()] | ||
| def to_dict(self) -> dict: | ||
| """Convert to dictionary""" | ||
| return { | ||
| 'Listings': [listing.to_dict() for listing in self.listings] | ||
| } |
| """ | ||
| Price model for OffersV2 | ||
| Specifies buying price of an offer | ||
| """ | ||
| from typing import Optional | ||
| from dataclasses import dataclass | ||
| from .money import Money | ||
| from .saving_basis import SavingBasis | ||
| from .savings import Savings | ||
| @dataclass | ||
| class Price: | ||
| """ | ||
| Complete pricing information including current price, savings, and unit pricing | ||
| Note: Price represents the price shown for a logged-in Amazon user | ||
| with an in-marketplace shipping address | ||
| """ | ||
| money: Optional[Money] = None | ||
| price_per_unit: Optional[Money] = None | ||
| saving_basis: Optional[SavingBasis] = None | ||
| savings: Optional[Savings] = None | ||
| @classmethod | ||
| def from_dict(cls, data: Optional[dict]) -> Optional['Price']: | ||
| """Create Price instance from API response dict""" | ||
| if not data: | ||
| return None | ||
| return cls( | ||
| money=Money.from_dict(data.get('Money')), | ||
| price_per_unit=Money.from_dict(data.get('PricePerUnit')), | ||
| saving_basis=SavingBasis.from_dict(data.get('SavingBasis')), | ||
| savings=Savings.from_dict(data.get('Savings')) | ||
| ) | ||
| def to_dict(self) -> dict: | ||
| """Convert to dictionary""" | ||
| result = {} | ||
| if self.money is not None: | ||
| result['Money'] = self.money.to_dict() | ||
| if self.price_per_unit is not None: | ||
| result['PricePerUnit'] = self.price_per_unit.to_dict() | ||
| if self.saving_basis is not None: | ||
| result['SavingBasis'] = self.saving_basis.to_dict() | ||
| if self.savings is not None: | ||
| result['Savings'] = self.savings.to_dict() | ||
| return result |
| """ | ||
| SavingBasis model for OffersV2 | ||
| Reference value which is used to calculate savings against | ||
| """ | ||
| from typing import Optional | ||
| from dataclasses import dataclass | ||
| from .money import Money | ||
| @dataclass | ||
| class SavingBasis: | ||
| """ | ||
| Reference pricing for savings calculations | ||
| Valid SavingBasisType values: | ||
| - LIST_PRICE | ||
| - LOWEST_PRICE | ||
| - LOWEST_PRICE_STRIKETHROUGH | ||
| - WAS_PRICE | ||
| """ | ||
| money: Optional[Money] = None | ||
| saving_basis_type: Optional[str] = None | ||
| saving_basis_type_label: Optional[str] = None | ||
| @classmethod | ||
| def from_dict(cls, data: Optional[dict]) -> Optional['SavingBasis']: | ||
| """Create SavingBasis instance from API response dict""" | ||
| if not data: | ||
| return None | ||
| return cls( | ||
| money=Money.from_dict(data.get('Money')), | ||
| saving_basis_type=data.get('SavingBasisType'), | ||
| saving_basis_type_label=data.get('SavingBasisTypeLabel') | ||
| ) | ||
| def to_dict(self) -> dict: | ||
| """Convert to dictionary""" | ||
| result = {} | ||
| if self.money is not None: | ||
| result['Money'] = self.money.to_dict() | ||
| if self.saving_basis_type is not None: | ||
| result['SavingBasisType'] = self.saving_basis_type | ||
| if self.saving_basis_type_label is not None: | ||
| result['SavingBasisTypeLabel'] = self.saving_basis_type_label | ||
| return result |
| """ | ||
| Savings model for OffersV2 | ||
| Savings of an offer | ||
| """ | ||
| from typing import Optional | ||
| from dataclasses import dataclass | ||
| from .money import Money | ||
| @dataclass | ||
| class Savings: | ||
| """ | ||
| Savings information including amount and percentage | ||
| This is the difference between Price Money and SavingBasis Money | ||
| """ | ||
| money: Optional[Money] = None | ||
| percentage: Optional[int] = None | ||
| @classmethod | ||
| def from_dict(cls, data: Optional[dict]) -> Optional['Savings']: | ||
| """Create Savings instance from API response dict""" | ||
| if not data: | ||
| return None | ||
| return cls( | ||
| money=Money.from_dict(data.get('Money')), | ||
| percentage=int(data['Percentage']) if 'Percentage' in data else None | ||
| ) | ||
| def to_dict(self) -> dict: | ||
| """Convert to dictionary""" | ||
| result = {} | ||
| if self.money is not None: | ||
| result['Money'] = self.money.to_dict() | ||
| if self.percentage is not None: | ||
| result['Percentage'] = self.percentage | ||
| return result |
| from cryptography.fernet import Fernet | ||
| import base64 | ||
| import os | ||
| import logging | ||
| from typing import Dict, Any, Optional | ||
| from ..exceptions import SecurityException | ||
| class CredentialManager: | ||
| """Secure credential management with encryption.""" | ||
| def __init__(self, encryption_key: str = None): | ||
| """ | ||
| Initialize with optional encryption key. | ||
| Args: | ||
| encryption_key: Optional encryption key for securing credentials | ||
| Raises: | ||
| SecurityException: If encryption key is invalid | ||
| """ | ||
| self.logger = logging.getLogger(__name__) | ||
| self.encryption_key = encryption_key | ||
| self.fernet = None | ||
| if encryption_key: | ||
| try: | ||
| # Use a more secure key derivation method | ||
| from cryptography.hazmat.primitives import hashes | ||
| from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC | ||
| # Use fixed salt for deterministic key derivation | ||
| salt = b'amazon_paapi5_python_sdk' | ||
| # Derive a proper key from the provided encryption key | ||
| kdf = PBKDF2HMAC( | ||
| algorithm=hashes.SHA256(), | ||
| length=32, | ||
| salt=salt, | ||
| iterations=100000, | ||
| ) | ||
| key = base64.urlsafe_b64encode(kdf.derive(encryption_key.encode('utf-8'))) | ||
| self.fernet = Fernet(key) | ||
| self.logger.info("Successfully initialized credential encryption") | ||
| except Exception as e: | ||
| self.logger.error(f"Failed to initialize encryption: {str(e)}") | ||
| raise SecurityException( | ||
| "Invalid encryption key format", | ||
| error_type="invalid_key" | ||
| ) from e | ||
| def encrypt_credentials(self, credentials: Dict[str, Any]) -> Dict[str, Any]: | ||
| """ | ||
| Encrypt sensitive credentials. | ||
| Args: | ||
| credentials: Dictionary of credentials to encrypt | ||
| Returns: | ||
| Dictionary with encrypted credentials | ||
| Raises: | ||
| SecurityException: If encryption fails | ||
| """ | ||
| if not self.encryption_key or not self.fernet: | ||
| return credentials | ||
| try: | ||
| encrypted = {} | ||
| for key, value in credentials.items(): | ||
| if isinstance(value, str): | ||
| encrypted_value = self.fernet.encrypt(value.encode()).decode() | ||
| encrypted[key] = encrypted_value | ||
| else: | ||
| encrypted[key] = value | ||
| return encrypted | ||
| except Exception as e: | ||
| self.logger.error(f"Encryption failed: {str(e)}") | ||
| raise SecurityException( | ||
| "Failed to encrypt credentials", | ||
| error_type="encryption_failed" | ||
| ) | ||
| def decrypt_credentials(self, credentials: Dict[str, Any]) -> Dict[str, Any]: | ||
| """ | ||
| Decrypt encrypted credentials. | ||
| Args: | ||
| credentials: Dictionary of encrypted credentials | ||
| Returns: | ||
| Dictionary with decrypted credentials | ||
| Raises: | ||
| SecurityException: If decryption fails | ||
| """ | ||
| if not self.encryption_key or not self.fernet: | ||
| return credentials | ||
| try: | ||
| decrypted = {} | ||
| for key, value in credentials.items(): | ||
| if isinstance(value, str): | ||
| try: | ||
| decrypted_value = self.fernet.decrypt(value.encode()).decode() | ||
| decrypted[key] = decrypted_value | ||
| except: | ||
| # If decryption fails, assume the value wasn't encrypted | ||
| decrypted[key] = value | ||
| else: | ||
| decrypted[key] = value | ||
| return decrypted | ||
| except Exception as e: | ||
| self.logger.error(f"Decryption failed: {str(e)}") | ||
| raise SecurityException( | ||
| "Failed to decrypt credentials", | ||
| error_type="decryption_failed" | ||
| ) | ||
| @staticmethod | ||
| def generate_encryption_key() -> str: | ||
| """ | ||
| Generate a secure encryption key. | ||
| Returns: | ||
| str: Base64-encoded encryption key | ||
| """ | ||
| return base64.urlsafe_b64encode(os.urandom(32)).decode() | ||
| def rotate_encryption_key(self, new_key: str, credentials: Dict[str, Any]) -> Dict[str, Any]: | ||
| """ | ||
| Rotate encryption key and re-encrypt credentials. | ||
| Args: | ||
| new_key: New encryption key | ||
| credentials: Currently encrypted credentials | ||
| Returns: | ||
| Dict: Credentials encrypted with the new key | ||
| Raises: | ||
| SecurityException: If rotation fails | ||
| """ | ||
| if not self.encryption_key: | ||
| self.encryption_key = new_key | ||
| self.fernet = Fernet(new_key) | ||
| return credentials | ||
| try: | ||
| # First decrypt with old key | ||
| decrypted = self.decrypt_credentials(credentials) | ||
| # Set up new key | ||
| old_key = self.encryption_key | ||
| self.encryption_key = new_key | ||
| # Use more secure key derivation as in __init__ | ||
| from cryptography.hazmat.primitives import hashes | ||
| from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC | ||
| salt = b'amazon_paapi5_python_sdk' | ||
| kdf = PBKDF2HMAC( | ||
| algorithm=hashes.SHA256(), | ||
| length=32, | ||
| salt=salt, | ||
| iterations=100000, | ||
| ) | ||
| key = base64.urlsafe_b64encode(kdf.derive(new_key.encode('utf-8'))) | ||
| self.fernet = Fernet(key) | ||
| # Re-encrypt with new key | ||
| return self.encrypt_credentials(decrypted) | ||
| except Exception as e: | ||
| # Restore old key in case of failure | ||
| self.encryption_key = old_key if 'old_key' in locals() else None | ||
| self.logger.error(f"Key rotation failed: {str(e)}") | ||
| raise SecurityException( | ||
| "Failed to rotate encryption key", | ||
| error_type="key_rotation_failed" | ||
| ) |
+19
-28
| Metadata-Version: 2.4 | ||
| Name: amazon-paapi5-python-sdk | ||
| Version: 1.0.5 | ||
| Version: 1.0.6 | ||
| Summary: Amazon Product Advertising API v5 Python SDK (Most Advance SDK) | ||
| Home-page: https://github.com/rajpurohithitesh/amazon-paapi5-python-sdk | ||
| License-File: LICENSE | ||
| License-File: NOTICE | ||
| Author: Hitesh Rajpurohit | ||
| Requires-Python: >=3.9 | ||
| Classifier: Programming Language :: Python :: 3 | ||
| Classifier: License :: OSI Approved :: Apache Software License | ||
| Classifier: Operating System :: OS Independent | ||
| Requires-Python: >=3.9 | ||
| Classifier: Programming Language :: Python :: 3.9 | ||
| Classifier: Programming Language :: Python :: 3.10 | ||
| Classifier: Programming Language :: Python :: 3.11 | ||
| Classifier: Programming Language :: Python :: 3.12 | ||
| Classifier: Programming Language :: Python :: 3.13 | ||
| Classifier: Programming Language :: Python :: 3.14 | ||
| Provides-Extra: redis | ||
| Requires-Dist: aiohttp (>=3.9.3) | ||
| Requires-Dist: cachetools (>=5.3.3) | ||
| Requires-Dist: cryptography (>=42.0.5) | ||
| Requires-Dist: redis (>=5.0.3) ; extra == "redis" | ||
| Requires-Dist: requests (>=2.31.0) | ||
| Description-Content-Type: text/markdown | ||
| License-File: LICENSE | ||
| License-File: NOTICE | ||
| Requires-Dist: requests>=2.28.0 | ||
| Requires-Dist: aiohttp>=3.9.0 | ||
| Requires-Dist: cachetools>=5.0.0 | ||
| Requires-Dist: cryptography>=3.4.8 | ||
| Provides-Extra: redis | ||
| Requires-Dist: redis>=4.0.0; extra == "redis" | ||
| Provides-Extra: dev | ||
| Requires-Dist: pytest>=7.0.0; extra == "dev" | ||
| Requires-Dist: pytest-asyncio>=0.18.0; extra == "dev" | ||
| Dynamic: author | ||
| Dynamic: classifier | ||
| Dynamic: description | ||
| Dynamic: description-content-type | ||
| Dynamic: home-page | ||
| Dynamic: license-file | ||
| Dynamic: provides-extra | ||
| Dynamic: requires-dist | ||
| Dynamic: requires-python | ||
| Dynamic: summary | ||
@@ -178,3 +168,3 @@ # Amazon PA-API 5.0 Python SDK | ||
| ``` | ||
| It should output `1.0.5`. | ||
| It should output `1.0.6`. | ||
@@ -342,3 +332,3 @@ ## Getting Started | ||
| - **`src/amazon_paapi5/`**: The main library code. | ||
| - `__init__.py`: Defines the library version (`1.0.5`). | ||
| - `__init__.py`: Defines the library version (`1.0.6`). | ||
| - `client.py`: The core `Client` class that handles API requests, throttling, caching, and signature generation. | ||
@@ -516,1 +506,2 @@ - `config.py`: Manages configuration (credentials, marketplace, throttling delay) and supports 18 marketplaces. | ||
| Happy coding, and enjoy building with the Amazon PA-API 5.0 Python SDK! | ||
+1
-1
| [tool.poetry] | ||
| name = "amazon-paapi5-python-sdk" | ||
| version = "1.0.5" | ||
| version = "1.0.6" | ||
| description = "Amazon Product Advertising API v5 Python SDK (Most Advance SDK)" | ||
@@ -5,0 +5,0 @@ authors = ["Hitesh Rajpurohit"] |
+2
-2
@@ -145,3 +145,3 @@ # Amazon PA-API 5.0 Python SDK | ||
| ``` | ||
| It should output `1.0.5`. | ||
| It should output `1.0.6`. | ||
@@ -309,3 +309,3 @@ ## Getting Started | ||
| - **`src/amazon_paapi5/`**: The main library code. | ||
| - `__init__.py`: Defines the library version (`1.0.5`). | ||
| - `__init__.py`: Defines the library version (`1.0.6`). | ||
| - `client.py`: The core `Client` class that handles API requests, throttling, caching, and signature generation. | ||
@@ -312,0 +312,0 @@ - `config.py`: Manages configuration (credentials, marketplace, throttling delay) and supports 18 marketplaces. |
| """Amazon Product Advertising API 5.0 SDK for Python""" | ||
| __version__ = "1.0.5" | ||
| __version__ = "1.0.6" | ||
@@ -5,0 +5,0 @@ from .client import Client |
@@ -169,3 +169,3 @@ import asyncio | ||
| "Accept-Encoding": "gzip", | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.5" | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.6" | ||
| } | ||
@@ -227,3 +227,3 @@ | ||
| "Accept-Encoding": "gzip", | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.5" | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.6" | ||
| } | ||
@@ -282,3 +282,3 @@ | ||
| "Accept-Encoding": "gzip", | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.5" | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.6" | ||
| } | ||
@@ -340,3 +340,3 @@ | ||
| "Accept-Encoding": "gzip", | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.5" | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.6" | ||
| } | ||
@@ -395,3 +395,3 @@ | ||
| "Accept-Encoding": "gzip", | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.5" | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.6" | ||
| } | ||
@@ -453,3 +453,3 @@ | ||
| "Accept-Encoding": "gzip", | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.5" | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.6" | ||
| } | ||
@@ -508,3 +508,3 @@ | ||
| "Accept-Encoding": "gzip", | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.5" | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.6" | ||
| } | ||
@@ -566,3 +566,3 @@ | ||
| "Accept-Encoding": "gzip", | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.5" | ||
| "User-Agent": "amazon-paapi5-python-sdk/1.0.6" | ||
| } | ||
@@ -569,0 +569,0 @@ |
| from typing import List, Optional | ||
| from dataclasses import dataclass | ||
| from dataclasses import dataclass, field | ||
| from ..resources import validate_resources | ||
| from ..exceptions import InvalidParameterException | ||
| from .offersv2 import OffersV2 | ||
@@ -36,3 +37,17 @@ @dataclass | ||
| detail_page_url: Optional[str] = None | ||
| raw_data: dict = field(default_factory=dict, repr=False) | ||
| offersv2: Optional[OffersV2] = None | ||
| @classmethod | ||
| def from_dict(cls, item_data: dict) -> 'Item': | ||
| """Create Item instance from API response dict with full OffersV2 support""" | ||
| return cls( | ||
| asin=item_data.get("ASIN", ""), | ||
| title=item_data.get("ItemInfo", {}).get("Title", {}).get("DisplayValue"), | ||
| price=item_data.get("Offers", {}).get("Listings", [{}])[0].get("Price", {}).get("Amount"), | ||
| detail_page_url=item_data.get("DetailPageURL"), | ||
| raw_data=item_data, | ||
| offersv2=OffersV2.from_dict(item_data.get("OffersV2")) | ||
| ) | ||
| @dataclass | ||
@@ -45,10 +60,5 @@ class GetItemsResponse: | ||
| items = [ | ||
| Item( | ||
| asin=item["ASIN"], | ||
| title=item.get("ItemInfo", {}).get("Title", {}).get("DisplayValue"), | ||
| price=item.get("Offers", {}).get("Listings", [{}])[0].get("Price", {}).get("Amount"), | ||
| detail_page_url=item.get("DetailPageURL"), | ||
| ) | ||
| Item.from_dict(item) | ||
| for item in data.get("ItemsResult", {}).get("Items", []) | ||
| ] | ||
| return cls(items=items) |
| from typing import List, Optional | ||
| from dataclasses import dataclass | ||
| from dataclasses import dataclass, field | ||
| from ..resources import validate_resources | ||
| from .offersv2 import OffersV2 | ||
@@ -32,3 +33,16 @@ @dataclass | ||
| title: Optional[str] = None | ||
| raw_data: dict = field(default_factory=dict, repr=False) | ||
| offersv2: Optional[OffersV2] = None | ||
| @classmethod | ||
| def from_dict(cls, item_data: dict) -> 'Variation': | ||
| """Create Variation instance from API response dict with full OffersV2 support""" | ||
| return cls( | ||
| asin=item_data.get("ASIN", ""), | ||
| dimensions=item_data.get("VariationSummary", {}).get("VariationDimension", []), | ||
| title=item_data.get("ItemInfo", {}).get("Title", {}).get("DisplayValue"), | ||
| raw_data=item_data, | ||
| offersv2=OffersV2.from_dict(item_data.get("OffersV2")) | ||
| ) | ||
| @dataclass | ||
@@ -41,9 +55,5 @@ class GetVariationsResponse: | ||
| variations = [ | ||
| Variation( | ||
| asin=item["ASIN"], | ||
| dimensions=item.get("VariationSummary", {}).get("VariationDimension", []), | ||
| title=item.get("ItemInfo", {}).get("Title", {}).get("DisplayValue"), | ||
| ) | ||
| Variation.from_dict(item) | ||
| for item in data.get("VariationsResult", {}).get("Items", []) | ||
| ] | ||
| return cls(variations=variations) |
| from typing import List, Optional | ||
| from dataclasses import dataclass | ||
| from dataclasses import dataclass, field | ||
| from ..resources import validate_resources | ||
| from .offersv2 import OffersV2 | ||
@@ -35,3 +36,17 @@ @dataclass | ||
| detail_page_url: Optional[str] = None | ||
| raw_data: dict = field(default_factory=dict, repr=False) | ||
| offersv2: Optional[OffersV2] = None | ||
| @classmethod | ||
| def from_dict(cls, item_data: dict) -> 'Item': | ||
| """Create Item instance from API response dict with full OffersV2 support""" | ||
| return cls( | ||
| asin=item_data.get("ASIN", ""), | ||
| title=item_data.get("ItemInfo", {}).get("Title", {}).get("DisplayValue"), | ||
| price=item_data.get("Offers", {}).get("Listings", [{}])[0].get("Price", {}).get("Amount"), | ||
| detail_page_url=item_data.get("DetailPageURL"), | ||
| raw_data=item_data, | ||
| offersv2=OffersV2.from_dict(item_data.get("OffersV2")) | ||
| ) | ||
| @dataclass | ||
@@ -44,10 +59,5 @@ class SearchItemsResponse: | ||
| items = [ | ||
| Item( | ||
| asin=item["ASIN"], | ||
| title=item.get("ItemInfo", {}).get("Title", {}).get("DisplayValue"), | ||
| price=item.get("Offers", {}).get("Listings", [{}])[0].get("Price", {}).get("Amount"), | ||
| detail_page_url=item.get("DetailPageURL"), | ||
| ) | ||
| Item.from_dict(item) | ||
| for item in data.get("SearchResult", {}).get("Items", []) | ||
| ] | ||
| return cls(items=items) |
@@ -78,2 +78,29 @@ """ | ||
| SEARCHREFINEMENTS = "SearchRefinements" | ||
| # OffersV2 Resources - Complete implementation | ||
| OFFERSV2_LISTINGS_AVAILABILITY_MAXORDERQUANTITY = "OffersV2.Listings.Availability.MaxOrderQuantity" | ||
| OFFERSV2_LISTINGS_AVAILABILITY_MESSAGE = "OffersV2.Listings.Availability.Message" | ||
| OFFERSV2_LISTINGS_AVAILABILITY_MINORDERQUANTITY = "OffersV2.Listings.Availability.MinOrderQuantity" | ||
| OFFERSV2_LISTINGS_AVAILABILITY_TYPE = "OffersV2.Listings.Availability.Type" | ||
| OFFERSV2_LISTINGS_CONDITION_CONDITIONNOTE = "OffersV2.Listings.Condition.ConditionNote" | ||
| OFFERSV2_LISTINGS_CONDITION_SUBCONDITION = "OffersV2.Listings.Condition.SubCondition" | ||
| OFFERSV2_LISTINGS_CONDITION_VALUE = "OffersV2.Listings.Condition.Value" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_ACCESSTYPE = "OffersV2.Listings.DealDetails.AccessType" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_BADGE = "OffersV2.Listings.DealDetails.Badge" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_EARLYACCESSDURATIONINMILLISECONDS = "OffersV2.Listings.DealDetails.EarlyAccessDurationInMilliseconds" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_ENDTIME = "OffersV2.Listings.DealDetails.EndTime" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_PERCENTCLAIMED = "OffersV2.Listings.DealDetails.PercentClaimed" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_STARTTIME = "OffersV2.Listings.DealDetails.StartTime" | ||
| OFFERSV2_LISTINGS_ISBUYBOXWINNER = "OffersV2.Listings.IsBuyBoxWinner" | ||
| OFFERSV2_LISTINGS_LOYALTYPOINTS_POINTS = "OffersV2.Listings.LoyaltyPoints.Points" | ||
| OFFERSV2_LISTINGS_MERCHANTINFO_ID = "OffersV2.Listings.MerchantInfo.Id" | ||
| OFFERSV2_LISTINGS_MERCHANTINFO_NAME = "OffersV2.Listings.MerchantInfo.Name" | ||
| OFFERSV2_LISTINGS_PRICE_MONEY = "OffersV2.Listings.Price.Money" | ||
| OFFERSV2_LISTINGS_PRICE_PRICEPERUNIT = "OffersV2.Listings.Price.PricePerUnit" | ||
| OFFERSV2_LISTINGS_PRICE_SAVINGBASIS_MONEY = "OffersV2.Listings.Price.SavingBasis.Money" | ||
| OFFERSV2_LISTINGS_PRICE_SAVINGBASIS_SAVINGBASISTYPE = "OffersV2.Listings.Price.SavingBasis.SavingBasisType" | ||
| OFFERSV2_LISTINGS_PRICE_SAVINGBASIS_SAVINGBASISTYPELABEL = "OffersV2.Listings.Price.SavingBasis.SavingBasisTypeLabel" | ||
| OFFERSV2_LISTINGS_PRICE_SAVINGS_MONEY = "OffersV2.Listings.Price.Savings.Money" | ||
| OFFERSV2_LISTINGS_PRICE_SAVINGS_PERCENTAGE = "OffersV2.Listings.Price.Savings.Percentage" | ||
| OFFERSV2_LISTINGS_TYPE = "OffersV2.Listings.Type" | ||
| OFFERSV2_LISTINGS_VIOLATESMAP = "OffersV2.Listings.ViolatesMAP" | ||
@@ -149,10 +176,29 @@ | ||
| RENTALOFFERS_LISTINGS_MERCHANTINFO = "RentalOffers.Listings.MerchantInfo" | ||
| OFFERSV2_LISTINGS_AVAILABILITY = "OffersV2.Listings.Availability" | ||
| OFFERSV2_LISTINGS_CONDITION = "OffersV2.Listings.Condition" | ||
| OFFERSV2_LISTINGS_DEALDETAILS = "OffersV2.Listings.DealDetails" | ||
| # OffersV2 Resources - Complete implementation | ||
| OFFERSV2_LISTINGS_AVAILABILITY_MAXORDERQUANTITY = "OffersV2.Listings.Availability.MaxOrderQuantity" | ||
| OFFERSV2_LISTINGS_AVAILABILITY_MESSAGE = "OffersV2.Listings.Availability.Message" | ||
| OFFERSV2_LISTINGS_AVAILABILITY_MINORDERQUANTITY = "OffersV2.Listings.Availability.MinOrderQuantity" | ||
| OFFERSV2_LISTINGS_AVAILABILITY_TYPE = "OffersV2.Listings.Availability.Type" | ||
| OFFERSV2_LISTINGS_CONDITION_CONDITIONNOTE = "OffersV2.Listings.Condition.ConditionNote" | ||
| OFFERSV2_LISTINGS_CONDITION_SUBCONDITION = "OffersV2.Listings.Condition.SubCondition" | ||
| OFFERSV2_LISTINGS_CONDITION_VALUE = "OffersV2.Listings.Condition.Value" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_ACCESSTYPE = "OffersV2.Listings.DealDetails.AccessType" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_BADGE = "OffersV2.Listings.DealDetails.Badge" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_EARLYACCESSDURATIONINMILLISECONDS = "OffersV2.Listings.DealDetails.EarlyAccessDurationInMilliseconds" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_ENDTIME = "OffersV2.Listings.DealDetails.EndTime" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_PERCENTCLAIMED = "OffersV2.Listings.DealDetails.PercentClaimed" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_STARTTIME = "OffersV2.Listings.DealDetails.StartTime" | ||
| OFFERSV2_LISTINGS_ISBUYBOXWINNER = "OffersV2.Listings.IsBuyBoxWinner" | ||
| OFFERSV2_LISTINGS_LOYALTYPOINTS = "OffersV2.Listings.LoyaltyPoints" | ||
| OFFERSV2_LISTINGS_MERCHANTINFO = "OffersV2.Listings.MerchantInfo" | ||
| OFFERSV2_LISTINGS_PRICE = "OffersV2.Listings.Price" | ||
| OFFERSV2_LISTINGS_LOYALTYPOINTS_POINTS = "OffersV2.Listings.LoyaltyPoints.Points" | ||
| OFFERSV2_LISTINGS_MERCHANTINFO_ID = "OffersV2.Listings.MerchantInfo.Id" | ||
| OFFERSV2_LISTINGS_MERCHANTINFO_NAME = "OffersV2.Listings.MerchantInfo.Name" | ||
| OFFERSV2_LISTINGS_PRICE_MONEY = "OffersV2.Listings.Price.Money" | ||
| OFFERSV2_LISTINGS_PRICE_PRICEPERUNIT = "OffersV2.Listings.Price.PricePerUnit" | ||
| OFFERSV2_LISTINGS_PRICE_SAVINGBASIS_MONEY = "OffersV2.Listings.Price.SavingBasis.Money" | ||
| OFFERSV2_LISTINGS_PRICE_SAVINGBASIS_SAVINGBASISTYPE = "OffersV2.Listings.Price.SavingBasis.SavingBasisType" | ||
| OFFERSV2_LISTINGS_PRICE_SAVINGBASIS_SAVINGBASISTYPELABEL = "OffersV2.Listings.Price.SavingBasis.SavingBasisTypeLabel" | ||
| OFFERSV2_LISTINGS_PRICE_SAVINGS_MONEY = "OffersV2.Listings.Price.Savings.Money" | ||
| OFFERSV2_LISTINGS_PRICE_SAVINGS_PERCENTAGE = "OffersV2.Listings.Price.Savings.Percentage" | ||
| OFFERSV2_LISTINGS_TYPE = "OffersV2.Listings.Type" | ||
| OFFERSV2_LISTINGS_VIOLATESMAP = "OffersV2.Listings.ViolatesMAP" | ||
@@ -228,2 +274,29 @@ | ||
| RENTALOFFERS_LISTINGS_MERCHANTINFO = "RentalOffers.Listings.MerchantInfo" | ||
| # OffersV2 Resources - Complete implementation | ||
| OFFERSV2_LISTINGS_AVAILABILITY_MAXORDERQUANTITY = "OffersV2.Listings.Availability.MaxOrderQuantity" | ||
| OFFERSV2_LISTINGS_AVAILABILITY_MESSAGE = "OffersV2.Listings.Availability.Message" | ||
| OFFERSV2_LISTINGS_AVAILABILITY_MINORDERQUANTITY = "OffersV2.Listings.Availability.MinOrderQuantity" | ||
| OFFERSV2_LISTINGS_AVAILABILITY_TYPE = "OffersV2.Listings.Availability.Type" | ||
| OFFERSV2_LISTINGS_CONDITION_CONDITIONNOTE = "OffersV2.Listings.Condition.ConditionNote" | ||
| OFFERSV2_LISTINGS_CONDITION_SUBCONDITION = "OffersV2.Listings.Condition.SubCondition" | ||
| OFFERSV2_LISTINGS_CONDITION_VALUE = "OffersV2.Listings.Condition.Value" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_ACCESSTYPE = "OffersV2.Listings.DealDetails.AccessType" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_BADGE = "OffersV2.Listings.DealDetails.Badge" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_EARLYACCESSDURATIONINMILLISECONDS = "OffersV2.Listings.DealDetails.EarlyAccessDurationInMilliseconds" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_ENDTIME = "OffersV2.Listings.DealDetails.EndTime" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_PERCENTCLAIMED = "OffersV2.Listings.DealDetails.PercentClaimed" | ||
| OFFERSV2_LISTINGS_DEALDETAILS_STARTTIME = "OffersV2.Listings.DealDetails.StartTime" | ||
| OFFERSV2_LISTINGS_ISBUYBOXWINNER = "OffersV2.Listings.IsBuyBoxWinner" | ||
| OFFERSV2_LISTINGS_LOYALTYPOINTS_POINTS = "OffersV2.Listings.LoyaltyPoints.Points" | ||
| OFFERSV2_LISTINGS_MERCHANTINFO_ID = "OffersV2.Listings.MerchantInfo.Id" | ||
| OFFERSV2_LISTINGS_MERCHANTINFO_NAME = "OffersV2.Listings.MerchantInfo.Name" | ||
| OFFERSV2_LISTINGS_PRICE_MONEY = "OffersV2.Listings.Price.Money" | ||
| OFFERSV2_LISTINGS_PRICE_PRICEPERUNIT = "OffersV2.Listings.Price.PricePerUnit" | ||
| OFFERSV2_LISTINGS_PRICE_SAVINGBASIS_MONEY = "OffersV2.Listings.Price.SavingBasis.Money" | ||
| OFFERSV2_LISTINGS_PRICE_SAVINGBASIS_SAVINGBASISTYPE = "OffersV2.Listings.Price.SavingBasis.SavingBasisType" | ||
| OFFERSV2_LISTINGS_PRICE_SAVINGBASIS_SAVINGBASISTYPELABEL = "OffersV2.Listings.Price.SavingBasis.SavingBasisTypeLabel" | ||
| OFFERSV2_LISTINGS_PRICE_SAVINGS_MONEY = "OffersV2.Listings.Price.Savings.Money" | ||
| OFFERSV2_LISTINGS_PRICE_SAVINGS_PERCENTAGE = "OffersV2.Listings.Price.Savings.Percentage" | ||
| OFFERSV2_LISTINGS_TYPE = "OffersV2.Listings.Type" | ||
| OFFERSV2_LISTINGS_VIOLATESMAP = "OffersV2.Listings.ViolatesMAP" | ||
| VARIATIONSUMMARY_PRICE_HIGHESTPRICE = "VariationSummary.Price.HighestPrice" | ||
@@ -230,0 +303,0 @@ VARIATIONSUMMARY_PRICE_LOWESTPRICE = "VariationSummary.Price.LowestPrice" |
| [egg_info] | ||
| tag_build = | ||
| tag_date = 0 | ||
-32
| from setuptools import setup, find_packages | ||
| setup( | ||
| name="amazon-paapi5-python-sdk", | ||
| version="1.0.5", | ||
| description="Amazon Product Advertising API v5 Python SDK (Most Advance SDK)", | ||
| long_description=open("README.md", encoding="utf-8").read(), | ||
| long_description_content_type="text/markdown", | ||
| author="Hitesh Rajpurohit", | ||
| url="https://github.com/rajpurohithitesh/amazon-paapi5-python-sdk", | ||
| packages=find_packages(where="src"), | ||
| package_dir={"": "src"}, | ||
| install_requires=[ | ||
| "requests>=2.28.0", | ||
| "aiohttp>=3.9.0", | ||
| "cachetools>=5.0.0", | ||
| "cryptography>=3.4.8", | ||
| ], | ||
| extras_require={ | ||
| "redis": ["redis>=4.0.0"], | ||
| "dev": [ | ||
| "pytest>=7.0.0", | ||
| "pytest-asyncio>=0.18.0", | ||
| ], | ||
| }, | ||
| classifiers=[ | ||
| "Programming Language :: Python :: 3", | ||
| "License :: OSI Approved :: Apache Software License", | ||
| "Operating System :: OS Independent", | ||
| ], | ||
| python_requires=">=3.9", | ||
| ) |
| Metadata-Version: 2.4 | ||
| Name: amazon-paapi5-python-sdk | ||
| Version: 1.0.5 | ||
| Summary: Amazon Product Advertising API v5 Python SDK (Most Advance SDK) | ||
| Home-page: https://github.com/rajpurohithitesh/amazon-paapi5-python-sdk | ||
| Author: Hitesh Rajpurohit | ||
| Classifier: Programming Language :: Python :: 3 | ||
| Classifier: License :: OSI Approved :: Apache Software License | ||
| Classifier: Operating System :: OS Independent | ||
| Requires-Python: >=3.9 | ||
| Description-Content-Type: text/markdown | ||
| License-File: LICENSE | ||
| License-File: NOTICE | ||
| Requires-Dist: requests>=2.28.0 | ||
| Requires-Dist: aiohttp>=3.9.0 | ||
| Requires-Dist: cachetools>=5.0.0 | ||
| Requires-Dist: cryptography>=3.4.8 | ||
| Provides-Extra: redis | ||
| Requires-Dist: redis>=4.0.0; extra == "redis" | ||
| Provides-Extra: dev | ||
| Requires-Dist: pytest>=7.0.0; extra == "dev" | ||
| Requires-Dist: pytest-asyncio>=0.18.0; extra == "dev" | ||
| Dynamic: author | ||
| Dynamic: classifier | ||
| Dynamic: description | ||
| Dynamic: description-content-type | ||
| Dynamic: home-page | ||
| Dynamic: license-file | ||
| Dynamic: provides-extra | ||
| Dynamic: requires-dist | ||
| Dynamic: requires-python | ||
| Dynamic: summary | ||
| # Amazon PA-API 5.0 Python SDK | ||
| [](https://pypi.org/project/amazon-paapi5-python-sdk/) | ||
| [](https://www.python.org/) | ||
| [](https://webservices.amazon.com/paapi5/documentation/) | ||
| [](https://pypi.org/project/amazon-paapi5-python-sdk/) | ||
|  | ||
| [](https://sonarcloud.io/summary/new_code?id=RajpurohitHitesh_amazon-paapi5-python-sdk) | ||
| [](https://sonarcloud.io/summary/new_code?id=RajpurohitHitesh_amazon-paapi5-python-sdk) | ||
| [](https://sonarcloud.io/summary/new_code?id=RajpurohitHitesh_amazon-paapi5-python-sdk) | ||
| [](https://github.com/RajpurohitHitesh/amazon-paapi5-python-sdk/actions/workflows/python-app.yml) | ||
| [](https://github.com/RajpurohitHitesh/amazon-paapi5-python-sdk/actions/workflows/python-package-conda.yml) | ||
| [](https://github.com/RajpurohitHitesh/amazon-paapi5-python-sdk/actions/workflows/python-package.yml) | ||
| Welcome to the **Amazon PA-API 5.0 Python SDK**, a lightweight and powerful library designed to help developers interact with Amazon’s Product Advertising API (PA-API) 5.0. This SDK makes it easy to fetch product details, search for items, retrieve variations, and explore browse nodes on Amazon’s marketplace, all while handling complex tasks like authentication, rate limiting, and caching for you. Whether you’re a beginner or an experienced coder, this guide will walk you through everything you need to know to use this library effectively. | ||
| This SDK is a Python equivalent of the PHP SDK ([amazon-paapi5-php-sdk](https://github.com/RajpurohitHitesh/amazon-paapi5-php-sdk)) by me ([Hitesh Rajpurohit](https://github.com/RajpurohitHitesh)), built to provide the same functionality with Python’s simplicity and flexibility. | ||
| ## Table of Contents | ||
| - [What is the Amazon PA-API 5.0?](#what-is-the-amazon-pa-api-50) | ||
| - [Features](#features) | ||
| - [Installation](#installation) | ||
| - [Getting Started](#getting-started) | ||
| - [Basic Usage Example](#basic-usage-example) | ||
| - [Asynchronous Example](#asynchronous-example) | ||
| - [Batch Processing Example](#batch-processing-example) | ||
| - [Supported Marketplaces](#supported-marketplaces) | ||
| - [File Structure](#file-structure) | ||
| - [How the SDK Works](#how-the-sdk-works) | ||
| - [Advanced Features](#advanced-features) | ||
| - [Caching with Redis](#caching-with-redis) | ||
| - [Custom Throttling](#custom-throttling) | ||
| - [Custom Resources](#custom-resources) | ||
| - [Gzip Compression and Connection Reuse](#gzip-compression-and-connection-reuse) | ||
| - [Running Tests](#running-tests) | ||
| - [Troubleshooting](#troubleshooting) | ||
| - [Comparison with PHP SDK](#comparison-with-php-sdk) | ||
| - [License](#license) | ||
| - [Contributing](#contributing) | ||
| - [Support](#support) | ||
| ## What is the Amazon PA-API 5.0? | ||
| The Amazon Product Advertising API (PA-API) 5.0 is a tool provided by Amazon for developers, particularly those in the Amazon Associates (affiliate) program. It allows you to: | ||
| - Search for products on Amazon (e.g., “laptops” in the Electronics category). | ||
| - Retrieve details about specific products using their ASINs (Amazon Standard Identification Numbers). | ||
| - Get variations of a product (e.g., different sizes or colors of a shirt). | ||
| - Explore browse nodes (categories like “Books” or “Electronics”) to understand Amazon’s product hierarchy. | ||
| This SDK simplifies these tasks by providing a clean, Pythonic interface, handling authentication, and ensuring your requests comply with Amazon’s strict rate limits. | ||
| ## Features | ||
| This SDK is packed with features to make your integration with PA-API 5.0 seamless: | ||
| 1. **Supported Operations**: | ||
| - **SearchItems**: Search for products by keywords and category (e.g., “Laptop” in Electronics). | ||
| - **GetItems**: Fetch details for specific products using ASINs, with batch processing for up to 10 ASINs. | ||
| - **GetVariations**: Retrieve variations of a product (e.g., different colors of a phone). | ||
| - **GetBrowseNodes**: Get information about Amazon’s product categories (browse nodes), with support for multiple IDs. | ||
| 2. **Smart Throttling**: | ||
| - Automatically spaces out API requests to avoid hitting Amazon’s rate limits. | ||
| - Uses exponential backoff (retries with increasing delays) if a request fails due to rate limits. | ||
| - Manages a request queue to prevent overloading the API. | ||
| 3. **Caching**: | ||
| - Stores API responses to reduce redundant requests, saving API quota and improving performance. | ||
| - Supports in-memory caching (using `cachetools`) and Redis caching (optional). | ||
| - Configurable Time-To-Live (TTL) for cached data (default: 1 hour). | ||
| 4. **Asynchronous Support**: | ||
| - Supports non-blocking API calls using Python’s `asyncio` and `aiohttp`, ideal for high-performance applications. | ||
| - Provides async methods (e.g., `search_items_async`) alongside synchronous methods. | ||
| 5. **Type-Safe Objects**: | ||
| - Uses Python’s `dataclasses` to create structured, type-safe request and response objects. | ||
| - Ensures code is easy to write and debug, with IDE support for auto-completion. | ||
| 6. **AWS V4 Signature Authentication**: | ||
| - Generates secure AWS V4 signatures for all API requests, using your access key, secret key, and encryption key. | ||
| - Handles the complex authentication process required by PA-API 5.0. | ||
| 7. **Marketplace Support**: | ||
| - Supports 18 Amazon marketplaces, each with its own region and host (e.g., `www.amazon.in` for India, `www.amazon.co.uk` for the UK). | ||
| - Automatically configures the correct region and host when you select a marketplace. | ||
| 8. **Resource Validation**: | ||
| - Validates API request resources (e.g., `ItemInfo.Title`, `Offers.Listings.Price`) to ensure only valid data is requested. | ||
| - Prevents errors due to invalid resource specifications. | ||
| 9. **Error Handling**: | ||
| - Provides a custom exception hierarchy (`AmazonAPIException`, `AuthenticationException`, `ThrottleException`, etc.) for clear error messages and recovery suggestions. | ||
| - Gracefully handles network issues and API response errors. | ||
| 10. **Performance Optimizations**: | ||
| - **Batch Processing**: Fetch up to 10 ASINs in a single `GetItems` request or multiple browse node IDs in `GetBrowseNodes` to reduce API calls. | ||
| - **Gzip Compression**: Requests compressed responses to minimize network overhead. | ||
| - **Connection Reuse**: Uses persistent HTTP connections for faster requests. | ||
| - **Memory Efficiency**: Optimized parsing and type-safe objects minimize memory usage. | ||
| 11. **Modular Design**: | ||
| - Organized into clear modules (`client`, `config`, `models`, `utils`) for easy maintenance and extension. | ||
| - Each module has a specific role, making the codebase easy to understand. | ||
| 12. **Comprehensive Testing**: | ||
| - Includes unit tests for all major components (client, models, caching, config) using `pytest`. | ||
| - Ensures the library is reliable and bug-free. | ||
| 13. **Beginner-Friendly Examples**: | ||
| - Provides example scripts for all operations (`search_items.py`, `get_items.py`, etc.) to help you get started quickly. | ||
| - Includes both synchronous and asynchronous examples. | ||
| 14. **Minimal Dependencies**: | ||
| - Requires only `requests`, `aiohttp`, `cachetools`, and optional `redis` for caching. | ||
| - Keeps your project lightweight and easy to set up. | ||
| 15. **Apache-2.0 License**: | ||
| - Open-source with a permissive license, allowing you to use, modify, and distribute the library freely. | ||
| ## Installation | ||
| To use the SDK, you need Python 3.9 or higher. Follow these steps: | ||
| 1. **Install the SDK**: | ||
| ```bash | ||
| pip install amazon-paapi5-python-sdk | ||
| ``` | ||
| This installs the core dependencies (`requests`, `aiohttp`, `cachetools`). | ||
| 2. **Optional: Install Redis Support**: | ||
| If you want to use Redis for caching, install the `redis` extra: | ||
| ```bash | ||
| pip install amazon-paapi5-python-sdk[redis] | ||
| ``` | ||
| 3. **Verify Installation**: | ||
| Run the following command to check if the SDK is installed: | ||
| ```bash | ||
| python -c "import amazon_paapi5; print(amazon_paapi5.__version__)" | ||
| ``` | ||
| It should output `1.0.5`. | ||
| ## Getting Started | ||
| To use the SDK, you need an Amazon Associates account with access to PA-API 5.0 credentials: | ||
| - **Access Key**: Your API access key. | ||
| - **Secret Key**: Your API secret key. | ||
| - **Partner Tag**: Your Amazon Associates tag (e.g., `yourtag-20`). | ||
| - **Encryption Key**: Used for AWS V4 signature generation (often the same as the secret key or a separate key). | ||
| ### Basic Usage Example | ||
| Here’s a simple example to search for laptops in the Electronics category on Amazon India: | ||
| ```python | ||
| from amazon_paapi5.client import Client | ||
| from amazon_paapi5.config import Config | ||
| from amazon_paapi5.models.search_items import SearchItemsRequest | ||
| # Step 1: Configure the SDK | ||
| config = Config( | ||
| access_key="YOUR_ACCESS_KEY", # Replace with your access key | ||
| secret_key="YOUR_SECRET_KEY", # Replace with your secret key | ||
| partner_tag="YOUR_PARTNER_TAG", # Replace with your partner tag | ||
| encryption_key="YOUR_ENCRYPTION_KEY",# Replace with your encryption key | ||
| marketplace="www.amazon.in", # Use India marketplace | ||
| ) | ||
| # Step 2: Create a client | ||
| client = Client(config) | ||
| # Step 3: Create a search request | ||
| request = SearchItemsRequest( | ||
| keywords="Laptop", # Search term | ||
| search_index="Electronics", # Category | ||
| item_count=10, # Number of results | ||
| partner_tag=config.partner_tag, # Your partner tag | ||
| ) | ||
| # Step 4: Make the API call | ||
| try: | ||
| response = client.search_items(request) | ||
| print("Search completed successfully!") | ||
| for item in response.items: | ||
| print(f"ASIN: {item.asin}, Title: {item.title}, Price: {item.price}, URL: {item.detail_page_url}") | ||
| except Exception as e: | ||
| print(f"Error: {str(e)}") | ||
| ``` | ||
| This code: | ||
| - Configures the SDK for the Indian marketplace. | ||
| - Searches for “Laptop” in the Electronics category. | ||
| - Prints the ASIN, title, price, and URL for each result. | ||
| ### Asynchronous Example | ||
| For better performance in applications with many API calls, use the asynchronous method: | ||
| ```python | ||
| import asyncio | ||
| from amazon_paapi5.client import Client | ||
| from amazon_paapi5.config import Config | ||
| from amazon_paapi5.models.search_items import SearchItemsRequest | ||
| async def main(): | ||
| config = Config( | ||
| access_key="YOUR_ACCESS_KEY", | ||
| secret_key="YOUR_SECRET_KEY", | ||
| partner_tag="YOUR_PARTNER_TAG", | ||
| encryption_key="YOUR_ENCRYPTION_KEY", | ||
| marketplace="www.amazon.in", | ||
| ) | ||
| client = Client(config) | ||
| request = SearchItemsRequest( | ||
| keywords="Laptop", | ||
| search_index="Electronics", | ||
| item_count=10, | ||
| partner_tag=config.partner_tag, | ||
| ) | ||
| try: | ||
| response = await client.search_items_async(request) | ||
| for item in response.items: | ||
| print(f"Async - ASIN: {item.asin}, Title: {item.title}") | ||
| except Exception as e: | ||
| print(f"Error: {str(e)}") | ||
| asyncio.run(main()) | ||
| ``` | ||
| This uses `search_items_async` to make a non-blocking API call. | ||
| ### Batch Processing Example | ||
| The SDK supports batch processing to fetch multiple items in a single request, reducing API calls. Here’s an example to fetch up to 10 ASINs: | ||
| ```python | ||
| from amazon_paapi5.client import Client | ||
| from amazon_paapi5.config import Config | ||
| from amazon_paapi5.models.get_items import GetItemsRequest | ||
| config = Config( | ||
| access_key="YOUR_ACCESS_KEY", | ||
| secret_key="YOUR_SECRET_KEY", | ||
| partner_tag="YOUR_PARTNER_TAG", | ||
| encryption_key="YOUR_ENCRYPTION_KEY", | ||
| marketplace="www.amazon.in", | ||
| ) | ||
| client = Client(config) | ||
| request = GetItemsRequest( | ||
| item_ids=["B08L5V9T6R", "B07XVMJF2L", "B09F3T2K7P"], # Up to 10 ASINs | ||
| partner_tag=config.partner_tag, | ||
| resources=["ItemInfo.Title", "Offers.Listings.Price", "Images.Primary.Medium"], | ||
| ) | ||
| try: | ||
| response = client.get_items(request) | ||
| print("Batch GetItems completed successfully!") | ||
| for item in response.items: | ||
| print(f"ASIN: {item.asin}, Title: {item.title}, Price: {item.price}") | ||
| except Exception as e: | ||
| print(f"Error: {str(e)}") | ||
| ``` | ||
| This fetches details for multiple ASINs in one API call, improving efficiency. | ||
| ## Supported Marketplaces | ||
| The SDK supports 18 Amazon marketplaces, each with its own region and host. When you set a `marketplace` in the `Config`, the SDK automatically configures the correct `region` and `host`. Here’s the full list: | ||
| | Marketplace | Region | Host | | ||
| |-------------|-------------|---------------------------------| | ||
| | www.amazon.com | us-east-1 | webservices.amazon.com | | ||
| | www.amazon.co.uk | eu-west-1 | webservices.amazon.co.uk | | ||
| | www.amazon.de | eu-west-1 | webservices.amazon.de | | ||
| | www.amazon.fr | eu-west-1 | webservices.amazon.fr | | ||
| | www.amazon.co.jp | us-west-2 | webservices.amazon.co.jp | | ||
| | www.amazon.ca | us-east-1 | webservices.amazon.ca | | ||
| | www.amazon.com.au | us-west-2 | webservices.amazon.com.au | | ||
| | www.amazon.in | us-east-1 | webservices.amazon.in | | ||
| | www.amazon.com.br | us-east-1 | webservices.amazon.com.br | | ||
| | www.amazon.it | eu-west-1 | webservices.amazon.it | | ||
| | www.amazon.es | eu-west-1 | webservices.amazon.es | | ||
| | www.amazon.com.mx | us-east-1 | webservices.amazon.com.mx | | ||
| | www.amazon.nl | eu-west-1 | webservices.amazon.nl | | ||
| | www.amazon.sg | us-west-2 | webservices.amazon.sg | | ||
| | www.amazon.ae | eu-west-1 | webservices.amazon.ae | | ||
| | www.amazon.sa | eu-west-1 | webservices.amazon.sa | | ||
| | www.amazon.com.tr | eu-west-1 | webservices.amazon.com.tr | | ||
| | www.amazon.se | eu-west-1 | webservices.amazon.se | | ||
| Example: Switch to the UK marketplace dynamically: | ||
| ```python | ||
| config.set_marketplace("www.amazon.co.uk") # Updates region to 'eu-west-1' and host to 'webservices.amazon.co.uk' | ||
| ``` | ||
| ## File Structure | ||
| The SDK is organized into a clear, modular structure. Here’s what each part does: | ||
| - **`src/amazon_paapi5/`**: The main library code. | ||
| - `__init__.py`: Defines the library version (`1.0.5`). | ||
| - `client.py`: The core `Client` class that handles API requests, throttling, caching, and signature generation. | ||
| - `config.py`: Manages configuration (credentials, marketplace, throttling delay) and supports 18 marketplaces. | ||
| - `signature.py`: Generates AWS V4 signatures for secure API authentication. | ||
| - `resources.py`: Defines and validates valid API resources (e.g., `ItemInfo.Title`). | ||
| - `models/`: | ||
| - `__init__.py`: Empty file to make `models/` a package. | ||
| - `search_items.py`: Defines `SearchItemsRequest` and `SearchItemsResponse` for searching products. | ||
| - `get_items.py`: Defines `GetItemsRequest` and `GetItemsResponse` for fetching product details. | ||
| - `get_variations.py`: Defines `GetVariationsRequest` and `GetVariationsResponse` for product variations. | ||
| - `get_browse_nodes.py`: Defines `GetBrowseNodesRequest` and `GetBrowseNodesResponse` for browse nodes. | ||
| - `utils/`: | ||
| - `__init__.py`: Empty file to make `utils/` a package. | ||
| - `throttling.py`: Implements the `Throttler` class for smart throttling with exponential backoff and queue management. | ||
| - `cache.py`: Implements the `Cache` class for in-memory and Redis caching. | ||
| - `async_helper.py`: Provides utilities for running async functions. | ||
| - `exceptions.py`: Defines custom exceptions (`AmazonAPIException`, `AuthenticationException`, etc.) for error handling. | ||
| - **`tests/`**: Unit tests to ensure the library works correctly. | ||
| - `__init__.py`: Empty file to make `tests/` a package. | ||
| - `test_client.py`: Tests the `Client` class and signature generation. | ||
| - `test_search_items.py`: Tests the `SearchItems` operation. | ||
| - `test_get_items.py`: Tests the `GetItems` operation. | ||
| - `test_get_variations.py`: Tests the `GetVariations` operation. | ||
| - `test_get_browse_nodes.py`: Tests the `GetBrowseNodes` operation. | ||
| - `test_cache.py`: Tests the caching functionality. | ||
| - `test_config.py`: Tests the marketplace configuration. | ||
| - **`examples/`**: Example scripts to help you get started. | ||
| - `search_items.py`: Shows how to search for products (sync and async). | ||
| - `get_items.py`: Shows how to fetch product details by ASIN with batch processing. | ||
| - `get_variations.py`: Shows how to get product variations. | ||
| - `get_browse_nodes.py`: Shows how to retrieve browse node information. | ||
| - **`setup.py`**: Configures the library for installation via `pip`. | ||
| - **`requirements.txt`**: Lists dependencies (`requests`, `aiohttp`, `cachetools`, `redis`, `pytest`). | ||
| - **`.gitignore`**: Ignores unnecessary files (e.g., `__pycache__`, `venv/`). | ||
| - **`README.md`**: This file, providing comprehensive documentation. | ||
| - **`CONTRIBUTING.md`**: Instructions for contributing to the project. | ||
| - **`LICENSE`**: Apache-2.0 License file. | ||
| ## How the SDK Works | ||
| Let’s break down how the SDK handles a typical API request, so even a new coder can understand the flow: | ||
| 1. **Configuration**: | ||
| - You create a `Config` object with your credentials (`access_key`, `secret_key`, `partner_tag`, `encryption_key`) and a `marketplace` (e.g., `www.amazon.in`). | ||
| - The `Config` class uses the `MARKETPLACES` dictionary to set the correct `region` (e.g., `us-east-1`) and `host` (e.g., `webservices.amazon.in`). | ||
| 2. **Client Creation**: | ||
| - The `Client` class takes the `Config` object and initializes: | ||
| - A `Throttler` for rate limiting. | ||
| - A `Cache` for storing responses (in-memory or Redis). | ||
| - A `Signature` object for AWS V4 signature generation. | ||
| - HTTP sessions for connection reuse. | ||
| 3. **Request Preparation**: | ||
| - You create a request object (e.g., `SearchItemsRequest`) with parameters like `keywords` and `search_index`. | ||
| - The request object validates resources (using `resources.py`) and converts to a dictionary for the API call. | ||
| 4. **API Call**: | ||
| - The `Client` generates an AWS V4 signature (using `signature.py`) for authentication. | ||
| - The `Throttler` ensures the request doesn’t exceed rate limits, using a delay, exponential backoff, or queue. | ||
| - The request is sent using `requests` (sync) or `aiohttp` (async), with Gzip compression and connection reuse. | ||
| - The response is cached (using `cache.py`) to avoid repeated calls. | ||
| 5. **Response Handling**: | ||
| - The response is parsed into a type-safe object (e.g., `SearchItemsResponse`) with fields like `items`. | ||
| - If an error occurs, a specific exception (e.g., `ThrottleException`) is raised with recovery suggestions. | ||
| ## Advanced Features | ||
| ### Caching with Redis | ||
| To use Redis for caching (instead of in-memory), initialize the client with Redis enabled: | ||
| ```python | ||
| client = Client(config) | ||
| client.cache = client.cache.__class__(ttl=3600, use_redis=True, redis_url="redis://localhost:6379") | ||
| ``` | ||
| This stores API responses in a Redis server, useful for large-scale applications. | ||
| ### Custom Throttling | ||
| Adjust the throttling delay or retry settings in the `Config`: | ||
| ```python | ||
| config = Config( | ||
| access_key="YOUR_ACCESS_KEY", | ||
| secret_key="YOUR_SECRET_KEY", | ||
| partner_tag="YOUR_PARTNER_TAG", | ||
| encryption_key="YOUR_ENCRYPTION_KEY", | ||
| marketplace="www.amazon.in", | ||
| throttle_delay=2.0, # 2-second delay between requests | ||
| ) | ||
| ``` | ||
| ### Custom Resources | ||
| Specify custom resources in your request: | ||
| ```python | ||
| request = SearchItemsRequest( | ||
| keywords="Laptop", | ||
| search_index="Electronics", | ||
| partner_tag=config.partner_tag, | ||
| resources=["ItemInfo.Title", "Images.Primary.Medium"], | ||
| ) | ||
| ``` | ||
| The SDK validates these resources to ensure they’re valid for the operation. | ||
| ### Gzip Compression and Connection Reuse | ||
| The SDK optimizes network performance by: | ||
| - Requesting Gzip-compressed responses to reduce data transfer size. | ||
| - Reusing HTTP connections via `requests.Session` (sync) and `aiohttp.ClientSession` (async) for faster requests. | ||
| These features are enabled automatically and require no configuration. | ||
| ## Running Tests | ||
| To verify the SDK works correctly, run the unit tests: | ||
| 1. Install development dependencies: | ||
| ```bash | ||
| pip install -r requirements.txt | ||
| ``` | ||
| 2. Run tests with `pytest`: | ||
| ```bash | ||
| pytest tests/ | ||
| ``` | ||
| The tests cover client initialization, request validation, caching, and configuration. | ||
| ## Troubleshooting | ||
| - **Rate Limit Errors (`ThrottleException`)**: Increase the `throttle_delay` in `Config` or check your API quota in your Amazon Associates account. | ||
| - **Authentication Errors (`AuthenticationException`)**: Ensure your `access_key`, `secret_key`, `partner_tag`, and `encryption_key` are correct. | ||
| - **Invalid Parameters (`InvalidParameterException`)**: Check that your request parameters (e.g., `item_ids`, `browse_node_ids`) are valid and within limits (e.g., max 10 ASINs). | ||
| - **Invalid Resources (`ResourceValidationException`)**: Use only supported resources listed in `resources.py`. | ||
| - **Redis Connection Issues**: Verify your Redis server is running and the `redis_url` is correct. | ||
| ## Comparison with PHP SDK | ||
| This Python SDK is designed to match the functionality of the PHP SDK ([amazon-paapi5-php-sdk](https://github.com/RajpurohitHitesh/amazon-paapi5-php-sdk)): | ||
| - **Operations**: Both support `SearchItems`, `GetItems`, `GetVariations`, `GetBrowseNodes`. | ||
| - **Throttling**: Both use exponential backoff and queue management. | ||
| - **Caching**: The PHP SDK uses PSR-6; the Python SDK uses `cachetools` and Redis, providing equivalent functionality. | ||
| - **Async Support**: The PHP SDK uses Guzzle promises; the Python SDK uses `aiohttp` with `asyncio`. | ||
| - **Authentication**: Both implement AWS V4 signatures. | ||
| - **Marketplaces**: Both support the same 18 marketplaces. | ||
| - **Type Safety**: The PHP SDK uses strict typing; the Python SDK uses `dataclasses`. | ||
| - **Performance**: Both support batch processing (up to 10 ASINs), Gzip compression, connection reuse, and memory-efficient parsing. | ||
| - **Error Handling**: Both provide custom exception hierarchies for detailed error reporting. | ||
| The Python SDK is tailored for Python developers, leveraging Python’s ecosystem while maintaining the same robustness and ease of use as the PHP SDK.[](https://packagist.org/packages/rajpurohithitesh/amazon-paapi5-sdk)[](https://packagist.org/packages/rajpurohithitesh/amazon-paapi5-php-sdk) | ||
| ## License | ||
| This SDK is licensed under the [Apache-2.0 License](LICENSE), which allows you to use, modify, and distribute the code freely, as long as you include the license in your project. | ||
| ## Contributing | ||
| We welcome contributions from the community! Whether you’re fixing a bug, adding a feature, or improving documentation, check out the [CONTRIBUTING.md](CONTRIBUTING.md) file for detailed instructions on how to get started. | ||
| ## Support | ||
| If you have questions or run into issues: | ||
| - Open an issue on [GitHub](https://github.com/rajpurohithitesh/amazon-paapi5-python-sdk). | ||
| - Check the Amazon Associates documentation for PA-API 5.0 details. | ||
| - Reach out to the maintainer, Hitesh Rajpurohit, via GitHub. | ||
| Happy coding, and enjoy building with the Amazon PA-API 5.0 Python SDK! |
| requests>=2.28.0 | ||
| aiohttp>=3.9.0 | ||
| cachetools>=5.0.0 | ||
| cryptography>=3.4.8 | ||
| [dev] | ||
| pytest>=7.0.0 | ||
| pytest-asyncio>=0.18.0 | ||
| [redis] | ||
| redis>=4.0.0 |
| LICENSE | ||
| NOTICE | ||
| README.md | ||
| pyproject.toml | ||
| setup.py | ||
| src/amazon_paapi5/__init__.py | ||
| src/amazon_paapi5/client.py | ||
| src/amazon_paapi5/config.py | ||
| src/amazon_paapi5/exceptions.py | ||
| src/amazon_paapi5/monitoring.py | ||
| src/amazon_paapi5/resource_constants.py | ||
| src/amazon_paapi5/resources.py | ||
| src/amazon_paapi5/signature.py | ||
| src/amazon_paapi5/models/__init__.py | ||
| src/amazon_paapi5/models/get_browse_nodes.py | ||
| src/amazon_paapi5/models/get_items.py | ||
| src/amazon_paapi5/models/get_variations.py | ||
| src/amazon_paapi5/models/search_items.py | ||
| src/amazon_paapi5/utils/__init__.py | ||
| src/amazon_paapi5/utils/async_helper.py | ||
| src/amazon_paapi5/utils/cache.py | ||
| src/amazon_paapi5/utils/file_cache.py | ||
| src/amazon_paapi5/utils/throttling.py | ||
| src/amazon_paapi5_python_sdk.egg-info/PKG-INFO | ||
| src/amazon_paapi5_python_sdk.egg-info/SOURCES.txt | ||
| src/amazon_paapi5_python_sdk.egg-info/dependency_links.txt | ||
| src/amazon_paapi5_python_sdk.egg-info/requires.txt | ||
| src/amazon_paapi5_python_sdk.egg-info/top_level.txt | ||
| tests/test_cache.py | ||
| tests/test_client.py | ||
| tests/test_config.py | ||
| tests/test_get_browse_nodes.py | ||
| tests/test_get_items.py | ||
| tests/test_get_variations.py | ||
| tests/test_search_items.py |
| amazon_paapi5 |
| import pytest | ||
| from amazon_paapi5.utils.cache import Cache | ||
| def test_cache_set_get(): | ||
| cache = Cache(ttl=10, maxsize=2) | ||
| cache.set("key1", {"data": "value1"}) | ||
| assert cache.get("key1") == {"data": "value1"} | ||
| cache.set("key2", {"data": "value2"}) | ||
| cache.set("key3", {"data": "value3"}) # Should evict key1 | ||
| assert cache.get("key1") is None |
| import pytest | ||
| from amazon_paapi5.client import Client | ||
| from amazon_paapi5.config import Config | ||
| from amazon_paapi5.signature import Signature | ||
| @pytest.fixture | ||
| def config(): | ||
| return Config( | ||
| access_key="test_key", | ||
| secret_key="test_secret", | ||
| partner_tag="test_tag", | ||
| encryption_key="test_encryption", | ||
| marketplace="www.amazon.com", | ||
| ) | ||
| @pytest.fixture | ||
| def config_no_encryption(): | ||
| return Config( | ||
| access_key="test_key", | ||
| secret_key="test_secret", | ||
| partner_tag="test_tag", | ||
| marketplace="www.amazon.com", | ||
| ) | ||
| @pytest.fixture | ||
| def client(config): | ||
| return Client(config) | ||
| @pytest.fixture | ||
| def client_no_encryption(config_no_encryption): | ||
| return Client(config_no_encryption) | ||
| def test_client_initialization_with_encryption(client, config): | ||
| """Test client initialization with encryption enabled.""" | ||
| # With encryption, the config values will be encrypted | ||
| assert client.config.access_key != "test_key" # Should be encrypted | ||
| assert client.config.throttle_delay == pytest.approx(1.0) | ||
| assert isinstance(client.signature, Signature) | ||
| # The signature should use the decrypted credentials | ||
| assert client.signature.access_key == "test_key" | ||
| assert client.signature.region == "us-east-1" | ||
| # Verify credential manager exists | ||
| assert client.credential_manager is not None | ||
| # Test decryption works | ||
| decrypted = client.credential_manager.decrypt_credentials({ | ||
| 'access_key': client.config.access_key, | ||
| 'secret_key': client.config.secret_key | ||
| }) | ||
| assert decrypted['access_key'] == "test_key" | ||
| assert decrypted['secret_key'] == "test_secret" | ||
| def test_client_initialization_without_encryption(client_no_encryption, config_no_encryption): | ||
| """Test client initialization without encryption.""" | ||
| # Without encryption, the config values should remain as-is | ||
| assert client_no_encryption.config.access_key == "test_key" | ||
| assert client_no_encryption.config.secret_key == "test_secret" | ||
| assert client_no_encryption.config.throttle_delay == pytest.approx(1.0) | ||
| assert isinstance(client_no_encryption.signature, Signature) | ||
| assert client_no_encryption.signature.access_key == "test_key" | ||
| assert client_no_encryption.signature.region == "us-east-1" | ||
| # Verify no credential manager exists | ||
| assert client_no_encryption.credential_manager is None | ||
| # Keep the original test name but fix it to work without encryption | ||
| def test_client_initialization(client_no_encryption, config_no_encryption): | ||
| """Original test - now uses no encryption to maintain backward compatibility.""" | ||
| assert client_no_encryption.config.access_key == "test_key" | ||
| assert client_no_encryption.config.throttle_delay == pytest.approx(1.0) | ||
| assert isinstance(client_no_encryption.signature, Signature) | ||
| assert client_no_encryption.signature.access_key == "test_key" | ||
| assert client_no_encryption.signature.region == "us-east-1" |
| import pytest | ||
| from amazon_paapi5.config import Config | ||
| def test_marketplace_config(): | ||
| config = Config( | ||
| access_key="test_key", | ||
| secret_key="test_secret", | ||
| partner_tag="test_tag", | ||
| encryption_key="test_encryption", | ||
| marketplace="www.amazon.co.uk", | ||
| ) | ||
| assert config.marketplace == "www.amazon.co.uk" | ||
| assert config.region == "eu-west-1" | ||
| assert config.host == "webservices.amazon.co.uk" | ||
| config.set_marketplace("www.amazon.in") | ||
| assert config.marketplace == "www.amazon.in" | ||
| assert config.region == "us-east-1" | ||
| assert config.host == "webservices.amazon.in" | ||
| with pytest.raises(ValueError): | ||
| config.set_marketplace("invalid.marketplace") |
| import pytest | ||
| from amazon_paapi5.models.get_browse_nodes import GetBrowseNodesRequest | ||
| from amazon_paapi5.resources import validate_resources | ||
| @pytest.mark.asyncio | ||
| async def test_get_browse_nodes_request(): | ||
| request = GetBrowseNodesRequest( | ||
| browse_node_ids=["123456", "789012"], | ||
| partner_tag="test_tag", | ||
| ) | ||
| assert request.to_dict()["BrowseNodeIds"] == ["123456", "789012"] | ||
| assert "BrowseNodeInfo.BrowseNodes" in request.resources | ||
| validate_resources("GetBrowseNodes", request.resources) |
| import pytest | ||
| from amazon_paapi5.models.get_items import GetItemsRequest | ||
| from amazon_paapi5.resources import validate_resources | ||
| @pytest.mark.asyncio | ||
| async def test_get_items_request(): | ||
| request = GetItemsRequest( | ||
| item_ids=["B08L5V9T6R", "B07XVMJF2L"], | ||
| partner_tag="test_tag", | ||
| ) | ||
| assert request.to_dict()["ItemIds"] == ["B08L5V9T6R", "B07XVMJF2L"] | ||
| assert "ItemInfo.Title" in request.resources | ||
| validate_resources("GetItems", request.resources) |
| import pytest | ||
| from amazon_paapi5.models.get_variations import GetVariationsRequest | ||
| from amazon_paapi5.resources import validate_resources | ||
| @pytest.mark.asyncio | ||
| async def test_get_variations_request(): | ||
| request = GetVariationsRequest( | ||
| asin="B08L5V9T6R", | ||
| variation_page=1, | ||
| partner_tag="test_tag", | ||
| ) | ||
| assert request.to_dict()["ASIN"] == "B08L5V9T6R" | ||
| assert "VariationSummary.VariationDimension" in request.resources | ||
| validate_resources("GetVariations", request.resources) |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
2881
28.62%191634
-0.14%36
-2.7%