From 3c5489ab4c3ee71a27aafb49d45ffbbae5c58477 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 13:33:20 +0000 Subject: [PATCH] docs: correct the migration guide and complete what the docs left out The migration guide told users that `include_unavailable`, `availability` and `delivery_flags` were gone, when the Creators API accepts all three, imported `get_asin` from a private path and listed only part of the exceptions of the module. The accepted values of `version`, the countries and the marketplace each one maps to, and the `marketplace` argument had no documentation at all, so the only way to find them was to read the code. The usage guide and the README had also drifted apart, each one holding sections the other lacked, and both promised the same methods in the two clients without telling that `close` only exists in the synchronous one. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HkMXgQgzMKJBkEE7Cw3jLi --- .github/ISSUE_TEMPLATE/---bug-report.md | 2 +- CHANGELOG.md | 12 ++ CONTRIBUTING.md | 89 +++++++++ README.md | 180 +++++++++++++------ docs/amazon_creatorsapi.aio.rst | 30 +++- docs/amazon_creatorsapi.core.rst | 8 +- docs/amazon_creatorsapi.errors.rst | 7 +- docs/amazon_creatorsapi.models.rst | 7 +- docs/amazon_creatorsapi.rst | 7 +- docs/conf.py | 13 +- docs/index.rst | 56 ++++-- docs/pages/migration-guide-6.md | 82 ++++----- docs/pages/usage-guide.md | 229 +++++++++++++++++++----- 13 files changed, 548 insertions(+), 174 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md index a0fa1fc..8195584 100644 --- a/.github/ISSUE_TEMPLATE/---bug-report.md +++ b/.github/ISSUE_TEMPLATE/---bug-report.md @@ -17,7 +17,7 @@ assignees: '' "Add some code that helps understanding the issue" ``` **Current behavior** -A clear and concise description of what is currenctly happening. +A clear and concise description of what is currently happening. **Expected behavior** A clear and concise description of what you expected to happen. diff --git a/CHANGELOG.md b/CHANGELOG.md index c6c98e2..f2d999d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- The values accepted by `version`, the countries and the marketplace each one maps to, and the `marketplace` argument are documented, instead of having to read the code to find them +- `CONTRIBUTING.md`, with the setup of the project, the commands of the `Makefile`, the conventions of the code and the tests, and what a pull request is expected to carry + ### Changed - A `version` of a family that the library cannot authenticate is rejected even when `auth_endpoint` is given, instead of being sent with the Cognito flow and rejected by Amazon without an explanation - The error of an unsupported version tells that a newer version of a known family can be used by providing its `auth_endpoint` - The auth flow of a version and the `Authorization` header it expects are decided in a single place, and the copies bundled in the SDK are pinned to them by tests, so a bump of the SDK cannot leave both halves disagreeing +- The usage guide covers everything the README does, so the documentation does not have to be read in both places, and the README links to it + +### Fixed + +- The migration guide listed `include_unavailable`, `availability` and `delivery_flags` as removed by the Creators API, which accepts the three of them +- The migration guide imported `get_asin` from `amazon_creatorsapi.core` and did not list `AuthenticationError`, `AccessDeniedError` and `ResourceNotFoundError` among the exceptions of the module +- Documentation that promised the same methods in both clients without telling that `close` only exists in the synchronous one, as every request of an asynchronous client used outside `async with` closes its own connection ## [7.4.0] - 2026-09-04 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ac9e6d7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,89 @@ +# Contributing + +Thanks for taking the time to contribute. This guide covers how to set the project up, +what the checks expect and how a change should be shaped. + +## Setting up + +The project is managed with [uv](https://docs.astral.sh/uv/): + +```bash +git clone https://github.com/sergioteula/python-amazon-paapi.git +cd python-amazon-paapi +uv sync --extra async +make setup +``` + +`make setup` installs the pre-commit hooks, which run Ruff, mypy and the tests before +every commit. The `async` extra is needed to run the tests of `AsyncAmazonCreatorsApi`. + +Copy `.env.template` to `.env` and fill it with your Creators API credentials to run the +integration tests. Without it the unit tests still run, and the integration tests are +skipped and reported as such. + +## Commands + +| Command | What it does | +| --------------------------- | ------------------------------------------------------- | +| `make test` | Run the test suite | +| `make coverage` | Run the tests with a coverage report | +| `make test-all-python-tags` | Run the tests on every supported Python version | +| `make lint` | Run Ruff and apply the fixes it can make | +| `make format` | Format the code with Ruff | +| `make mypy` | Check the types | +| `make pre-commit` | Run every hook against all the files, as CI does | +| `make docs` | Build the documentation into `docs/_build/html` | + +## Layout + +| Path | What it holds | +| ------------------------- | ------------------------------------------------------------------ | +| `amazon_creatorsapi/` | The library: the clients, the errors and the core utilities | +| `creatorsapi_python_sdk/` | The SDK generated from the API schema, vendored and not edited | +| `tests/` | The test suite, mirroring the structure of `amazon_creatorsapi` | +| `docs/` | The Sphinx documentation published on Read the Docs | + +`creatorsapi_python_sdk` is generated code: it is excluded from Ruff, mypy and the +formatter, and it is replaced wholesale when the SDK is bumped, so a fix belongs in +`amazon_creatorsapi` and not there. + +## Style + +- Everything in English: code, comments, docstrings and commit messages. +- Docstrings on every function, and type hints on every signature. +- Comments only where the reason for the code is not obvious from the code itself. +- No abbreviations or single-letter names. +- `noqa` only when there is no other way, and never without a reason. + +## Tests + +- Tests come with the change, following TDD. +- The `tests` directory mirrors the structure of `amazon_creatorsapi`. +- `unittest.TestCase` with `setUp` and `tearDown`, unittest assertions and `@patch` + decorators rather than context managers. +- Coverage may not drop below 98%, which is enforced by the test run. + +## Documentation + +A change that adds or changes something a user can see belongs in the documentation as +well: + +- `README.md` and `docs/pages/usage-guide.md` cover the same ground, the guide in more + depth. Keep both in step. +- Docstrings feed the API reference, so a new argument is documented there too. + +## Sending a change + +1. Add an entry to `CHANGELOG.md`, under `[Unreleased]`, in the section that matches the + change: `Added`, `Changed`, `Fixed` or `Removed`. CI rejects a pull request that does + not touch the changelog. +2. Run `make pre-commit` and make sure everything passes. +3. Open the pull request describing what changes for the user and why. + +The version lives in `pyproject.toml`, `docs/conf.py` and `CHANGELOG.md`, and +`scripts/check_version.py` checks that the three agree. Only a release changes them. + +## Getting help + +Ask in the [Telegram group](https://t.me/PythonAmazonPAAPI) or open an +[issue](https://github.com/sergioteula/python-amazon-paapi/issues). diff --git a/README.md b/README.md index e1084a4..8e81556 100644 --- a/README.md +++ b/README.md @@ -10,25 +10,65 @@ A Python wrapper for the [Amazon Creators API](https://webservices.amazon.com/cr ## Features - 🎯 **Simple object-oriented interface** for easy integration -- � **Async/await support** for high-performance applications -- �🔍 **Product search** by keywords, categories, or browse nodes +- ⚡ **Async/await support** for high-performance applications +- 🔍 **Product search** by keywords, categories, or browse nodes - 📦 **Product details** via ASIN or Amazon URL - 🔄 **Item variations** support (size, color, etc.) - 📊 **Feeds and reports** listing and download URLs - 💰 **OffersV2 support** for enhanced pricing and offer details -- 🌍 **20+ countries** supported -- 🛡️ **Built-in throttling** to avoid API rate limits +- 🌍 **20 marketplaces** supported +- 🛡️ **Built-in throttling and retries** to avoid API rate limits - 📝 **Full type hints** for better IDE support +## Table of contents + +- [Installation](#installation) +- [Credentials](#credentials) +- [Quick start](#quick-start) +- [Usage examples](#usage-examples) +- [Configuration](#configuration) +- [Error handling](#error-handling) +- [Async support](#async-support) +- [Working with models](#working-with-models) +- [Documentation](#documentation) +- [Contributing](#contributing) + ## Installation ```bash pip install python-amazon-paapi --upgrade ``` ---- +Python 3.9 or newer is required. Install the `async` extra to use the asynchronous +client: + +```bash +pip install python-amazon-paapi[async] --upgrade +``` + +## Credentials + +Four values are needed to create a client, and all of them come from the Amazon +Associates Creators API portal: -## Quick Start +| Argument | What it is | +| ------------------- | --------------------------------------------------------- | +| `credential_id` | Identifier of your Creators API credentials | +| `credential_secret` | Secret of your Creators API credentials | +| `version` | API version your credentials were issued for | +| `tag` | Your affiliate tracking id, also known as the partner tag | + +`version` is the version of the Creators API, not of this library: it is the value Amazon +gave you along with the credentials, and it also decides which endpoint issues the OAuth2 +token. The accepted values are `2.1`, `2.2`, `2.3`, `3.1`, `3.2` and `3.3`; any other one +raises `ValueError` when the client is created. + +The marketplace is chosen with `country`, which accepts `AU`, `BE`, `BR`, `CA`, `DE`, +`ES`, `FR`, `IN`, `IT`, `JP`, `MX`, `NL`, `PL`, `SA`, `SE`, `SG`, `TR`, `UK`, `US` and +`AE`, either as a string or through the `Country` constants. Pass `marketplace` instead +to give the host directly, such as `marketplace="www.amazon.es"`. + +## Quick start ```python from amazon_creatorsapi import AmazonCreatorsApi, Country @@ -50,9 +90,12 @@ print(items[0].item_info.title.display_value) items = api.get_items(["https://www.amazon.com/dp/B01N5IB20Q"]) ``` -## Usage Examples +Every field of a response is optional, as Amazon only sends what it has for an item, so +check a value before using it when the item may not carry it. + +## Usage examples -### Get Multiple Items +### Get multiple items ```python items = api.get_items(["B01N5IB20Q", "B01F9G43WU"]) @@ -61,7 +104,7 @@ for item in items: ``` Items come back in the order they were requested, duplicates are asked for -only once, and requests with more items than the API accepts at once are +only once, and requests with more items than the API accepts at once (10) are split into as many calls as needed, so any amount of items can be requested: ```python @@ -83,7 +126,7 @@ for item in items: print(f"{item.asin} is not available") ``` -### Search Products +### Search products ```python results = api.search_items(keywords="nintendo switch") @@ -91,7 +134,9 @@ for item in results.items: print(item.item_info.title.display_value) ``` -A search needs at least one of `keywords`, `actor`, `artist`, `author`, `brand`, `title`, `browse_node_id` or `search_index`, and only returns the items available for purchase unless asked otherwise: +A search needs at least one of `keywords`, `actor`, `artist`, `author`, `brand`, `title`, +`browse_node_id` or `search_index`, and only returns the items available for purchase +unless asked otherwise: ```python from amazon_creatorsapi.models import Availability @@ -102,7 +147,7 @@ results = api.search_items( ) ``` -### Get Product Variations +### Get product variations ```python # Using ASIN @@ -115,7 +160,7 @@ for item in variations.items: print(item.detail_page_url) ``` -### Get Browse Node Information +### Get browse node information ```python nodes = api.get_browse_nodes(["667049031"]) @@ -123,7 +168,7 @@ for node in nodes: print(node.display_name) ``` -### Feeds and Reports +### Feeds and reports Feeds and reports are listed per marketplace, and downloaded through the temporary URL returned by the API: @@ -154,7 +199,7 @@ from amazon_creatorsapi import get_asin asin = get_asin("https://www.amazon.com/dp/B01N5IB20Q") ``` -### Using OffersV2 Resources +### Using OffersV2 resources ```python items = api.get_items(["B01N5IB20Q"]) @@ -165,47 +210,66 @@ if item.offers_v2 and item.offers_v2.listings: print(listing.merchant_info.name) ``` +## Configuration + ### Throttling -Throttling value represents the wait time in seconds between API calls, being the default value 1 second. Use it to avoid reaching Amazon request limits. +Throttling value represents the wait time in seconds between API calls, being the default +value 1 second. Use it to avoid reaching Amazon request limits. ```python -amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, throttling=4) # Makes 1 request every 4 seconds -amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, throttling=0) # No wait time between requests +api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, throttling=4) # Makes 1 request every 4 seconds +api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, throttling=0) # No wait time between requests ``` -### Closing the client +The interval is kept per client and is safe to share between threads. + +### Timeout -The client keeps a pool of connections open, so it is meant to be created once and reused. Close it, or use it as a context manager, when it is not going to be used again: +Timeout value represents the number of seconds to wait for a response before failing, +being the default value 30 seconds. Use `None` to wait indefinitely. ```python -with AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY) as amazon: - items = amazon.get_items(["B01N5IB20Q"]) +api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=10) # Fails after 10 seconds +api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=0.5) # Fails after half a second ``` -### Timeout +It applies to every API request, including the OAuth2 token refresh. -Timeout value represents the number of seconds to wait for a response before failing, being the default value 30 seconds. Use `None` to wait indefinitely. +### Retries + +Amazon asks clients to back off and try again when it throttles a request or fails to +serve it. The client does that on its own, waiting longer before every attempt and +honouring the `Retry-After` header when the API sends it. An expired token is refreshed +once and the request is sent again. ```python -amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=10) # Fails after 10 seconds -amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=0.5) # Fails after half a second +api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, retries=5) # Up to 5 extra attempts +api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, retries=0) # Fail on the first error ``` -It applies to every API request, including the OAuth2 token refresh. +The default is 3 extra attempts, and only the failures that Amazon asks to retry are +retried: a rejected request fails right away. -### Retries +### Closing the client -Amazon asks clients to back off and try again when it throttles a request or fails to serve it. The client does that on its own, waiting longer before every attempt and honouring the `Retry-After` header when the API sends it. An expired token is refreshed once and the request is sent again. +The client keeps a pool of connections open, so it is meant to be created once and +reused. Close it, or use it as a context manager, when it is not going to be used again: ```python -amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, retries=5) # Up to 5 extra attempts -amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, retries=0) # Fail on the first error +with AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY) as api: + items = api.get_items(["B01N5IB20Q"]) ``` -### Custom Endpoints +### Custom endpoints -The base URL of the API and the one used to get the OAuth2 token can be replaced, which is useful to run the tests of a project against a mock server. Providing `auth_endpoint` also makes valid a `version` that is not in the list yet, so a new one can be used before the library knows about it, as long as it belongs to a family that the library can authenticate: `2.x` with Cognito and `3.x` with Login with Amazon. A version of any other family is rejected, as a new family brings a new authentication flow and not just another endpoint: +The base URL of the API and the one used to get the OAuth2 token can be replaced, which +is useful to run the tests of a project against a mock server. Providing `auth_endpoint` +also makes valid a `version` that is not in the list yet, so a new one can be used before +the library knows about it, as long as it belongs to a family that the library can +authenticate: `2.x` with Cognito and `3.x` with Login with Amazon. A version of any other +family is rejected, as a new family brings a new authentication flow and not just another +endpoint: ```python api = AmazonCreatorsApi( @@ -219,9 +283,12 @@ api = AmazonCreatorsApi( ) ``` -### Error Handling +## Error handling -Every error raised by the library inherits from `AmazonCreatorsApiError`, so a single `except` covers them all. The message carries the reason given by Amazon, the fields that failed validation and the identifier of the request, which is what Amazon support asks for: +Every error raised by the library inherits from `AmazonCreatorsApiError`, so a single +`except` covers them all. The message carries the reason given by Amazon, the fields that +failed validation and the identifier of the request, which is what Amazon support asks +for: | Exception | Raised when | | --- | --- | @@ -245,7 +312,7 @@ except AmazonCreatorsApiError as error: print(error) ``` -### Async Support +## Async support For async/await applications, use the async version of the API with `httpx`: @@ -253,11 +320,12 @@ For async/await applications, use the async version of the API with `httpx`: pip install python-amazon-paapi[async] --upgrade ``` -The async API provides the same methods as the synchronous version, but they must be called with `await`: +The async API provides the same methods, parameters and errors as the synchronous +version, and they are called with `await`: ```python -from amazon_creatorsapi.aio import AsyncAmazonCreatorsApi from amazon_creatorsapi import Country +from amazon_creatorsapi.aio import AsyncAmazonCreatorsApi # Use as async context manager (recommended for connection pooling) async with AsyncAmazonCreatorsApi( @@ -279,19 +347,21 @@ api = AsyncAmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY) items = await api.get_items(["B01N5IB20Q"]) ``` -> **Note:** All synchronous methods and parameters work identically in async mode. Use `async with` for better performance when making multiple API calls. +> **Note:** outside `async with`, every request opens and closes its own connection, so +> there is nothing to release and the async client has no `close` method. Use +> `async with` when making more than one call, to keep the connection open between them. -### Working with Models +## Working with models All SDK models are re-exported through `amazon_creatorsapi.models` for convenient access: ```python from amazon_creatorsapi.models import ( - Item, Condition, - SortBy, GetItemsResource, + Item, SearchItemsResource, + SortBy, ) # Use Condition enum for filtering @@ -304,7 +374,6 @@ results = api.search_items( ) # Specify which resources to retrieve -from amazon_creatorsapi.models import GetItemsResource resources = [ GetItemsResource.ITEM_INFO_DOT_TITLE, GetItemsResource.OFFERS_V2_DOT_LISTINGS_DOT_PRICE, @@ -312,31 +381,34 @@ resources = [ items = api.get_items(["B01N5IB20Q"], resources=resources) ``` +Every method asks for all the resources of its operation when `resources` is not given. +Narrowing the list makes the response smaller and faster, and the fields left out come +back as `None`. + --- ## Documentation -- 📖 [Full Documentation](https://python-amazon-paapi.readthedocs.io/) +- 📖 [Full documentation](https://python-amazon-paapi.readthedocs.io/) +- 📘 [Usage guide](https://python-amazon-paapi.readthedocs.io/en/latest/pages/usage-guide.html) +- 🔀 [Migration guide from `amazon_paapi`](https://python-amazon-paapi.readthedocs.io/en/latest/pages/migration-guide-6.html) - 📋 [Changelog](https://github.com/sergioteula/python-amazon-paapi/blob/master/CHANGELOG.md) -- 💬 [Telegram Support Group](https://t.me/PythonAmazonPAAPI) +- 💬 [Telegram support group](https://t.me/PythonAmazonPAAPI) ## Contributing -Contributions are welcome! To get started: - -1. Install [uv](https://docs.astral.sh/uv/) package manager -2. Clone and set up the project: +Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide, or +get started with: ```bash git clone https://github.com/sergioteula/python-amazon-paapi.git cd python-amazon-paapi -uv sync -uv run pre-commit install +uv sync --extra async +make setup +make test ``` -3. Copy `.env.template` to `.env` and add your API credentials for integration tests. - -Pre-commit hooks will automatically run Ruff, mypy, and tests before each commit. +Pre-commit hooks run Ruff, mypy and the tests before each commit. ## License diff --git a/docs/amazon_creatorsapi.aio.rst b/docs/amazon_creatorsapi.aio.rst index 430fc3f..9470ecc 100644 --- a/docs/amazon_creatorsapi.aio.rst +++ b/docs/amazon_creatorsapi.aio.rst @@ -1,18 +1,38 @@ Async API module -======================== +================ -The async version of the API provides the same functionality as the synchronous API, but uses -``async/await`` for non-blocking operations. Requires ``httpx`` for HTTP requests. +The async version of the API provides the same methods, parameters and errors as the +synchronous one, but uses ``async/await`` for non-blocking operations. It requires +``httpx``, which comes with the ``async`` extra. Installation ------------ -Install with async support: - .. code-block:: bash pip install python-amazon-paapi[async] --upgrade +Usage +----- + +Used as an async context manager, the client keeps a single connection open for every +request made inside it. Outside of one, each request opens and closes its own connection, +so there is nothing to release and the client has no ``close`` method. + +.. code-block:: python + + from amazon_creatorsapi import Country + from amazon_creatorsapi.aio import AsyncAmazonCreatorsApi + + async with AsyncAmazonCreatorsApi( + credential_id="your_credential_id", + credential_secret="your_credential_secret", + version="2.2", + tag="your-affiliate-tag", + country=Country.US, + ) as api: + items = await api.get_items(["B01N5IB20Q"]) + API Reference ------------- diff --git a/docs/amazon_creatorsapi.core.rst b/docs/amazon_creatorsapi.core.rst index 57c4fd9..704e91d 100644 --- a/docs/amazon_creatorsapi.core.rst +++ b/docs/amazon_creatorsapi.core.rst @@ -1,5 +1,9 @@ -Core Utilities -=========================== +Core utilities +============== + +The helpers that are useful outside the clients: the country constants accepted by the +``country`` argument, and the parser that pulls an ASIN out of an Amazon URL. Both are +also available directly in ``amazon_creatorsapi``. .. automodule:: amazon_creatorsapi.core :members: diff --git a/docs/amazon_creatorsapi.errors.rst b/docs/amazon_creatorsapi.errors.rst index 4eba938..90793f3 100644 --- a/docs/amazon_creatorsapi.errors.rst +++ b/docs/amazon_creatorsapi.errors.rst @@ -1,5 +1,10 @@ Errors -=================== +====== + +Every error raised by the library inherits from ``AmazonCreatorsApiError``, so a single +``except`` covers them all. The message carries the reason given by Amazon, the fields +that failed validation and the identifier of the request, which is what Amazon support +asks for. .. automodule:: amazon_creatorsapi.errors :members: diff --git a/docs/amazon_creatorsapi.models.rst b/docs/amazon_creatorsapi.models.rst index 71c5c10..435459c 100644 --- a/docs/amazon_creatorsapi.models.rst +++ b/docs/amazon_creatorsapi.models.rst @@ -1,5 +1,10 @@ Models -============= +====== + +Every model of the API, re-exported from the generated SDK so they can be imported from +``amazon_creatorsapi.models`` without navigating the SDK structure. The enums listed here +are the ones to use for ``condition``, ``availability``, ``sort_by``, ``resources`` and +the rest of the arguments that only accept a fixed set of values. .. automodule:: amazon_creatorsapi.models :members: diff --git a/docs/amazon_creatorsapi.rst b/docs/amazon_creatorsapi.rst index 2ed6d75..7a3b10a 100644 --- a/docs/amazon_creatorsapi.rst +++ b/docs/amazon_creatorsapi.rst @@ -1,8 +1,9 @@ API module -======================== +========== -This is the main class that provides authentication and methods for accessing the Amazon Creators -API. Instance it with your credentials and configuration. +This is the main class that provides authentication and methods for accessing the Amazon +Creators API. Instance it with your credentials and configuration, and reuse it: it keeps +a pool of connections open, which ``close`` and the context manager release. .. autoclass:: amazon_creatorsapi.api.AmazonCreatorsApi :members: diff --git a/docs/conf.py b/docs/conf.py index 3f4f4e4..a651060 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,10 +33,18 @@ # ones. extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon", "myst_parser"] +# Generate anchors for the headings of the Markdown pages, so they can be +# linked to across pages, as the migration guide does with the usage guide +myst_heading_anchors = 3 + # Don't show type hints in the signature - that just makes it hardly readable # and we document the types anyway autodoc_typehints = "none" +# Keep the members in the order they are written instead of alphabetically, so +# the methods of the clients are read in the order they are usually called +autodoc_member_order = "bysource" + # Add any paths that contain templates here, relative to this directory. # templates_path = ["_templates"] @@ -83,8 +91,3 @@ # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = "_static/pa-paapi-icon.ico" - - -# -- script stuff -------------------------------------------------------- - -# No custom events needed diff --git a/docs/index.rst b/docs/index.rst index 12501bf..0a19ca7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,34 +1,54 @@ -.. python-amazon-paapi documentation master file, created by - sphinx-quickstart on Mon Nov 22 18:33:43 2021. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. +Python Amazon Creators API +========================== -Welcome to Python Amazon Creators API's documentation! -====================================================== +A Python wrapper for the `Amazon Creators API +`_, which lets you search +Amazon products, read their details, offers and variations, and list the feeds and +reports of your account. -A simple Python wrapper for the Amazon Creators API. -This module allows interacting with Amazon using the official APIs in an easier way. +Install it with: + +.. code-block:: bash + + pip install python-amazon-paapi --upgrade + +Then create a client with the credentials of the Amazon Associates Creators API portal: + +.. code-block:: python + + from amazon_creatorsapi import AmazonCreatorsApi, Country + + api = AmazonCreatorsApi( + credential_id="your_credential_id", + credential_secret="your_credential_secret", + version="2.2", + tag="your-affiliate-tag", + country=Country.US, + ) + + items = api.get_items(["B01N5IB20Q"]) + print(items[0].item_info.title.display_value) + +The usage guide covers every option of the client, the asynchronous version and the +errors it raises. Introduction ---------------- +------------ .. toctree:: - :maxdepth: 1 + :maxdepth: 2 ./pages/usage-guide.md API Reference ----------------------- +------------- .. toctree:: + :maxdepth: 2 amazon_creatorsapi amazon_creatorsapi.aio amazon_creatorsapi.errors - -.. toctree:: - :maxdepth: 2 - amazon_creatorsapi.core .. toctree:: @@ -51,3 +71,9 @@ Changelog --------- See the `changelog `_ for a detailed history of changes. + +Support +------- + +Ask for help in the `Telegram group `_ or open an +`issue `_ on GitHub. diff --git a/docs/pages/migration-guide-6.md b/docs/pages/migration-guide-6.md index 61a3e74..c8b9dce 100644 --- a/docs/pages/migration-guide-6.md +++ b/docs/pages/migration-guide-6.md @@ -15,17 +15,13 @@ Key benefits of the Creators API: - Simplified credential management - New features and improvements from Amazon -## Changelog summary - -- New `amazon_creatorsapi` module for the Creators API -- The `amazon_paapi` module is deprecated in 6.0.0 and removed in 7.0.0 -- Different authentication credentials required - ## How to upgrade? Upgrading to the last version of this module is as easy as running this pip command: - pip install python-amazon-paapi --upgrade +```bash +pip install python-amazon-paapi --upgrade +``` ## Credential changes @@ -40,6 +36,9 @@ The Creators API uses different credentials than PAAPI: | Country | Country | You will need to obtain new credentials from the Amazon Associates Creators API portal. +The credentials are issued for a specific API version, which is a new value that PAAPI +did not have and that every client has to provide. See +[API versions](usage-guide.md#api-versions) for the accepted values. ## What should I change in my current code? @@ -71,64 +70,51 @@ You will need to obtain new credentials from the Amazon Associates Creators API ### Method signature changes -While the main methods have the same names, there are important parameter differences: - -#### `get_items` - -| Removed in Creators API | New in Creators API | -| ----------------------- | ----------------------------------- | -| `merchant` | `resources: list[GetItemsResource]` | -| `include_unavailable` | | -| `**kwargs` | | - -#### `search_items` - -| Removed in Creators API | New in Creators API | -| ----------------------- | -------------------------------------- | -| `availability` | `resources: list[SearchItemsResource]` | -| `delivery_flags` | | -| `merchant` | | -| `**kwargs` | | +The main methods keep their names, but some parameters are gone and a `resources` +parameter was added to every one of them, to choose which fields Amazon returns: -#### `get_variations` +| Method | Removed parameters | Added parameters | +| ------------------ | ----------------------- | ----------------------------------------- | +| `get_items` | `merchant`, `**kwargs` | `resources: list[GetItemsResource]` | +| `search_items` | `merchant`, `**kwargs` | `resources: list[SearchItemsResource]` | +| `get_variations` | `merchant`, `**kwargs` | `resources: list[GetVariationsResource]` | +| `get_browse_nodes` | `**kwargs` | `resources: list[GetBrowseNodesResource]` | -| Removed in Creators API | New in Creators API | -| ----------------------- | ---------------------------------------- | -| `merchant` | `resources: list[GetVariationsResource]` | -| `**kwargs` | | +The `merchant` filter has no equivalent in the Creators API. The `**kwargs` catch-all is +gone on purpose: an unknown argument is now rejected by the signature instead of being +forwarded to Amazon and silently ignored. -#### `get_browse_nodes` - -| Removed in Creators API | New in Creators API | -| ----------------------- | ----------------------------------------- | -| `**kwargs` | `resources: list[GetBrowseNodesResource]` | +Parameters such as `include_unavailable` in `get_items`, or `availability` and +`delivery_flags` in `search_items`, do exist in the Creators API and behave as they did +in PAAPI. See the [usage guide](usage-guide.md) for the complete signatures. #### Basic usage examples ```python # Get items -items = amazon.get_items(['B01N5IB20Q']) +items = amazon.get_items(["B01N5IB20Q"]) # Search items -results = amazon.search_items(keywords='nintendo') +results = amazon.search_items(keywords="nintendo") # Get variations -variations = amazon.get_variations('B01N5IB20Q') +variations = amazon.get_variations("B01N5IB20Q") # Get browse nodes -nodes = amazon.get_browse_nodes(['667049031']) +nodes = amazon.get_browse_nodes(["667049031"]) ``` ### Helper functions ```diff - from amazon_paapi import get_asin -+ from amazon_creatorsapi.core import get_asin ++ from amazon_creatorsapi import get_asin ``` ### Models module -Version 6.0 introduces a new `models` module that re-exports all SDK models for convenient access: +Version 6.0 introduced a `models` module that re-exports all SDK models for convenient +access: ```python from amazon_creatorsapi.models import ( @@ -146,19 +132,25 @@ This allows you to import models directly without navigating the SDK structure. ### Exceptions -Exception names have changed to use the `Error` suffix: +Exception names have changed to use the `Error` suffix, and every one of them inherits +from `AmazonCreatorsApiError`, so a single `except` covers them all: ```python from amazon_creatorsapi.errors import ( - AmazonCreatorsApiError, # Base exception + AmazonCreatorsApiError, # Base exception + AccessDeniedError, + AssociateValidationError, + AuthenticationError, InvalidArgumentError, ItemsNotFoundError, - TooManyRequestsError, - AssociateValidationError, RequestError, + ResourceNotFoundError, + TooManyRequestsError, ) ``` +See [Error handling](usage-guide.md#error-handling) for what raises each of them. + ## I need more help You can always ask for help in our [Telegram group](https://t.me/PythonAmazonPAAPI) or raise an issue on diff --git a/docs/pages/usage-guide.md b/docs/pages/usage-guide.md index 4b95434..2f5b5a6 100644 --- a/docs/pages/usage-guide.md +++ b/docs/pages/usage-guide.md @@ -1,4 +1,10 @@ -# Installation +# Usage guide + +The `amazon_creatorsapi` module provides access to Amazon's Creators API through a +synchronous client, `AmazonCreatorsApi`, and an asynchronous one, +`AsyncAmazonCreatorsApi`. Both expose the same methods and options. + +## Installation You can install or upgrade the module with: @@ -6,11 +12,77 @@ You can install or upgrade the module with: pip install python-amazon-paapi --upgrade ``` -# Usage Guide +Python 3.9 or newer is required. The asynchronous client needs an extra dependency, +which is installed with the `async` extra: + +```bash +pip install python-amazon-paapi[async] --upgrade +``` + +## Credentials + +Four values are needed to create a client, and all of them come from the Amazon +Associates Creators API portal: + +| Argument | What it is | +| ------------------- | ------------------------------------------------------------- | +| `credential_id` | Identifier of your Creators API credentials | +| `credential_secret` | Secret of your Creators API credentials | +| `version` | API version your credentials were issued for | +| `tag` | Your affiliate tracking id, also known as the partner tag | + +The marketplace to query is chosen with `country`, or with `marketplace` when you would +rather give the host directly. + +### API versions + +The `version` is not a version of this library: it is the version of the Creators API +that your credentials belong to, and it also decides which endpoint issues the OAuth2 +token. Use the one Amazon gave you: + +| Version | Authentication | Token endpoint region | +| ------- | ----------------- | --------------------- | +| `2.1` | Cognito | `us-east-1` | +| `2.2` | Cognito | `eu-south-2` | +| `2.3` | Cognito | `us-west-2` | +| `3.1` | Login with Amazon | `amazon.com` | +| `3.2` | Login with Amazon | `amazon.co.uk` | +| `3.3` | Login with Amazon | `amazon.co.jp` | + +A version outside this list raises `ValueError` when the client is created, unless it +belongs to the `2.x` or `3.x` families and an `auth_endpoint` is given. See +[Custom endpoints](#custom-endpoints). + +### Countries + +`country` accepts any of the codes below, which the client turns into the matching +marketplace. The `Country` class holds them as constants, so `Country.ES` and `"ES"` are +the same value: + +| Code | Marketplace | Code | Marketplace | +| ---- | ------------------- | ---- | ------------------- | +| `AU` | www.amazon.com.au | `NL` | www.amazon.nl | +| `BE` | www.amazon.com.be | `PL` | www.amazon.pl | +| `BR` | www.amazon.com.br | `SA` | www.amazon.sa | +| `CA` | www.amazon.ca | `SE` | www.amazon.se | +| `DE` | www.amazon.de | `SG` | www.amazon.sg | +| `ES` | www.amazon.es | `TR` | www.amazon.com.tr | +| `FR` | www.amazon.fr | `UK` | www.amazon.co.uk | +| `IN` | www.amazon.in | `US` | www.amazon.com | +| `IT` | www.amazon.it | `AE` | www.amazon.ae | +| `JP` | www.amazon.co.jp | `MX` | www.amazon.com.mx | + +A marketplace that is not in the list can still be used through the `marketplace` +argument, which takes precedence over `country`: + +```python +api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, marketplace="www.amazon.es") +``` -The `amazon_creatorsapi` module provides access to Amazon's Creators API. +Giving neither `country` nor `marketplace`, or an unknown country code, raises +`InvalidArgumentError`. -## Basic Usage +## Basic usage ```python from amazon_creatorsapi import AmazonCreatorsApi, Country @@ -31,7 +103,10 @@ print(items[0].item_info.title.display_value) items = api.get_items(["https://www.amazon.com/dp/B01N5IB20Q"]) ``` -## Get Multiple Items +Every field of a response is optional, as Amazon only sends what it has for an item, so +check a value before using it when the item may not carry it. + +## Get multiple items ```python items = api.get_items(["B01N5IB20Q", "B01F9G43WU"]) @@ -40,13 +115,16 @@ for item in items: ``` Items come back in the order they were requested, duplicates are asked for -only once, and requests with more items than the API accepts at once are +only once, and requests with more items than the API accepts at once (10) are split into as many calls as needed, so any amount of items can be requested: ```python items = api.get_items(asins) # Any amount of items, split into several calls ``` +A call that keeps failing after the retries raises, discarding the items returned by the +previous calls. + Amazon can answer with only some of the requested items, describing the missing ones as partial errors. Those errors are available in the returned list, and unavailable items can be included as an item holding only the ASIN: @@ -62,7 +140,13 @@ for item in items: print(f"{item.asin} is not available") ``` -## Search Products +The returned value is a `ResultList`, which behaves like a regular list and adds the +`errors` attribute. `get_browse_nodes` returns one as well. + +Without `include_unavailable`, a response holding none of the requested items raises +`ItemsNotFoundError`. + +## Search products ```python results = api.search_items(keywords="nintendo switch") @@ -70,7 +154,9 @@ for item in results.items: print(item.item_info.title.display_value) ``` -A search needs at least one of `keywords`, `actor`, `artist`, `author`, `brand`, `title`, `browse_node_id` or `search_index`, and only returns the items available for purchase unless asked otherwise: +A search needs at least one of `keywords`, `actor`, `artist`, `author`, `brand`, `title`, +`browse_node_id` or `search_index`, and only returns the items available for purchase +unless asked otherwise: ```python from amazon_creatorsapi.models import Availability @@ -81,7 +167,10 @@ results = api.search_items( ) ``` -## Get Product Variations +Results are paginated: `item_count` sets how many items a page holds (1-100, 10 by +default) and `item_page` which page to ask for (1-10, the first one by default). + +## Get product variations ```python # Using ASIN @@ -94,7 +183,10 @@ for item in variations.items: print(item.detail_page_url) ``` -## Get Browse Node Information +`variation_count` sets how many variations a page holds (1-10, 10 by default) and +`variation_page` which page to ask for (1 or above, the first one by default). + +## Get browse node information ```python nodes = api.get_browse_nodes(["667049031"]) @@ -102,7 +194,7 @@ for node in nodes: print(node.display_name) ``` -## Feeds and Reports +## Feeds and reports Feeds and reports are listed per marketplace, and downloaded through the temporary URL returned by the API: @@ -123,7 +215,7 @@ url = api.get_report("earnings.csv", report_type=ReportType.CREATOR_CONNECTIONS) The type is only needed to disambiguate a name available in more than one program, such as a report present in both Creator Central and Creator -Connections. +Connections. A name that does not exist raises `ResourceNotFoundError`. ## Get the ASIN from URL @@ -133,9 +225,14 @@ from amazon_creatorsapi import get_asin asin = get_asin("https://www.amazon.com/dp/B01N5IB20Q") ``` -## Using OffersV2 Resources +It accepts an ASIN, which it returns in uppercase, and the usual shapes of an Amazon +product URL. A text holding no ASIN raises `InvalidArgumentError`, which is also what +`get_items` and `get_variations` raise for an item they cannot parse. -OffersV2 provides enhanced pricing and offer details. All resources are included by default: +## Using OffersV2 resources + +OffersV2 provides enhanced pricing and offer details. All resources are included by +default: ```python items = api.get_items(["B01N5IB20Q"]) @@ -148,16 +245,21 @@ if item.offers_v2 and item.offers_v2.listings: ## Throttling -Throttling value represents the wait time in seconds between API calls, being the default value 1 second. Use it to avoid reaching Amazon request limits. +Throttling value represents the wait time in seconds between API calls, being the default +value 1 second. Use it to avoid reaching Amazon request limits. ```python api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, throttling=4) # Makes 1 request every 4 seconds api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, throttling=0) # No wait time between requests ``` +The interval is kept per client, and it is measured with a monotonic clock behind a lock, +so several threads sharing a client still make one request every `throttling` seconds. + ## Timeout -Timeout value represents the number of seconds to wait for a response before failing, being the default value 30 seconds. Use `None` to wait indefinitely. +Timeout value represents the number of seconds to wait for a response before failing, +being the default value 30 seconds. Use `None` to wait indefinitely. ```python api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, timeout=10) # Fails after 10 seconds @@ -168,16 +270,41 @@ It applies to every API request, including the OAuth2 token refresh. ## Retries -Amazon asks clients to back off and try again when it throttles a request or fails to serve it. The client does that on its own, waiting longer before every attempt and honouring the `Retry-After` header when the API sends it. An expired token is refreshed once and the request is sent again. +Amazon asks clients to back off and try again when it throttles a request or fails to +serve it. The client does that on its own, waiting longer before every attempt and +honouring the `Retry-After` header when the API sends it. An expired token is refreshed +once and the request is sent again. ```python -amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, retries=5) # Up to 5 extra attempts -amazon = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, retries=0) # Fail on the first error +api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, retries=5) # Up to 5 extra attempts +api = AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY, retries=0) # Fail on the first error ``` -## Custom Endpoints +The default is 3 extra attempts. Only the failures that Amazon asks to retry are retried: +a rejected request fails right away. -The base URL of the API and the one used to get the OAuth2 token can be replaced, which is useful to run the tests of a project against a mock server. Providing `auth_endpoint` also makes valid a `version` that is not in the list yet, as long as it belongs to a family that the library can authenticate: `2.x` with Cognito and `3.x` with Login with Amazon. A version of any other family is rejected, as a new family brings a new authentication flow and not just another endpoint: +## Closing the client + +The client keeps a pool of connections open, so it is meant to be created once and +reused. Close it, or use it as a context manager, when it is not going to be used again: + +```python +with AmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY) as api: + items = api.get_items(["B01N5IB20Q"]) +``` + +The client stays usable after closing it, opening a new connection on the next request, +so calling `close` is only needed for clients that are not reused. + +## Custom endpoints + +The base URL of the API and the one used to get the OAuth2 token can be replaced, which +is useful to run the tests of a project against a mock server. Providing `auth_endpoint` +also makes valid a `version` that is not in the list yet, so a new one can be used before +the library knows about it, as long as it belongs to a family that the library can +authenticate: `2.x` with Cognito and `3.x` with Login with Amazon. A version of any other +family is rejected, as a new family brings a new authentication flow and not just another +endpoint: ```python api = AmazonCreatorsApi( @@ -191,20 +318,23 @@ api = AmazonCreatorsApi( ) ``` -## Error Handling +## Error handling -Every error raised by the library inherits from `AmazonCreatorsApiError`, so a single `except` covers them all. The message carries the reason given by Amazon, the fields that failed validation and the identifier of the request, which is what Amazon support asks for: +Every error raised by the library inherits from `AmazonCreatorsApiError`, so a single +`except` covers them all. The message carries the reason given by Amazon, the fields that +failed validation and the identifier of the request, which is what Amazon support asks +for: -| Exception | Raised when | -| --- | --- | -| `InvalidArgumentError` | An argument is not valid or the request is rejected by Amazon | -| `AssociateValidationError` | The credentials are not valid for the selected marketplace | -| `AuthenticationError` | The credentials are missing, invalid or expired | -| `AccessDeniedError` | The credentials cannot perform the requested operation | -| `ItemsNotFoundError` | No items are found for the request | -| `ResourceNotFoundError` | The requested feed or report does not exist | -| `TooManyRequestsError` | The rate limit is exceeded and the retries are exhausted | -| `RequestError` | The request fails for any other reason | +| Exception | Raised when | +| -------------------------- | ----------------------------------------------------------------- | +| `InvalidArgumentError` | An argument is not valid or the request is rejected by Amazon | +| `AssociateValidationError` | The credentials are not valid for the selected marketplace | +| `AuthenticationError` | The credentials are missing, invalid or expired | +| `AccessDeniedError` | The credentials cannot perform the requested operation | +| `ItemsNotFoundError` | No items are found for the request | +| `ResourceNotFoundError` | The requested feed or report does not exist | +| `TooManyRequestsError` | The rate limit is exceeded and the retries are exhausted | +| `RequestError` | The request fails for any other reason | ```python from amazon_creatorsapi.errors import AmazonCreatorsApiError, ItemsNotFoundError @@ -217,7 +347,11 @@ except AmazonCreatorsApiError as error: print(error) ``` -## Async Support +An unsupported `version` is the one failure that is not one of these: it raises +`ValueError` when the client is created, as it is a mistake in the code and not an answer +from Amazon. + +## Async support For async/await applications, install with async support: @@ -225,11 +359,12 @@ For async/await applications, install with async support: pip install python-amazon-paapi[async] --upgrade ``` -The async API provides the same methods as the synchronous version: +The async API provides the same methods, parameters and errors as the synchronous +version, and they are called with `await`: ```python -from amazon_creatorsapi.aio import AsyncAmazonCreatorsApi from amazon_creatorsapi import Country +from amazon_creatorsapi.aio import AsyncAmazonCreatorsApi # Use as async context manager (recommended for connection pooling) async with AsyncAmazonCreatorsApi( @@ -239,7 +374,6 @@ async with AsyncAmazonCreatorsApi( tag="your-affiliate-tag", country=Country.US, ) as api: - # All methods work identically, just use await items = await api.get_items(["B01N5IB20Q"]) results = await api.search_items(keywords="laptop") variations = await api.get_variations("B01N5IB20Q") @@ -252,19 +386,23 @@ api = AsyncAmazonCreatorsApi(ID, SECRET, VERSION, TAG, COUNTRY) items = await api.get_items(["B01N5IB20Q"]) ``` -> **Note:** All methods and parameters work identically in async mode. Use `async with` for better performance when making multiple requests. +```{note} +Outside `async with`, every request opens and closes its own connection, so there is +nothing to release and the async client has no `close` method. Use `async with` when +making more than one call, to keep the connection open between them. +``` -## Working with Models +## Working with models All SDK models are re-exported through `amazon_creatorsapi.models` for convenient access: ```python from amazon_creatorsapi.models import ( - Item, Condition, - SortBy, GetItemsResource, + Item, SearchItemsResource, + SortBy, ) # Use Condition enum for filtering @@ -277,10 +415,17 @@ results = api.search_items( ) # Specify which resources to retrieve -from amazon_creatorsapi.models import GetItemsResource resources = [ GetItemsResource.ITEM_INFO_DOT_TITLE, GetItemsResource.OFFERS_V2_DOT_LISTINGS_DOT_PRICE, ] items = api.get_items(["B01N5IB20Q"], resources=resources) ``` + +Every method asks for all the resources of its operation when `resources` is not given. +Narrowing the list makes the response smaller and faster, and the fields left out come +back as `None`. + +The names of the members follow the ones generated for the API schema, where a dot +becomes `_DOT_` and a colon `_COLON_`, as in `GetItemsResource.ITEM_INFO_DOT_TITLE` for +`itemInfo.title`. The {doc}`models reference ` lists them all.