katana_public_api_client.katana_client¶
katana_public_api_client.katana_client
¶
KatanaClient - The pythonic Katana API client with automatic resilience.
This client uses httpx's native transport layer to provide automatic retries, rate limiting, error handling, and pagination for all API calls without any decorators or wrapper methods needed.
Attributes¶
Classes¶
ErrorLoggingTransport(wrapped_transport=None, logger=None, **kwargs)
¶
Bases: AsyncBaseTransport
Transport layer that adds detailed error logging for 4xx client errors.
This transport wraps another transport and intercepts responses to log
detailed error information using the generated error models. Inherits
from AsyncBaseTransport (not AsyncHTTPTransport) so we don't
spin up an unused connection pool inside this layer; all I/O goes
through the wrapped transport.
Parameters:
-
wrapped_transport(AsyncBaseTransport | None, default:None) –The transport to wrap. If None, creates a new AsyncHTTPTransport.
-
logger(Logger | None, default:None) –Logger instance for capturing error details. If None, creates a default logger.
-
**kwargs(Any, default:{}) –Additional arguments passed to AsyncHTTPTransport if wrapped_transport is None.
Source code in katana_public_api_client/katana_client.py
Functions¶
aclose()
async
¶
Propagate close down the wrapped chain so inner transports release resources.
handle_async_request(request)
async
¶
Handle request and log detailed error information for 4xx responses.
Source code in katana_public_api_client/katana_client.py
KatanaClient(api_key=None, base_url=None, timeout=30.0, max_retries=5, max_pages=100, logger=None, *, requests_per_minute=60, **httpx_kwargs)
¶
Bases: AuthenticatedClient
The pythonic Katana API client with automatic resilience and pagination.
This client inherits from AuthenticatedClient and can be passed directly to generated API methods without needing the .client property.
Features: - Automatic retries on network errors and server errors (5xx) - Automatic rate limit handling with Retry-After header support - Auto-pagination ON by default for GET requests (collects all pages automatically) - Uses 250 items per page (Katana's max) for efficient pagination - Rich logging and observability - Minimal configuration - just works out of the box
Auto-pagination behavior:
- ON by default for GET requests with NO page parameter
- Uses 250 items per page when no limit specified by caller
- If caller specifies a limit, that limit is used per page
- ANY explicit page parameter disables auto-pagination (e.g., page=1)
- Disabled per-request via extensions: extensions={"auto_pagination": False}
- Control max pages via max_pages constructor parameter
- Limit total items via extensions: extensions={"max_items": 200}
Usage
async with KatanaClient() as client: from katana_public_api_client.api.product import get_all_products
# Auto-pagination is ON - all pages collected automatically
# Uses 250 items per page for efficiency
response = await get_all_products.asyncio_detailed(
client=client, # Pass client directly - no .client needed!
)
# Use a custom limit per page (100 instead of 250)
response = await get_all_products.asyncio_detailed(
client=client,
limit=100, # Use 100 per page
)
# Get a specific page only (ANY page param disables auto-pagination)
response = await get_all_products.asyncio_detailed(
client=client,
page=2, # Get page 2 only
limit=50
)
# Limit total items collected (via httpx client)
httpx_client = client.get_async_httpx_client()
response = await httpx_client.get(
"/products",
extensions={"max_items": 200} # Stop after 200 items
)
# Control max pages globally
client_limited = KatanaClient(max_pages=5) # Limit to 5 pages max
Parameters:
-
api_key(str | None, default:None) –Katana API key. If None, will try to load from KATANA_API_KEY env var, .env file, or ~/.netrc file (in that order).
-
base_url(str | None, default:None) –Base URL for the Katana API. Defaults to https://api.katanamrp.com/v1
-
timeout(float, default:30.0) –Request timeout in seconds. Defaults to 30.0.
-
max_retries(int, default:5) –Maximum number of retry attempts for failed requests. Defaults to 5.
-
max_pages(int, default:100) –Maximum number of pages to collect during auto-pagination. Defaults to 100.
-
requests_per_minute(int | None, default:60) –Steady-state request budget for the proactive rate limiter. Defaults to 60 (Katana's documented limit). Set to
Noneto disable the rate limiter entirely (e.g. when callers want to manage throttling themselves, or for tests that need raw throughput). When the limiter is active, every actual HTTP request — including retries and per-page paginated fetches — consumes one token, and the transport adapts to the server'sX-Ratelimit-Remaining/X-Ratelimit-Resetheaders. -
logger(Logger | None, default:None) –Any object whose debug/info/warning/error methods accept (msg, *args, **kwargs) — the standard logging.Logger call convention (e.g. logging.Logger, structlog.BoundLogger). If None, creates a default stdlib logger.
-
**httpx_kwargs(Any, default:{}) –Additional arguments passed to the base AsyncHTTPTransport. Common parameters include: - http2 (bool): Enable HTTP/2 support - limits (httpx.Limits): Connection pool limits - verify (bool | str | ssl.SSLContext): SSL certificate verification - cert (str | tuple): Client-side certificates - trust_env (bool): Trust environment variables for proxy configuration - event_hooks (dict): Custom event hooks (will be merged with built-in hooks)
Raises:
-
ValueError–If no API key is provided via api_key param, KATANA_API_KEY env var, .env file, or ~/.netrc file.
Note
Transport-related parameters (http2, limits, verify, etc.) are correctly passed to the innermost AsyncHTTPTransport layer, ensuring they take effect even with the layered transport architecture.
Example
async with KatanaClient() as client: ... # All API calls through client get automatic resilience ... response = await some_api_method.asyncio_detailed(client=client)
Source code in katana_public_api_client/katana_client.py
1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 | |
Attributes¶
api
property
¶
Thin CRUD wrappers for all API resources. Returns raw attrs models.
Example
async with KatanaClient() as client: ... products = await client.api.products.list(is_sellable=True) ... product = await client.api.products.get(123) ... await client.api.products.delete(123)
materials
property
¶
Access material catalog operations.
Returns:
-
Materials–Materials instance for material CRUD operations.
Example
async with KatanaClient() as client: ... materials = await client.materials.list() ... material = await client.materials.get(123)
products
property
¶
Access product catalog operations.
Returns:
-
Products–Products instance for product CRUD and search operations.
Example
async with KatanaClient() as client: ... # Product CRUD ... products = await client.products.list(is_sellable=True) ... product = await client.products.get(123) ... results = await client.products.search("widget")
services
property
¶
Access service catalog operations.
Returns:
-
Services–Services instance for service CRUD operations.
Example
async with KatanaClient() as client: ... services = await client.services.list() ... service = await client.services.get(123)
variants
property
¶
Access variant catalog operations.
Returns:
-
Variants–Variants instance for variant CRUD operations.
Example
async with KatanaClient() as client: ... variants = await client.variants.list() ... variant = await client.variants.get(123)
Functions¶
PaginationTransport(wrapped_transport=None, max_pages=100, logger=None, **kwargs)
¶
Bases: AsyncBaseTransport
Transport layer that adds automatic pagination for GET requests.
This transport wraps another transport and automatically collects all pages
for GET requests by default. Inherits from AsyncBaseTransport (not
AsyncHTTPTransport) so we don't spin up an unused connection pool
inside this layer; all I/O goes through the wrapped transport.
Auto-pagination behavior:
- ON by default for GET requests with NO page parameter in URL
- Uses 250 items per page (Katana's max) when no limit specified by caller
- If caller specifies a limit, that limit is used (caller's choice)
- ANY explicit page parameter in URL disables auto-pagination (e.g., ?page=1)
- Disabled when request has extensions={"auto_pagination": False}
- Only applies to GET requests (POST, PUT, etc. are never paginated)
Controlling pagination limits:
- max_pages (constructor): Maximum number of pages to fetch
- max_items (extension): Maximum total items to collect, e.g.,
extensions={"max_items": 200} stops after 200 items
Parameters:
-
wrapped_transport(AsyncBaseTransport | None, default:None) –The transport to wrap. If None, creates a new AsyncHTTPTransport.
-
max_pages(int, default:100) –Maximum number of pages to collect during auto-pagination. Defaults to 100.
-
logger(Logger | None, default:None) –Logger instance for capturing pagination operations. If None, creates a default logger.
-
**kwargs(Any, default:{}) –Additional arguments passed to AsyncHTTPTransport if wrapped_transport is None.
Source code in katana_public_api_client/katana_client.py
Functions¶
aclose()
async
¶
Propagate close down the wrapped chain so inner transports release resources.
handle_async_request(request)
async
¶
Handle request with automatic pagination for GET requests.
Auto-pagination is ON by default for GET requests. It is disabled when:
- extensions={"auto_pagination": False} is set, OR
- ANY explicit page parameter is in the URL (e.g., ?page=1 or ?page=2)
To get auto-pagination, simply don't pass a page parameter. The transport will automatically use 250 items per page (Katana's max) unless you specify a limit, in which case your limit will be respected.
Source code in katana_public_api_client/katana_client.py
RateLimitAwareRetry(*args, **kwargs)
¶
Bases: Retry
Custom Retry class that allows non-idempotent methods (POST, PATCH) to be retried ONLY when receiving a 429 (Too Many Requests) status code.
For all other retryable status codes (502, 503, 504), only idempotent methods (HEAD, GET, PUT, DELETE, OPTIONS, TRACE) will be retried.
This ensures we don't accidentally retry non-idempotent operations after server errors, but we DO retry them when we're being rate-limited.
Source code in katana_public_api_client/katana_client.py
Functions¶
increment()
¶
Return a new retry instance with the attempt count incremented.
Source code in katana_public_api_client/katana_client.py
is_retryable_method(method)
¶
Allow all methods to pass through the initial check.
Store the method for later use in is_retryable_status_code.
Source code in katana_public_api_client/katana_client.py
is_retryable_status_code(status_code)
¶
Check if a status code is retryable for the current method.
For 429 (rate limiting), allow all methods. For other errors (502, 503, 504), only allow idempotent methods.
Source code in katana_public_api_client/katana_client.py
RateLimitTransport(wrapped_transport=None, *, requests_per_minute=60, logger=None, **kwargs)
¶
Bases: AsyncBaseTransport
Proactive rate-limiter that respects Katana's X-Ratelimit-* headers.
Wraps another transport and gates each request through a pyrate-limiter
token bucket sized for Katana's documented rate limit (60 req/min by
default, X-Ratelimit-Limit per the spec). After every response the
transport reads X-Ratelimit-Remaining / X-Ratelimit-Reset and
adapts:
- Sync down: when the server reports fewer remaining tokens than our local estimate (e.g., another client is sharing the API key), drain the local bucket to match. We never sync up — the server is authoritative on the lower bound only.
- Reset gate: when remaining hits 0, an
asyncio.Eventblocks all future requests untilX-Ratelimit-Resetelapses. This prevents pyrate's bucket from racing ahead of Katana's window.
Stack placement is innermost (above the base AsyncHTTPTransport):
every actual HTTP request — including retries from RetryTransport
above and per-page paginated fetches from PaginationTransport —
consumes one token, matching how Katana counts requests server-side.
Retry-After waiting on 429 responses stays in RetryTransport
(urllib3.Retry honors the header via respect_retry_after_header=True).
Sleeping in this transport on 429 would double-delay; we only update the
reset gate from headers and let retry handle the actual wait.
Out-of-order responses are handled correctly: the sync-down logic only
fires when the response's remaining is below the current estimate,
so a delayed earlier response with a higher remaining value won't
overwrite a fresher (lower) estimate.
Inherits from AsyncBaseTransport (not AsyncHTTPTransport) because
we delegate every request to _wrapped_transport — there's no need to
spin up an unused connection pool inside this layer.
Parameters:
-
wrapped_transport(AsyncBaseTransport | None, default:None) –The transport to wrap. If None, creates a new AsyncHTTPTransport.
-
requests_per_minute(int, default:60) –Steady-state request budget. Must be
> 0; callers wanting to disable the limiter entirely should omit this transport from the chain rather than passing 0. -
logger(Logger | None, default:None) –Logger instance for capturing state changes. If None, creates a default logger.
-
**kwargs(Any, default:{}) –Additional arguments passed to AsyncHTTPTransport if wrapped_transport is None.
Source code in katana_public_api_client/katana_client.py
Functions¶
aclose()
async
¶
Cancel the pending reset timer and any in-flight release, then close the wrapped transport.
Two cancellation paths must run before delegating aclose down
the chain:
_reset_handle— theloop.call_latercallback. If it fires after the loop is cleaned up, we get scheduling errors._release_task— the async_release_reset_gatetask spawned when the timer fired. If the timer fired just before shutdown, the task may still be running (waiting onself._lock); without explicit cancel + await, asyncio emits "Task was destroyed but it is pending" and the task can race with the wrapped transport's own shutdown.
Source code in katana_public_api_client/katana_client.py
handle_async_request(request)
async
¶
Acquire a token, forward the request, and observe rate-limit headers.
Source code in katana_public_api_client/katana_client.py
Functions¶
ResilientAsyncTransport(max_retries=5, max_pages=100, logger=None, *, requests_per_minute=60, **kwargs)
¶
Factory function that creates a chained transport with error logging, pagination, rate limiting, and retry capabilities.
This function chains multiple transport layers (innermost → outermost): 1. AsyncHTTPTransport (base HTTP transport) 2. RateLimitTransport (proactive 60-req/min throttle, header-aware) 3. ErrorLoggingTransport (logs detailed 4xx errors) 4. PaginationTransport (auto-collects paginated responses) 5. RetryTransport (handles retries with Retry-After header support)
The rate limiter is innermost (above the base) because Katana counts
every HTTP request — retries from the outer RetryTransport and
individual paginated pages from PaginationTransport all consume
server-side budget. Placing the limiter higher up would under-count.
Parameters:
-
max_retries(int, default:5) –Maximum number of retry attempts for failed requests. Defaults to 5.
-
max_pages(int, default:100) –Maximum number of pages to collect during auto-pagination. Defaults to 100.
-
requests_per_minute(int | None, default:60) –Steady-state request budget for the rate-limit transport. Defaults to 60 (Katana's documented default). Pass
Noneto omit the rate-limit layer entirely (e.g. when the caller is responsible for throttling, or for tests that need raw throughput). -
logger(Logger | None, default:None) –Logger instance for capturing operations. If None, creates a default logger.
-
**kwargs(Any, default:{}) –Additional arguments passed to the base AsyncHTTPTransport. Common parameters include: - http2 (bool): Enable HTTP/2 support - limits (httpx.Limits): Connection pool limits - verify (bool | str | ssl.SSLContext): SSL certificate verification - cert (str | tuple): Client-side certificates - trust_env (bool): Trust environment variables for proxy configuration
Returns:
-
RetryTransport–A RetryTransport instance wrapping all the layered transports.
Note
When using a custom transport, parameters like http2, limits, and verify must be passed to this factory function (which passes them to the base AsyncHTTPTransport), not to the httpx.Client/AsyncClient constructor.
Example
Source code in katana_public_api_client/katana_client.py
1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 | |