diff --git a/src/apify_client/_apify_client.py b/src/apify_client/_apify_client.py index 93f68f1a..17651f15 100644 --- a/src/apify_client/_apify_client.py +++ b/src/apify_client/_apify_client.py @@ -12,6 +12,7 @@ DEFAULT_MIN_DELAY_BETWEEN_RETRIES, DEFAULT_TIMEOUT, ) +from apify_client._docs import docs_group from apify_client._http_clients import HttpClient, HttpClientAsync from apify_client._resource_clients import ( ActorClient, @@ -52,12 +53,8 @@ ScheduleClientAsync, ScheduleCollectionClient, ScheduleCollectionClientAsync, - StatusMessageWatcherAsync, - StatusMessageWatcherSync, StoreCollectionClient, StoreCollectionClientAsync, - StreamedLogAsync, - StreamedLogSync, TaskClient, TaskClientAsync, TaskCollectionClient, @@ -79,8 +76,35 @@ from datetime import timedelta +@docs_group('Apify API clients') class ApifyClient: - """The Apify API client.""" + """Synchronous client for the Apify API. + + This is the main entry point for interacting with the Apify platform. It provides methods to access + resource-specific sub-clients for managing Actors, runs, datasets, key-value stores, request queues, + schedules, webhooks, and more. + + The client automatically handles retries with exponential backoff for failed or rate-limited requests. + + ### Usage + + ```python + from apify_client import ApifyClient + + client = ApifyClient(token='MY-APIFY-TOKEN') + + # Start an Actor and wait for it to finish. + actor_client = client.actor('username/my-actor') + run = actor_client.call(run_input={'query': 'web scraping'}) + + # Fetch results from the run's default dataset. + if run is not None: + dataset_client = client.dataset(run.default_dataset_id) + items = dataset_client.list_items().items + for item in items: + print(item) + ``` + """ _OVERRIDABLE_DEFAULT_HEADERS: ClassVar[set[str]] = {'Accept', 'Authorization', 'Accept-Encoding', 'User-Agent'} @@ -95,18 +119,19 @@ def __init__( timeout: timedelta = DEFAULT_TIMEOUT, headers: dict[str, str] | None = None, ) -> None: - """Initialize a new instance. + """Initialize the Apify API client. Args: - token: The Apify API token. - api_url: The URL of the Apify API server to which to connect. Defaults to https://api.apify.com. It can - be an internal URL that is not globally accessible, in such case `api_public_url` should be set as well. - api_public_url: The globally accessible URL of the Apify API server. It should be set only if the `api_url` + token: The Apify API token. You can find your token on the + [Integrations](https://console.apify.com/account/integrations) page in the Apify Console. + api_url: The URL of the Apify API server to connect to. Defaults to https://api.apify.com. It can + be an internal URL that is not globally accessible, in which case `api_public_url` should be set + as well. + api_public_url: The globally accessible URL of the Apify API server. Should be set only if `api_url` is an internal URL that is not globally accessible. Defaults to https://api.apify.com. - max_retries: How many times to retry a failed request at most. - min_delay_between_retries: How long will the client wait between retrying requests - (increases exponentially from this value). - timeout: The socket timeout of the HTTP requests sent to the Apify API. + max_retries: Maximum number of retry attempts for failed requests. + min_delay_between_retries: Minimum delay between retries (increases exponentially with each attempt). + timeout: Timeout for HTTP requests sent to the Apify API. headers: Additional HTTP headers to include in all API requests. """ # We need to do this because of mocking in tests and default mutable arguments. @@ -152,8 +177,6 @@ def __init__( key_value_store_client=KeyValueStoreClient, key_value_store_collection_client=KeyValueStoreCollectionClient, log_client=LogClient, - status_message_watcher=StatusMessageWatcherSync, - streamed_log=StreamedLogSync, request_queue_client=RequestQueueClient, request_queue_collection_client=RequestQueueCollectionClient, run_client=RunClient, @@ -199,7 +222,7 @@ def token(self) -> str | None: return self._token def actor(self, actor_id: str) -> ActorClient: - """Retrieve the sub-client for manipulating a single Actor. + """Get the sub-client for a specific Actor. Args: actor_id: ID of the Actor to be manipulated. @@ -207,11 +230,11 @@ def actor(self, actor_id: str) -> ActorClient: return ActorClient(resource_id=actor_id, **self._base_kwargs) def actors(self) -> ActorCollectionClient: - """Retrieve the sub-client for manipulating Actors.""" + """Get the sub-client for the Actor collection, allowing to list and create Actors.""" return ActorCollectionClient(**self._base_kwargs) def build(self, build_id: str) -> BuildClient: - """Retrieve the sub-client for manipulating a single Actor build. + """Get the sub-client for a specific Actor build. Args: build_id: ID of the Actor build to be manipulated. @@ -219,11 +242,11 @@ def build(self, build_id: str) -> BuildClient: return BuildClient(resource_id=build_id, **self._base_kwargs) def builds(self) -> BuildCollectionClient: - """Retrieve the sub-client for querying multiple builds of a user.""" + """Get the sub-client for the build collection, allowing to list builds.""" return BuildCollectionClient(**self._base_kwargs) def run(self, run_id: str) -> RunClient: - """Retrieve the sub-client for manipulating a single Actor run. + """Get the sub-client for a specific Actor run. Args: run_id: ID of the Actor run to be manipulated. @@ -231,11 +254,11 @@ def run(self, run_id: str) -> RunClient: return RunClient(resource_id=run_id, **self._base_kwargs) def runs(self) -> RunCollectionClient: - """Retrieve the sub-client for querying multiple Actor runs of a user.""" + """Get the sub-client for the run collection, allowing to list Actor runs.""" return RunCollectionClient(**self._base_kwargs) def dataset(self, dataset_id: str) -> DatasetClient: - """Retrieve the sub-client for manipulating a single dataset. + """Get the sub-client for a specific dataset. Args: dataset_id: ID of the dataset to be manipulated. @@ -243,11 +266,11 @@ def dataset(self, dataset_id: str) -> DatasetClient: return DatasetClient(resource_id=dataset_id, **self._base_kwargs) def datasets(self) -> DatasetCollectionClient: - """Retrieve the sub-client for manipulating datasets.""" + """Get the sub-client for the dataset collection, allowing to list and create datasets.""" return DatasetCollectionClient(**self._base_kwargs) def key_value_store(self, key_value_store_id: str) -> KeyValueStoreClient: - """Retrieve the sub-client for manipulating a single key-value store. + """Get the sub-client for a specific key-value store. Args: key_value_store_id: ID of the key-value store to be manipulated. @@ -255,11 +278,11 @@ def key_value_store(self, key_value_store_id: str) -> KeyValueStoreClient: return KeyValueStoreClient(resource_id=key_value_store_id, **self._base_kwargs) def key_value_stores(self) -> KeyValueStoreCollectionClient: - """Retrieve the sub-client for manipulating key-value stores.""" + """Get the sub-client for the key-value store collection, allowing to list and create key-value stores.""" return KeyValueStoreCollectionClient(**self._base_kwargs) def request_queue(self, request_queue_id: str, *, client_key: str | None = None) -> RequestQueueClient: - """Retrieve the sub-client for manipulating a single request queue. + """Get the sub-client for a specific request queue. Args: request_queue_id: ID of the request queue to be manipulated. @@ -268,11 +291,11 @@ def request_queue(self, request_queue_id: str, *, client_key: str | None = None) return RequestQueueClient(resource_id=request_queue_id, client_key=client_key, **self._base_kwargs) def request_queues(self) -> RequestQueueCollectionClient: - """Retrieve the sub-client for manipulating request queues.""" + """Get the sub-client for the request queue collection, allowing to list and create request queues.""" return RequestQueueCollectionClient(**self._base_kwargs) def webhook(self, webhook_id: str) -> WebhookClient: - """Retrieve the sub-client for manipulating a single webhook. + """Get the sub-client for a specific webhook. Args: webhook_id: ID of the webhook to be manipulated. @@ -280,11 +303,11 @@ def webhook(self, webhook_id: str) -> WebhookClient: return WebhookClient(resource_id=webhook_id, **self._base_kwargs) def webhooks(self) -> WebhookCollectionClient: - """Retrieve the sub-client for querying multiple webhooks of a user.""" + """Get the sub-client for the webhook collection, allowing to list and create webhooks.""" return WebhookCollectionClient(**self._base_kwargs) def webhook_dispatch(self, webhook_dispatch_id: str) -> WebhookDispatchClient: - """Retrieve the sub-client for accessing a single webhook dispatch. + """Get the sub-client for a specific webhook dispatch. Args: webhook_dispatch_id: ID of the webhook dispatch to access. @@ -292,11 +315,11 @@ def webhook_dispatch(self, webhook_dispatch_id: str) -> WebhookDispatchClient: return WebhookDispatchClient(resource_id=webhook_dispatch_id, **self._base_kwargs) def webhook_dispatches(self) -> WebhookDispatchCollectionClient: - """Retrieve the sub-client for querying multiple webhook dispatches of a user.""" + """Get the sub-client for the webhook dispatch collection, allowing to list webhook dispatches.""" return WebhookDispatchCollectionClient(**self._base_kwargs) def schedule(self, schedule_id: str) -> ScheduleClient: - """Retrieve the sub-client for manipulating a single schedule. + """Get the sub-client for a specific schedule. Args: schedule_id: ID of the schedule to be manipulated. @@ -304,11 +327,11 @@ def schedule(self, schedule_id: str) -> ScheduleClient: return ScheduleClient(resource_id=schedule_id, **self._base_kwargs) def schedules(self) -> ScheduleCollectionClient: - """Retrieve the sub-client for manipulating schedules.""" + """Get the sub-client for the schedule collection, allowing to list and create schedules.""" return ScheduleCollectionClient(**self._base_kwargs) def log(self, build_or_run_id: str) -> LogClient: - """Retrieve the sub-client for retrieving logs. + """Get the sub-client for retrieving logs of an Actor build or run. Args: build_or_run_id: ID of the Actor build or run for which to access the log. @@ -316,7 +339,7 @@ def log(self, build_or_run_id: str) -> LogClient: return LogClient(resource_id=build_or_run_id, **self._base_kwargs) def task(self, task_id: str) -> TaskClient: - """Retrieve the sub-client for manipulating a single task. + """Get the sub-client for a specific Actor task. Args: task_id: ID of the task to be manipulated. @@ -324,11 +347,11 @@ def task(self, task_id: str) -> TaskClient: return TaskClient(resource_id=task_id, **self._base_kwargs) def tasks(self) -> TaskCollectionClient: - """Retrieve the sub-client for manipulating tasks.""" + """Get the sub-client for the task collection, allowing to list and create Actor tasks.""" return TaskCollectionClient(**self._base_kwargs) def user(self, user_id: str | None = None) -> UserClient: - """Retrieve the sub-client for querying users. + """Get the sub-client for querying user data. Args: user_id: ID of user to be queried. If None, queries the user belonging to the token supplied to the client. @@ -336,12 +359,46 @@ def user(self, user_id: str | None = None) -> UserClient: return UserClient(resource_id=user_id, **self._base_kwargs) def store(self) -> StoreCollectionClient: - """Retrieve the sub-client for Apify store.""" + """Get the sub-client for the Apify Store, allowing to list Actors published in the store.""" return StoreCollectionClient(**self._base_kwargs) +@docs_group('Apify API clients') class ApifyClientAsync: - """The asynchronous version of the Apify API client.""" + """Asynchronous client for the Apify API. + + This is the main entry point for interacting with the Apify platform using async/await. It provides + methods to access resource-specific sub-clients for managing Actors, runs, datasets, key-value stores, + request queues, schedules, webhooks, and more. + + The client automatically handles retries with exponential backoff for failed or rate-limited requests. + + ### Usage + + ```python + import asyncio + + from apify_client import ApifyClientAsync + + + async def main() -> None: + client = ApifyClientAsync(token='MY-APIFY-TOKEN') + + # Start an Actor and wait for it to finish. + actor_client = client.actor('username/my-actor') + run = await actor_client.call(run_input={'query': 'web scraping'}) + + # Fetch results from the run's default dataset. + if run is not None: + dataset_client = client.dataset(run.default_dataset_id) + items = (await dataset_client.list_items()).items + for item in items: + print(item) + + + asyncio.run(main()) + ``` + """ _OVERRIDABLE_DEFAULT_HEADERS: ClassVar[set[str]] = {'Accept', 'Authorization', 'Accept-Encoding', 'User-Agent'} @@ -356,18 +413,19 @@ def __init__( timeout: timedelta = DEFAULT_TIMEOUT, headers: dict[str, str] | None = None, ) -> None: - """Initialize a new instance. + """Initialize the Apify API client. Args: - token: The Apify API token. - api_url: The URL of the Apify API server to which to connect. Defaults to https://api.apify.com. It can - be an internal URL that is not globally accessible, in such case `api_public_url` should be set as well. - api_public_url: The globally accessible URL of the Apify API server. It should be set only if the `api_url` + token: The Apify API token. You can find your token on the + [Integrations](https://console.apify.com/account/integrations) page in the Apify Console. + api_url: The URL of the Apify API server to connect to. Defaults to https://api.apify.com. It can + be an internal URL that is not globally accessible, in which case `api_public_url` should be set + as well. + api_public_url: The globally accessible URL of the Apify API server. Should be set only if `api_url` is an internal URL that is not globally accessible. Defaults to https://api.apify.com. - max_retries: How many times to retry a failed request at most. - min_delay_between_retries: How long will the client wait between retrying requests - (increases exponentially from this value). - timeout: The socket timeout of the HTTP requests sent to the Apify API. + max_retries: Maximum number of retry attempts for failed requests. + min_delay_between_retries: Minimum delay between retries (increases exponentially with each attempt). + timeout: Timeout for HTTP requests sent to the Apify API. headers: Additional HTTP headers to include in all API requests. """ # We need to do this because of mocking in tests and default mutable arguments. @@ -413,8 +471,6 @@ def __init__( key_value_store_client=KeyValueStoreClientAsync, key_value_store_collection_client=KeyValueStoreCollectionClientAsync, log_client=LogClientAsync, - status_message_watcher=StatusMessageWatcherAsync, - streamed_log=StreamedLogAsync, request_queue_client=RequestQueueClientAsync, request_queue_collection_client=RequestQueueCollectionClientAsync, run_client=RunClientAsync, @@ -460,7 +516,7 @@ def token(self) -> str | None: return self._token def actor(self, actor_id: str) -> ActorClientAsync: - """Retrieve the sub-client for manipulating a single Actor. + """Get the sub-client for a specific Actor. Args: actor_id: ID of the Actor to be manipulated. @@ -468,11 +524,11 @@ def actor(self, actor_id: str) -> ActorClientAsync: return ActorClientAsync(resource_id=actor_id, **self._base_kwargs) def actors(self) -> ActorCollectionClientAsync: - """Retrieve the sub-client for manipulating Actors.""" + """Get the sub-client for the Actor collection, allowing to list and create Actors.""" return ActorCollectionClientAsync(**self._base_kwargs) def build(self, build_id: str) -> BuildClientAsync: - """Retrieve the sub-client for manipulating a single Actor build. + """Get the sub-client for a specific Actor build. Args: build_id: ID of the Actor build to be manipulated. @@ -480,11 +536,11 @@ def build(self, build_id: str) -> BuildClientAsync: return BuildClientAsync(resource_id=build_id, **self._base_kwargs) def builds(self) -> BuildCollectionClientAsync: - """Retrieve the sub-client for querying multiple builds of a user.""" + """Get the sub-client for the build collection, allowing to list builds.""" return BuildCollectionClientAsync(**self._base_kwargs) def run(self, run_id: str) -> RunClientAsync: - """Retrieve the sub-client for manipulating a single Actor run. + """Get the sub-client for a specific Actor run. Args: run_id: ID of the Actor run to be manipulated. @@ -492,11 +548,11 @@ def run(self, run_id: str) -> RunClientAsync: return RunClientAsync(resource_id=run_id, **self._base_kwargs) def runs(self) -> RunCollectionClientAsync: - """Retrieve the sub-client for querying multiple Actor runs of a user.""" + """Get the sub-client for the run collection, allowing to list Actor runs.""" return RunCollectionClientAsync(**self._base_kwargs) def dataset(self, dataset_id: str) -> DatasetClientAsync: - """Retrieve the sub-client for manipulating a single dataset. + """Get the sub-client for a specific dataset. Args: dataset_id: ID of the dataset to be manipulated. @@ -504,11 +560,11 @@ def dataset(self, dataset_id: str) -> DatasetClientAsync: return DatasetClientAsync(resource_id=dataset_id, **self._base_kwargs) def datasets(self) -> DatasetCollectionClientAsync: - """Retrieve the sub-client for manipulating datasets.""" + """Get the sub-client for the dataset collection, allowing to list and create datasets.""" return DatasetCollectionClientAsync(**self._base_kwargs) def key_value_store(self, key_value_store_id: str) -> KeyValueStoreClientAsync: - """Retrieve the sub-client for manipulating a single key-value store. + """Get the sub-client for a specific key-value store. Args: key_value_store_id: ID of the key-value store to be manipulated. @@ -516,11 +572,11 @@ def key_value_store(self, key_value_store_id: str) -> KeyValueStoreClientAsync: return KeyValueStoreClientAsync(resource_id=key_value_store_id, **self._base_kwargs) def key_value_stores(self) -> KeyValueStoreCollectionClientAsync: - """Retrieve the sub-client for manipulating key-value stores.""" + """Get the sub-client for the key-value store collection, allowing to list and create key-value stores.""" return KeyValueStoreCollectionClientAsync(**self._base_kwargs) def request_queue(self, request_queue_id: str, *, client_key: str | None = None) -> RequestQueueClientAsync: - """Retrieve the sub-client for manipulating a single request queue. + """Get the sub-client for a specific request queue. Args: request_queue_id: ID of the request queue to be manipulated. @@ -529,11 +585,11 @@ def request_queue(self, request_queue_id: str, *, client_key: str | None = None) return RequestQueueClientAsync(resource_id=request_queue_id, client_key=client_key, **self._base_kwargs) def request_queues(self) -> RequestQueueCollectionClientAsync: - """Retrieve the sub-client for manipulating request queues.""" + """Get the sub-client for the request queue collection, allowing to list and create request queues.""" return RequestQueueCollectionClientAsync(**self._base_kwargs) def webhook(self, webhook_id: str) -> WebhookClientAsync: - """Retrieve the sub-client for manipulating a single webhook. + """Get the sub-client for a specific webhook. Args: webhook_id: ID of the webhook to be manipulated. @@ -541,11 +597,11 @@ def webhook(self, webhook_id: str) -> WebhookClientAsync: return WebhookClientAsync(resource_id=webhook_id, **self._base_kwargs) def webhooks(self) -> WebhookCollectionClientAsync: - """Retrieve the sub-client for querying multiple webhooks of a user.""" + """Get the sub-client for the webhook collection, allowing to list and create webhooks.""" return WebhookCollectionClientAsync(**self._base_kwargs) def webhook_dispatch(self, webhook_dispatch_id: str) -> WebhookDispatchClientAsync: - """Retrieve the sub-client for accessing a single webhook dispatch. + """Get the sub-client for a specific webhook dispatch. Args: webhook_dispatch_id: ID of the webhook dispatch to access. @@ -553,11 +609,11 @@ def webhook_dispatch(self, webhook_dispatch_id: str) -> WebhookDispatchClientAsy return WebhookDispatchClientAsync(resource_id=webhook_dispatch_id, **self._base_kwargs) def webhook_dispatches(self) -> WebhookDispatchCollectionClientAsync: - """Retrieve the sub-client for querying multiple webhook dispatches of a user.""" + """Get the sub-client for the webhook dispatch collection, allowing to list webhook dispatches.""" return WebhookDispatchCollectionClientAsync(**self._base_kwargs) def schedule(self, schedule_id: str) -> ScheduleClientAsync: - """Retrieve the sub-client for manipulating a single schedule. + """Get the sub-client for a specific schedule. Args: schedule_id: ID of the schedule to be manipulated. @@ -565,11 +621,11 @@ def schedule(self, schedule_id: str) -> ScheduleClientAsync: return ScheduleClientAsync(resource_id=schedule_id, **self._base_kwargs) def schedules(self) -> ScheduleCollectionClientAsync: - """Retrieve the sub-client for manipulating schedules.""" + """Get the sub-client for the schedule collection, allowing to list and create schedules.""" return ScheduleCollectionClientAsync(**self._base_kwargs) def log(self, build_or_run_id: str) -> LogClientAsync: - """Retrieve the sub-client for retrieving logs. + """Get the sub-client for retrieving logs of an Actor build or run. Args: build_or_run_id: ID of the Actor build or run for which to access the log. @@ -577,7 +633,7 @@ def log(self, build_or_run_id: str) -> LogClientAsync: return LogClientAsync(resource_id=build_or_run_id, **self._base_kwargs) def task(self, task_id: str) -> TaskClientAsync: - """Retrieve the sub-client for manipulating a single task. + """Get the sub-client for a specific Actor task. Args: task_id: ID of the task to be manipulated. @@ -585,11 +641,11 @@ def task(self, task_id: str) -> TaskClientAsync: return TaskClientAsync(resource_id=task_id, **self._base_kwargs) def tasks(self) -> TaskCollectionClientAsync: - """Retrieve the sub-client for manipulating tasks.""" + """Get the sub-client for the task collection, allowing to list and create Actor tasks.""" return TaskCollectionClientAsync(**self._base_kwargs) def user(self, user_id: str | None = None) -> UserClientAsync: - """Retrieve the sub-client for querying users. + """Get the sub-client for querying user data. Args: user_id: ID of user to be queried. If None, queries the user belonging to the token supplied to the client. @@ -597,5 +653,5 @@ def user(self, user_id: str | None = None) -> UserClientAsync: return UserClientAsync(resource_id=user_id, **self._base_kwargs) def store(self) -> StoreCollectionClientAsync: - """Retrieve the sub-client for Apify store.""" + """Get the sub-client for the Apify Store, allowing to list Actors published in the store.""" return StoreCollectionClientAsync(**self._base_kwargs) diff --git a/src/apify_client/_client_registry.py b/src/apify_client/_client_registry.py index 3564a458..5f3c7994 100644 --- a/src/apify_client/_client_registry.py +++ b/src/apify_client/_client_registry.py @@ -43,12 +43,8 @@ ScheduleClientAsync, ScheduleCollectionClient, ScheduleCollectionClientAsync, - StatusMessageWatcherAsync, - StatusMessageWatcherSync, StoreCollectionClient, StoreCollectionClientAsync, - StreamedLogAsync, - StreamedLogSync, TaskClient, TaskClientAsync, TaskCollectionClient, @@ -87,8 +83,6 @@ class ClientRegistry: key_value_store_client: type[KeyValueStoreClient] key_value_store_collection_client: type[KeyValueStoreCollectionClient] log_client: type[LogClient] - status_message_watcher: type[StatusMessageWatcherSync] - streamed_log: type[StreamedLogSync] request_queue_client: type[RequestQueueClient] request_queue_collection_client: type[RequestQueueCollectionClient] run_client: type[RunClient] @@ -126,8 +120,6 @@ class ClientRegistryAsync: key_value_store_client: type[KeyValueStoreClientAsync] key_value_store_collection_client: type[KeyValueStoreCollectionClientAsync] log_client: type[LogClientAsync] - status_message_watcher: type[StatusMessageWatcherAsync] - streamed_log: type[StreamedLogAsync] request_queue_client: type[RequestQueueClientAsync] request_queue_collection_client: type[RequestQueueCollectionClientAsync] run_client: type[RunClientAsync] diff --git a/src/apify_client/_docs.py b/src/apify_client/_docs.py new file mode 100644 index 00000000..fa5983fa --- /dev/null +++ b/src/apify_client/_docs.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from collections.abc import Callable +from typing import Any, Literal, TypeVar + +# The order of the rendered API groups is defined by GROUP_ORDER in website/transformDocs.js +# and applied via groupSort in website/docusaurus.config.js. +GroupName = Literal[ + 'Apify API clients', + 'HTTP clients', + 'Resource clients', + 'Errors', + 'Models', + 'Other', +] + +T = TypeVar('T', bound=Callable[..., Any]) + + +def docs_group(group_name: GroupName) -> Callable[[T], T]: # noqa: ARG001 + """Mark a symbol for rendering and grouping in documentation. + + This decorator is used solely for documentation purposes and does not modify the behavior + of the decorated callable. + + Args: + group_name: The documentation group to which the symbol belongs. + + Returns: + The original callable without modification. + """ + + def wrapper(func: T) -> T: + return func + + return wrapper diff --git a/src/apify_client/_http_clients/_http_client.py b/src/apify_client/_http_clients/_http_client.py index 291fac3f..7fc10cc2 100644 --- a/src/apify_client/_http_clients/_http_client.py +++ b/src/apify_client/_http_clients/_http_client.py @@ -11,6 +11,7 @@ import impit from apify_client._consts import DEFAULT_MAX_RETRIES, DEFAULT_MIN_DELAY_BETWEEN_RETRIES, DEFAULT_TIMEOUT +from apify_client._docs import docs_group from apify_client._http_clients._base import BaseHttpClient from apify_client._logging import log_context, logger_name from apify_client._utils import to_seconds @@ -27,8 +28,14 @@ logger = logging.getLogger(logger_name) +@docs_group('HTTP clients') class HttpClient(BaseHttpClient): - """Synchronous HTTP client for Apify API with automatic retries and exponential backoff.""" + """Synchronous HTTP client for the Apify API. + + Handles authentication, request serialization, and automatic retries with exponential backoff + for rate-limited (HTTP 429) and server error (HTTP 5xx) responses. Non-retryable errors + (e.g. HTTP 4xx client errors) are raised immediately. + """ def __init__( self, @@ -44,9 +51,9 @@ def __init__( Args: token: Apify API token for authentication. - timeout: Request timeout. - max_retries: Maximum number of retries for failed requests. - min_delay_between_retries: Minimum delay between retries. + timeout: Default timeout for HTTP requests. + max_retries: Maximum number of retry attempts for failed requests. + min_delay_between_retries: Minimum delay between retries (increases exponentially with each attempt). statistics: Statistics tracker for API calls. Created automatically if not provided. headers: Additional HTTP headers to include in all requests. """ @@ -247,8 +254,14 @@ def stop_retrying() -> None: return func(stop_retrying, max_retries + 1) +@docs_group('HTTP clients') class HttpClientAsync(BaseHttpClient): - """Asynchronous HTTP client for Apify API with automatic retries and exponential backoff.""" + """Asynchronous HTTP client for the Apify API. + + Handles authentication, request serialization, and automatic retries with exponential backoff + for rate-limited (HTTP 429) and server error (HTTP 5xx) responses. Non-retryable errors + (e.g. HTTP 4xx client errors) are raised immediately. + """ def __init__( self, @@ -264,9 +277,9 @@ def __init__( Args: token: Apify API token for authentication. - timeout: Request timeout. - max_retries: Maximum number of retries for failed requests. - min_delay_between_retries: Minimum delay between retries. + timeout: Default timeout for HTTP requests. + max_retries: Maximum number of retry attempts for failed requests. + min_delay_between_retries: Minimum delay between retries (increases exponentially with each attempt). statistics: Statistics tracker for API calls. Created automatically if not provided. headers: Additional HTTP headers to include in all requests. """ diff --git a/src/apify_client/_resource_clients/__init__.py b/src/apify_client/_resource_clients/__init__.py index d95ec9f9..e818ce34 100644 --- a/src/apify_client/_resource_clients/__init__.py +++ b/src/apify_client/_resource_clients/__init__.py @@ -17,9 +17,7 @@ from .run_collection import RunCollectionClient, RunCollectionClientAsync from .schedule import ScheduleClient, ScheduleClientAsync from .schedule_collection import ScheduleCollectionClient, ScheduleCollectionClientAsync -from .status_message_watcher import StatusMessageWatcher, StatusMessageWatcherAsync, StatusMessageWatcherSync from .store_collection import StoreCollectionClient, StoreCollectionClientAsync -from .streamed_log import StreamedLog, StreamedLogAsync, StreamedLogSync from .task import TaskClient, TaskClientAsync from .task_collection import TaskCollectionClient, TaskCollectionClientAsync from .user import UserClient, UserClientAsync @@ -67,14 +65,8 @@ 'ScheduleClientAsync', 'ScheduleCollectionClient', 'ScheduleCollectionClientAsync', - 'StatusMessageWatcher', - 'StatusMessageWatcherAsync', - 'StatusMessageWatcherSync', 'StoreCollectionClient', 'StoreCollectionClientAsync', - 'StreamedLog', - 'StreamedLogAsync', - 'StreamedLogSync', 'TaskClient', 'TaskClientAsync', 'TaskCollectionClient', diff --git a/src/apify_client/_resource_clients/_resource_client.py b/src/apify_client/_resource_clients/_resource_client.py index 7b2020ff..5f271ee8 100644 --- a/src/apify_client/_resource_clients/_resource_client.py +++ b/src/apify_client/_resource_clients/_resource_client.py @@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Any from apify_client._consts import DEFAULT_WAIT_FOR_FINISH, DEFAULT_WAIT_WHEN_JOB_NOT_EXIST, TERMINAL_STATUSES +from apify_client._docs import docs_group from apify_client._internal_models import ActorJobResponse from apify_client._logging import WithLogDetailsClient from apify_client._utils import catch_not_found_or_throw, response_to_dict, to_safe_id, to_seconds @@ -120,6 +121,7 @@ def _build_params(self, **kwargs: Any) -> dict: return {k: v for k, v in merged.items() if v is not None} +@docs_group('Resource clients') class ResourceClient(ResourceClientBase): """Base class for synchronous resource clients.""" @@ -286,6 +288,7 @@ def _wait_for_finish( return actor_job +@docs_group('Resource clients') class ResourceClientAsync(ResourceClientBase): """Base class for asynchronous resource clients.""" diff --git a/src/apify_client/_resource_clients/actor.py b/src/apify_client/_resource_clients/actor.py index 5e3594aa..c7000e66 100644 --- a/src/apify_client/_resource_clients/actor.py +++ b/src/apify_client/_resource_clients/actor.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any, Literal +from apify_client._docs import docs_group from apify_client._models import ( Actor, ActorPermissionLevel, @@ -47,8 +48,13 @@ ) +@docs_group('Resource clients') class ActorClient(ResourceClient): - """Sub-client for manipulating a single Actor.""" + """Sub-client for managing a specific Actor. + + Provides methods to manage a specific Actor, e.g. update it, delete it, build it, or start runs. Obtain an instance + via an appropriate method on the `ApifyClient` class. + """ def __init__( self, @@ -506,8 +512,13 @@ def validate_input( return True +@docs_group('Resource clients') class ActorClientAsync(ResourceClientAsync): - """Async sub-client for manipulating a single Actor.""" + """Sub-client for managing a specific Actor. + + Provides methods to manage a specific Actor, e.g. update it, delete it, build it, or start runs. Obtain an instance + via an appropriate method on the `ApifyClientAsync` class. + """ def __init__( self, diff --git a/src/apify_client/_resource_clients/actor_collection.py b/src/apify_client/_resource_clients/actor_collection.py index 7a2a6c63..255d9f9c 100644 --- a/src/apify_client/_resource_clients/actor_collection.py +++ b/src/apify_client/_resource_clients/actor_collection.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any, Literal +from apify_client._docs import docs_group from apify_client._models import Actor, ActorResponse, ListOfActors, ListOfActorsResponse from apify_client._representations import get_actor_repr from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync @@ -11,8 +12,13 @@ from datetime import timedelta +@docs_group('Resource clients') class ActorCollectionClient(ResourceClient): - """Sub-client for manipulating Actors.""" + """Sub-client for the Actor collection. + + Provides methods to manage the Actor collection, e.g. list or create Actors. Obtain an instance via an appropriate + method on the `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'acts') @@ -138,8 +144,13 @@ def create( return ActorResponse.model_validate(result).data +@docs_group('Resource clients') class ActorCollectionClientAsync(ResourceClientAsync): - """Async sub-client for manipulating Actors.""" + """Sub-client for the Actor collection. + + Provides methods to manage the Actor collection, e.g. list or create Actors. Obtain an instance via an appropriate + method on the `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'acts') diff --git a/src/apify_client/_resource_clients/actor_env_var.py b/src/apify_client/_resource_clients/actor_env_var.py index e4b9e985..ea5acce9 100644 --- a/src/apify_client/_resource_clients/actor_env_var.py +++ b/src/apify_client/_resource_clients/actor_env_var.py @@ -2,6 +2,7 @@ from typing import Any +from apify_client._docs import docs_group from apify_client._models import EnvVar, EnvVarResponse from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync from apify_client._utils import filter_none_values @@ -21,8 +22,13 @@ def get_actor_env_var_representation( } +@docs_group('Resource clients') class ActorEnvVarClient(ResourceClient): - """Sub-client for manipulating a single Actor environment variable.""" + """Sub-client for managing a specific Actor environment variable. + + Provides methods to manage a specific Actor environment variable, e.g. get, update, or delete it. Obtain an instance + via an appropriate method on the `ActorVersionClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'env-vars') @@ -77,8 +83,13 @@ def delete(self) -> None: self._delete() +@docs_group('Resource clients') class ActorEnvVarClientAsync(ResourceClientAsync): - """Async sub-client for manipulating a single Actor environment variable.""" + """Sub-client for managing a specific Actor environment variable. + + Provides methods to manage a specific Actor environment variable, e.g. get, update, or delete it. Obtain an instance + via an appropriate method on the `ActorVersionClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'env-vars') diff --git a/src/apify_client/_resource_clients/actor_env_var_collection.py b/src/apify_client/_resource_clients/actor_env_var_collection.py index 5bdd8f2f..4a82fb6a 100644 --- a/src/apify_client/_resource_clients/actor_env_var_collection.py +++ b/src/apify_client/_resource_clients/actor_env_var_collection.py @@ -2,14 +2,20 @@ from typing import Any +from apify_client._docs import docs_group from apify_client._models import EnvVar, EnvVarResponse, ListOfEnvVars, ListOfEnvVarsResponse from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync from apify_client._resource_clients.actor_env_var import get_actor_env_var_representation from apify_client._utils import filter_none_values +@docs_group('Resource clients') class ActorEnvVarCollectionClient(ResourceClient): - """Sub-client for manipulating Actor env vars.""" + """Sub-client for the Actor environment variable collection. + + Provides methods to manage Actor environment variables, e.g. list or create them. Obtain an instance via an + appropriate method on the `ActorVersionClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'env-vars') @@ -55,8 +61,13 @@ def create( return EnvVarResponse.model_validate(result).data +@docs_group('Resource clients') class ActorEnvVarCollectionClientAsync(ResourceClientAsync): - """Async sub-client for manipulating Actor env vars.""" + """Sub-client for the Actor environment variable collection. + + Provides methods to manage Actor environment variables, e.g. list or create them. Obtain an instance via an + appropriate method on the `ActorVersionClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'env-vars') diff --git a/src/apify_client/_resource_clients/actor_version.py b/src/apify_client/_resource_clients/actor_version.py index f4ea03a2..ce53e521 100644 --- a/src/apify_client/_resource_clients/actor_version.py +++ b/src/apify_client/_resource_clients/actor_version.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any +from apify_client._docs import docs_group from apify_client._models import Version, VersionResponse, VersionSourceType from apify_client._representations import get_actor_version_repr from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync @@ -16,8 +17,13 @@ ) +@docs_group('Resource clients') class ActorVersionClient(ResourceClient): - """Sub-client for manipulating a single Actor version.""" + """Sub-client for managing a specific Actor version. + + Provides methods to manage a specific Actor version, e.g. get, update, or delete it. Obtain an instance via an + appropriate method on the `ActorClient` class. + """ def __init__( self, @@ -121,8 +127,13 @@ def env_var(self, env_var_name: str) -> ActorEnvVarClient: ) +@docs_group('Resource clients') class ActorVersionClientAsync(ResourceClientAsync): - """Async sub-client for manipulating a single Actor version.""" + """Sub-client for managing a specific Actor version. + + Provides methods to manage a specific Actor version, e.g. get, update, or delete it. Obtain an instance via an + appropriate method on the `ActorClientAsync` class. + """ def __init__( self, diff --git a/src/apify_client/_resource_clients/actor_version_collection.py b/src/apify_client/_resource_clients/actor_version_collection.py index 40c69c03..bde7517b 100644 --- a/src/apify_client/_resource_clients/actor_version_collection.py +++ b/src/apify_client/_resource_clients/actor_version_collection.py @@ -2,6 +2,7 @@ from typing import Any +from apify_client._docs import docs_group from apify_client._models import ( ListOfVersions, ListOfVersionsResponse, @@ -14,8 +15,13 @@ from apify_client._utils import filter_none_values +@docs_group('Resource clients') class ActorVersionCollectionClient(ResourceClient): - """Sub-client for manipulating Actor versions.""" + """Sub-client for the Actor version collection. + + Provides methods to manage Actor versions, e.g. list or create them. Obtain an instance via an appropriate method + on the `ActorClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'versions') @@ -85,8 +91,13 @@ def create( return VersionResponse.model_validate(result).data +@docs_group('Resource clients') class ActorVersionCollectionClientAsync(ResourceClientAsync): - """Async sub-client for manipulating Actor versions.""" + """Sub-client for the Actor version collection. + + Provides methods to manage Actor versions, e.g. list or create them. Obtain an instance via an appropriate method + on the `ActorClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'versions') diff --git a/src/apify_client/_resource_clients/build.py b/src/apify_client/_resource_clients/build.py index ef8f7ef6..7416b614 100644 --- a/src/apify_client/_resource_clients/build.py +++ b/src/apify_client/_resource_clients/build.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any +from apify_client._docs import docs_group from apify_client._models import Build, BuildResponse from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync from apify_client._utils import response_to_dict @@ -12,8 +13,13 @@ from apify_client._resource_clients import LogClient, LogClientAsync +@docs_group('Resource clients') class BuildClient(ResourceClient): - """Sub-client for manipulating a single Actor build.""" + """Sub-client for managing a specific Actor build. + + Provides methods to manage a specific Actor build, e.g. get it, abort it, or wait for it to finish. Obtain an + instance via an appropriate method on the `ApifyClient` class. + """ def __init__( self, @@ -109,8 +115,13 @@ def log(self) -> LogClient: ) +@docs_group('Resource clients') class BuildClientAsync(ResourceClientAsync): - """Async sub-client for manipulating a single Actor build.""" + """Sub-client for managing a specific Actor build. + + Provides methods to manage a specific Actor build, e.g. get it, abort it, or wait for it to finish. Obtain an + instance via an appropriate method on the `ApifyClientAsync` class. + """ def __init__( self, diff --git a/src/apify_client/_resource_clients/build_collection.py b/src/apify_client/_resource_clients/build_collection.py index 3daee7aa..1a395a7f 100644 --- a/src/apify_client/_resource_clients/build_collection.py +++ b/src/apify_client/_resource_clients/build_collection.py @@ -2,12 +2,18 @@ from typing import Any +from apify_client._docs import docs_group from apify_client._models import ListOfBuilds, ListOfBuildsResponse from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync +@docs_group('Resource clients') class BuildCollectionClient(ResourceClient): - """Sub-client for listing Actor builds.""" + """Sub-client for the Actor build collection. + + Provides methods to manage Actor builds, e.g. list them. Obtain an instance via an appropriate method on the + `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'actor-builds') @@ -40,8 +46,13 @@ def list( return ListOfBuildsResponse.model_validate(result).data +@docs_group('Resource clients') class BuildCollectionClientAsync(ResourceClientAsync): - """Async sub-client for listing Actor builds.""" + """Sub-client for the Actor build collection. + + Provides methods to manage Actor builds, e.g. list them. Obtain an instance via an appropriate method on the + `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'actor-builds') diff --git a/src/apify_client/_resource_clients/dataset.py b/src/apify_client/_resource_clients/dataset.py index e0534743..2720a6cc 100644 --- a/src/apify_client/_resource_clients/dataset.py +++ b/src/apify_client/_resource_clients/dataset.py @@ -7,6 +7,7 @@ from urllib.parse import urlencode, urlparse, urlunparse from apify_client._consts import FAST_OPERATION_TIMEOUT, STANDARD_OPERATION_TIMEOUT +from apify_client._docs import docs_group from apify_client._models import Dataset, DatasetResponse, DatasetStatistics, DatasetStatisticsResponse from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync from apify_client._utils import ( @@ -28,6 +29,7 @@ from apify_client._models import GeneralAccess +@docs_group('Other') @dataclass class DatasetItemsPage: """A page of dataset items returned by the `list_items` method. @@ -56,8 +58,13 @@ class DatasetItemsPage: """Whether the items are sorted in descending order.""" +@docs_group('Resource clients') class DatasetClient(ResourceClient): - """Sub-client for manipulating a single dataset.""" + """Sub-client for managing a specific dataset. + + Provides methods to manage a specific dataset, e.g. get it, update it, or download its items. Obtain an instance + via an appropriate method on the `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'datasets') @@ -672,8 +679,13 @@ def create_items_public_url( return urlunparse(items_public_url) +@docs_group('Resource clients') class DatasetClientAsync(ResourceClientAsync): - """Async sub-client for manipulating a single dataset.""" + """Sub-client for managing a specific dataset. + + Provides methods to manage a specific dataset, e.g. get it, update it, or download its items. Obtain an instance + via an appropriate method on the `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'datasets') diff --git a/src/apify_client/_resource_clients/dataset_collection.py b/src/apify_client/_resource_clients/dataset_collection.py index 1effcdd0..2322fd67 100644 --- a/src/apify_client/_resource_clients/dataset_collection.py +++ b/src/apify_client/_resource_clients/dataset_collection.py @@ -2,13 +2,19 @@ from typing import Any +from apify_client._docs import docs_group from apify_client._models import Dataset, DatasetResponse, ListOfDatasets, ListOfDatasetsResponse from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync from apify_client._utils import filter_none_values +@docs_group('Resource clients') class DatasetCollectionClient(ResourceClient): - """Sub-client for manipulating datasets.""" + """Sub-client for the dataset collection. + + Provides methods to manage the dataset collection, e.g. list or create datasets. Obtain an instance via an + appropriate method on the `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'datasets') @@ -54,8 +60,13 @@ def get_or_create(self, *, name: str | None = None, schema: dict | None = None) return DatasetResponse.model_validate(result).data +@docs_group('Resource clients') class DatasetCollectionClientAsync(ResourceClientAsync): - """Async sub-client for manipulating datasets.""" + """Sub-client for the dataset collection. + + Provides methods to manage the dataset collection, e.g. list or create datasets. Obtain an instance via an + appropriate method on the `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'datasets') diff --git a/src/apify_client/_resource_clients/key_value_store.py b/src/apify_client/_resource_clients/key_value_store.py index d6490108..03119c5f 100644 --- a/src/apify_client/_resource_clients/key_value_store.py +++ b/src/apify_client/_resource_clients/key_value_store.py @@ -7,6 +7,7 @@ from urllib.parse import urlencode, urlparse, urlunparse from apify_client._consts import FAST_OPERATION_TIMEOUT, STANDARD_OPERATION_TIMEOUT +from apify_client._docs import docs_group from apify_client._models import ( KeyValueStore, KeyValueStoreKey, @@ -68,8 +69,13 @@ def _parse_get_record_response(response: Response) -> Any: return response_data +@docs_group('Resource clients') class KeyValueStoreClient(ResourceClient): - """Sub-client for manipulating a single key-value store.""" + """Sub-client for managing a specific key-value store. + + Provides methods to manage a specific key-value store, e.g. get it, update it, or manage its records. Obtain an + instance via an appropriate method on the `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'key-value-stores') @@ -447,8 +453,13 @@ def create_keys_public_url( return urlunparse(keys_public_url) +@docs_group('Resource clients') class KeyValueStoreClientAsync(ResourceClientAsync): - """Async sub-client for manipulating a single key-value store.""" + """Sub-client for managing a specific key-value store. + + Provides methods to manage a specific key-value store, e.g. get it, update it, or manage its records. Obtain an + instance via an appropriate method on the `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'key-value-stores') diff --git a/src/apify_client/_resource_clients/key_value_store_collection.py b/src/apify_client/_resource_clients/key_value_store_collection.py index abf5a87f..f693952d 100644 --- a/src/apify_client/_resource_clients/key_value_store_collection.py +++ b/src/apify_client/_resource_clients/key_value_store_collection.py @@ -2,6 +2,7 @@ from typing import Any +from apify_client._docs import docs_group from apify_client._models import ( KeyValueStore, KeyValueStoreResponse, @@ -12,8 +13,13 @@ from apify_client._utils import filter_none_values +@docs_group('Resource clients') class KeyValueStoreCollectionClient(ResourceClient): - """Sub-client for manipulating key-value stores.""" + """Sub-client for the key-value store collection. + + Provides methods to manage the key-value store collection, e.g. list or create key-value stores. Obtain an instance + via an appropriate method on the `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'key-value-stores') @@ -64,8 +70,13 @@ def get_or_create( return KeyValueStoreResponse.model_validate(result).data +@docs_group('Resource clients') class KeyValueStoreCollectionClientAsync(ResourceClientAsync): - """Async sub-client for manipulating key-value stores.""" + """Sub-client for the key-value store collection. + + Provides methods to manage the key-value store collection, e.g. list or create key-value stores. Obtain an instance + via an appropriate method on the `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'key-value-stores') diff --git a/src/apify_client/_resource_clients/log.py b/src/apify_client/_resource_clients/log.py index b214c114..07493ab9 100644 --- a/src/apify_client/_resource_clients/log.py +++ b/src/apify_client/_resource_clients/log.py @@ -3,6 +3,7 @@ from contextlib import asynccontextmanager, contextmanager from typing import TYPE_CHECKING, Any +from apify_client._docs import docs_group from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync from apify_client._utils import catch_not_found_or_throw from apify_client.errors import ApifyApiError @@ -13,8 +14,13 @@ import impit +@docs_group('Resource clients') class LogClient(ResourceClient): - """Sub-client for manipulating logs.""" + """Sub-client for managing a specific log. + + Provides methods to manage logs, e.g. get or stream them. Obtain an instance via an appropriate method on the + `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'logs') @@ -100,8 +106,13 @@ def stream(self, *, raw: bool = False) -> Iterator[impit.Response | None]: response.close() +@docs_group('Resource clients') class LogClientAsync(ResourceClientAsync): - """Async sub-client for manipulating logs.""" + """Sub-client for managing a specific log. + + Provides methods to manage logs, e.g. get or stream them. Obtain an instance via an appropriate method on the + `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'logs') diff --git a/src/apify_client/_resource_clients/request_queue.py b/src/apify_client/_resource_clients/request_queue.py index 2a7ac991..408a3d73 100644 --- a/src/apify_client/_resource_clients/request_queue.py +++ b/src/apify_client/_resource_clients/request_queue.py @@ -10,6 +10,7 @@ from more_itertools import constrained_batches from apify_client._consts import FAST_OPERATION_TIMEOUT, STANDARD_OPERATION_TIMEOUT +from apify_client._docs import docs_group from apify_client._models import ( AddedRequest, AddRequestResponse, @@ -51,8 +52,13 @@ _SAFETY_BUFFER_PERCENT = 0.01 / 100 # 0.01% +@docs_group('Resource clients') class RequestQueueClient(ResourceClient): - """Sub-client for manipulating a single request queue.""" + """Sub-client for managing a specific request queue. + + Provides methods to manage a specific request queue, e.g. update it, delete it, or manage its requests. Obtain an + instance via an appropriate method on the `ApifyClient` class. + """ def __init__( # noqa: D417 self, @@ -456,8 +462,13 @@ def unlock_requests(self: RequestQueueClient) -> UnlockRequestsResult: return UnlockRequestsResponse.model_validate(result).data +@docs_group('Resource clients') class RequestQueueClientAsync(ResourceClientAsync): - """Async sub-client for manipulating a single request queue.""" + """Sub-client for managing a specific request queue. + + Provides methods to manage a specific request queue, e.g. update it, delete it, or manage its requests. Obtain an + instance via an appropriate method on the `ApifyClientAsync` class. + """ def __init__( # noqa: D417 self, diff --git a/src/apify_client/_resource_clients/request_queue_collection.py b/src/apify_client/_resource_clients/request_queue_collection.py index 177450f5..04ae9afd 100644 --- a/src/apify_client/_resource_clients/request_queue_collection.py +++ b/src/apify_client/_resource_clients/request_queue_collection.py @@ -2,6 +2,7 @@ from typing import Any +from apify_client._docs import docs_group from apify_client._models import ( ListOfRequestQueues, ListOfRequestQueuesResponse, @@ -11,8 +12,13 @@ from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync +@docs_group('Resource clients') class RequestQueueCollectionClient(ResourceClient): - """Sub-client for manipulating request queues.""" + """Sub-client for the request queue collection. + + Provides methods to manage the request queue collection, e.g. list or create request queues. Obtain an instance + via an appropriate method on the `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'request-queues') @@ -57,8 +63,13 @@ def get_or_create(self, *, name: str | None = None) -> RequestQueue: return RequestQueueResponse.model_validate(result).data +@docs_group('Resource clients') class RequestQueueCollectionClientAsync(ResourceClientAsync): - """Async sub-client for manipulating request queues.""" + """Sub-client for the request queue collection. + + Provides methods to manage the request queue collection, e.g. list or create request queues. Obtain an instance + via an appropriate method on the `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'request-queues') diff --git a/src/apify_client/_resource_clients/run.py b/src/apify_client/_resource_clients/run.py index c545ebd6..1f7da905 100644 --- a/src/apify_client/_resource_clients/run.py +++ b/src/apify_client/_resource_clients/run.py @@ -7,9 +7,12 @@ from datetime import timedelta from typing import TYPE_CHECKING, Any +from apify_client._docs import docs_group from apify_client._logging import create_redirect_logger from apify_client._models import Run, RunResponse from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync +from apify_client._status_message_watcher import StatusMessageWatcher, StatusMessageWatcherAsync +from apify_client._streamed_log import StreamedLog, StreamedLogAsync from apify_client._utils import ( encode_key_value_store_record_value, filter_none_values, @@ -32,15 +35,16 @@ LogClientAsync, RequestQueueClient, RequestQueueClientAsync, - StatusMessageWatcherAsync, - StatusMessageWatcherSync, - StreamedLogAsync, - StreamedLogSync, ) +@docs_group('Resource clients') class RunClient(ResourceClient): - """Sub-client for manipulating a single Actor run.""" + """Sub-client for managing a specific Actor run. + + Provides methods to manage a specific Actor run, e.g. get it, update it, abort it, or wait for it to finish. + Obtain an instance via an appropriate method on the `ApifyClient` class. + """ def __init__( self, @@ -305,7 +309,12 @@ def log(self) -> LogClient: **self._base_client_kwargs, ) - def get_streamed_log(self, to_logger: logging.Logger | None = None, *, from_start: bool = True) -> StreamedLogSync: + def get_streamed_log( + self, + to_logger: logging.Logger | None = None, + *, + from_start: bool = True, + ) -> StreamedLog: """Get `StreamedLog` instance that can be used to redirect logs. `StreamedLog` can be explicitly started and stopped or used as a context manager. @@ -339,7 +348,7 @@ def get_streamed_log(self, to_logger: logging.Logger | None = None, *, from_star name = ' '.join(part for part in (actor_name, run_id) if part) to_logger = create_redirect_logger(f'apify.{name}') - return self._client_registry.streamed_log(log_client=self.log(), to_logger=to_logger, from_start=from_start) + return StreamedLog(log_client=self.log(), to_logger=to_logger, from_start=from_start) def charge( self, @@ -384,8 +393,10 @@ def charge( ) def get_status_message_watcher( - self, to_logger: logging.Logger | None = None, check_period: timedelta = timedelta(seconds=1) - ) -> StatusMessageWatcherSync: + self, + to_logger: logging.Logger | None = None, + check_period: timedelta = timedelta(seconds=1), + ) -> StatusMessageWatcher: """Get `StatusMessageWatcher` instance that can be used to redirect status and status messages to logs. `StatusMessageWatcher` can be explicitly started and stopped or used as a context manager. @@ -418,13 +429,16 @@ def get_status_message_watcher( name = ' '.join(part for part in (actor_name, run_id) if part) to_logger = create_redirect_logger(f'apify.{name}') - return self._client_registry.status_message_watcher( - run_client=self, to_logger=to_logger, check_period=check_period - ) + return StatusMessageWatcher(run_client=self, to_logger=to_logger, check_period=check_period) +@docs_group('Resource clients') class RunClientAsync(ResourceClientAsync): - """Async sub-client for manipulating a single Actor run.""" + """Sub-client for managing a specific Actor run. + + Provides methods to manage a specific Actor run, e.g. get it, update it, abort it, or wait for it to finish. + Obtain an instance via an appropriate method on the `ApifyClientAsync` class. + """ def __init__( self, @@ -689,7 +703,10 @@ def log(self) -> LogClientAsync: ) async def get_streamed_log( - self, to_logger: logging.Logger | None = None, *, from_start: bool = True + self, + to_logger: logging.Logger | None = None, + *, + from_start: bool = True, ) -> StreamedLogAsync: """Get `StreamedLog` instance that can be used to redirect logs. @@ -724,7 +741,7 @@ async def get_streamed_log( name = ' '.join(part for part in (actor_name, run_id) if part) to_logger = create_redirect_logger(f'apify.{name}') - return self._client_registry.streamed_log(log_client=self.log(), to_logger=to_logger, from_start=from_start) + return StreamedLogAsync(log_client=self.log(), to_logger=to_logger, from_start=from_start) async def charge( self, @@ -806,6 +823,4 @@ async def get_status_message_watcher( name = ' '.join(part for part in (actor_name, run_id) if part) to_logger = create_redirect_logger(f'apify.{name}') - return self._client_registry.status_message_watcher( - run_client=self, to_logger=to_logger, check_period=check_period - ) + return StatusMessageWatcherAsync(run_client=self, to_logger=to_logger, check_period=check_period) diff --git a/src/apify_client/_resource_clients/run_collection.py b/src/apify_client/_resource_clients/run_collection.py index cdded09d..2cf32801 100644 --- a/src/apify_client/_resource_clients/run_collection.py +++ b/src/apify_client/_resource_clients/run_collection.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any +from apify_client._docs import docs_group from apify_client._models import ListOfRuns, ListOfRunsResponse from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync from apify_client._utils import enum_to_value @@ -12,8 +13,13 @@ from apify_client._consts import ActorJobStatus +@docs_group('Resource clients') class RunCollectionClient(ResourceClient): - """Sub-client for listing Actor runs.""" + """Sub-client for the Actor run collection. + + Provides methods to manage Actor runs, e.g. list them. Obtain an instance via an appropriate method on the + `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'actor-runs') @@ -61,8 +67,13 @@ def list( return ListOfRunsResponse.model_validate(result).data +@docs_group('Resource clients') class RunCollectionClientAsync(ResourceClientAsync): - """Async sub-client for listing Actor runs.""" + """Sub-client for the Actor run collection. + + Provides methods to manage Actor runs, e.g. list them. Obtain an instance via an appropriate method on the + `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'actor-runs') diff --git a/src/apify_client/_resource_clients/schedule.py b/src/apify_client/_resource_clients/schedule.py index b87e3a17..63bd4ebd 100644 --- a/src/apify_client/_resource_clients/schedule.py +++ b/src/apify_client/_resource_clients/schedule.py @@ -2,6 +2,7 @@ from typing import Any +from apify_client._docs import docs_group from apify_client._models import Schedule, ScheduleInvoked, ScheduleLogResponse, ScheduleResponse from apify_client._representations import get_schedule_repr from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync @@ -9,8 +10,13 @@ from apify_client.errors import ApifyApiError +@docs_group('Resource clients') class ScheduleClient(ResourceClient): - """Sub-client for manipulating a single schedule.""" + """Sub-client for managing a specific schedule. + + Provides methods to manage a specific schedule, e.g. get, update, or delete it. Obtain an instance via an + appropriate method on the `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'schedules') @@ -104,8 +110,13 @@ def get_log(self) -> list[ScheduleInvoked] | None: return None +@docs_group('Resource clients') class ScheduleClientAsync(ResourceClientAsync): - """Async sub-client for manipulating a single schedule.""" + """Sub-client for managing a specific schedule. + + Provides methods to manage a specific schedule, e.g. get, update, or delete it. Obtain an instance via an + appropriate method on the `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'schedules') diff --git a/src/apify_client/_resource_clients/schedule_collection.py b/src/apify_client/_resource_clients/schedule_collection.py index ff69a2d5..a335b213 100644 --- a/src/apify_client/_resource_clients/schedule_collection.py +++ b/src/apify_client/_resource_clients/schedule_collection.py @@ -2,14 +2,20 @@ from typing import Any +from apify_client._docs import docs_group from apify_client._models import ListOfSchedules, ListOfSchedulesResponse, Schedule, ScheduleResponse from apify_client._representations import get_schedule_repr from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync from apify_client._utils import filter_none_values +@docs_group('Resource clients') class ScheduleCollectionClient(ResourceClient): - """Sub-client for manipulating schedules.""" + """Sub-client for the schedule collection. + + Provides methods to manage the schedule collection, e.g. list or create schedules. Obtain an instance via an + appropriate method on the `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'schedules') @@ -86,8 +92,13 @@ def create( return ScheduleResponse.model_validate(result).data +@docs_group('Resource clients') class ScheduleCollectionClientAsync(ResourceClientAsync): - """Async sub-client for manipulating schedules.""" + """Sub-client for the schedule collection. + + Provides methods to manage the schedule collection, e.g. list or create schedules. Obtain an instance via an + appropriate method on the `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'schedules') diff --git a/src/apify_client/_resource_clients/store_collection.py b/src/apify_client/_resource_clients/store_collection.py index ffa7f9b6..002dacf3 100644 --- a/src/apify_client/_resource_clients/store_collection.py +++ b/src/apify_client/_resource_clients/store_collection.py @@ -2,12 +2,18 @@ from typing import Any +from apify_client._docs import docs_group from apify_client._models import ListOfActorsInStoreResponse, ListOfStoreActors from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync +@docs_group('Resource clients') class StoreCollectionClient(ResourceClient): - """Sub-client for Apify store.""" + """Sub-client for the Apify store collection. + + Provides methods to browse the Apify store, e.g. list available Actors. Obtain an instance via an appropriate + method on the `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'store') @@ -53,8 +59,13 @@ def list( return ListOfActorsInStoreResponse.model_validate(result).data +@docs_group('Resource clients') class StoreCollectionClientAsync(ResourceClientAsync): - """Async sub-client for Apify store.""" + """Sub-client for the Apify store collection. + + Provides methods to browse the Apify store, e.g. list available Actors. Obtain an instance via an appropriate + method on the `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'store') diff --git a/src/apify_client/_resource_clients/task.py b/src/apify_client/_resource_clients/task.py index a380510c..04d76883 100644 --- a/src/apify_client/_resource_clients/task.py +++ b/src/apify_client/_resource_clients/task.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any +from apify_client._docs import docs_group from apify_client._models import Run, RunOrigin, RunResponse, Task, TaskResponse from apify_client._representations import get_task_repr from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync @@ -29,8 +30,13 @@ ) +@docs_group('Resource clients') class TaskClient(ResourceClient): - """Sub-client for manipulating a single task.""" + """Sub-client for managing a specific task. + + Provides methods to manage a specific task, e.g. update it, delete it, or start runs. Obtain an instance via an + appropriate method on the `ApifyClient` class. + """ def __init__( self, @@ -317,8 +323,13 @@ def webhooks(self) -> WebhookCollectionClient: return self._client_registry.webhook_collection_client(**self._base_client_kwargs) +@docs_group('Resource clients') class TaskClientAsync(ResourceClientAsync): - """Async sub-client for manipulating a single task.""" + """Sub-client for managing a specific task. + + Provides methods to manage a specific task, e.g. update it, delete it, or start runs. Obtain an instance via an + appropriate method on the `ApifyClientAsync` class. + """ def __init__( self, diff --git a/src/apify_client/_resource_clients/task_collection.py b/src/apify_client/_resource_clients/task_collection.py index e55b0d46..b70bcae0 100644 --- a/src/apify_client/_resource_clients/task_collection.py +++ b/src/apify_client/_resource_clients/task_collection.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any +from apify_client._docs import docs_group from apify_client._models import ListOfTasks, ListOfTasksResponse, Task, TaskResponse from apify_client._representations import get_task_repr from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync @@ -11,8 +12,13 @@ from datetime import timedelta +@docs_group('Resource clients') class TaskCollectionClient(ResourceClient): - """Sub-client for manipulating tasks.""" + """Sub-client for the task collection. + + Provides methods to manage the task collection, e.g. list or create tasks. Obtain an instance via an appropriate + method on the `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'actor-tasks') @@ -110,8 +116,13 @@ def create( return TaskResponse.model_validate(result).data +@docs_group('Resource clients') class TaskCollectionClientAsync(ResourceClientAsync): - """Async sub-client for manipulating tasks.""" + """Sub-client for the task collection. + + Provides methods to manage the task collection, e.g. list or create tasks. Obtain an instance via an appropriate + method on the `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'actor-tasks') diff --git a/src/apify_client/_resource_clients/user.py b/src/apify_client/_resource_clients/user.py index af6e6357..165242f8 100644 --- a/src/apify_client/_resource_clients/user.py +++ b/src/apify_client/_resource_clients/user.py @@ -4,6 +4,7 @@ from pydantic import ValidationError +from apify_client._docs import docs_group from apify_client._models import ( AccountLimits, LimitsResponse, @@ -19,8 +20,13 @@ from apify_client.errors import ApifyApiError +@docs_group('Resource clients') class UserClient(ResourceClient): - """Sub-client for querying user data.""" + """Sub-client for managing user account information. + + Provides methods to manage user account information, e.g. get user data or monthly usage. Obtain an instance via + an appropriate method on the `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_id = kwargs.pop('resource_id', None) @@ -118,8 +124,13 @@ def update_limits( ) +@docs_group('Resource clients') class UserClientAsync(ResourceClientAsync): - """Async sub-client for querying user data.""" + """Sub-client for managing user account information. + + Provides methods to manage user account information, e.g. get user data or monthly usage. Obtain an instance via + an appropriate method on the `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_id = kwargs.pop('resource_id', None) diff --git a/src/apify_client/_resource_clients/webhook.py b/src/apify_client/_resource_clients/webhook.py index ecf906de..bb70e23f 100644 --- a/src/apify_client/_resource_clients/webhook.py +++ b/src/apify_client/_resource_clients/webhook.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any +from apify_client._docs import docs_group from apify_client._models import ( TestWebhookResponse, Webhook, @@ -18,8 +19,13 @@ from apify_client._resource_clients import WebhookDispatchCollectionClient, WebhookDispatchCollectionClientAsync +@docs_group('Resource clients') class WebhookClient(ResourceClient): - """Sub-client for manipulating a single webhook.""" + """Sub-client for managing a specific webhook. + + Provides methods to manage a specific webhook, e.g. get, update, or delete it. Obtain an instance via an + appropriate method on the `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'webhooks') @@ -135,8 +141,13 @@ def dispatches(self) -> WebhookDispatchCollectionClient: ) +@docs_group('Resource clients') class WebhookClientAsync(ResourceClientAsync): - """Async sub-client for manipulating a single webhook.""" + """Sub-client for managing a specific webhook. + + Provides methods to manage a specific webhook, e.g. get, update, or delete it. Obtain an instance via an + appropriate method on the `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'webhooks') diff --git a/src/apify_client/_resource_clients/webhook_collection.py b/src/apify_client/_resource_clients/webhook_collection.py index 82c90781..db4de310 100644 --- a/src/apify_client/_resource_clients/webhook_collection.py +++ b/src/apify_client/_resource_clients/webhook_collection.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Any +from apify_client._docs import docs_group from apify_client._models import ListOfWebhooks, ListOfWebhooksResponse, Webhook, WebhookResponse from apify_client._representations import get_webhook_repr from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync @@ -11,8 +12,13 @@ from apify_client._models import WebhookEventType +@docs_group('Resource clients') class WebhookCollectionClient(ResourceClient): - """Sub-client for manipulating webhooks.""" + """Sub-client for the webhook collection. + + Provides methods to manage the webhook collection, e.g. list or create webhooks. Obtain an instance via an + appropriate method on the `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'webhooks') @@ -97,8 +103,13 @@ def create( return WebhookResponse.model_validate(result).data +@docs_group('Resource clients') class WebhookCollectionClientAsync(ResourceClientAsync): - """Async sub-client for manipulating webhooks.""" + """Sub-client for the webhook collection. + + Provides methods to manage the webhook collection, e.g. list or create webhooks. Obtain an instance via an + appropriate method on the `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'webhooks') diff --git a/src/apify_client/_resource_clients/webhook_dispatch.py b/src/apify_client/_resource_clients/webhook_dispatch.py index 9ee80cbb..be9a8a6e 100644 --- a/src/apify_client/_resource_clients/webhook_dispatch.py +++ b/src/apify_client/_resource_clients/webhook_dispatch.py @@ -2,12 +2,18 @@ from typing import Any +from apify_client._docs import docs_group from apify_client._models import WebhookDispatch, WebhookDispatchResponse from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync +@docs_group('Resource clients') class WebhookDispatchClient(ResourceClient): - """Sub-client for querying information about a webhook dispatch.""" + """Sub-client for managing a specific webhook dispatch. + + Provides methods to manage a specific webhook dispatch, e.g. get its details. Obtain an instance via an appropriate + method on the `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'webhook-dispatches') @@ -27,8 +33,13 @@ def get(self) -> WebhookDispatch | None: return WebhookDispatchResponse.model_validate(result).data +@docs_group('Resource clients') class WebhookDispatchClientAsync(ResourceClientAsync): - """Async sub-client for querying information about a webhook dispatch.""" + """Sub-client for managing a specific webhook dispatch. + + Provides methods to manage a specific webhook dispatch, e.g. get its details. Obtain an instance via an appropriate + method on the `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'webhook-dispatches') diff --git a/src/apify_client/_resource_clients/webhook_dispatch_collection.py b/src/apify_client/_resource_clients/webhook_dispatch_collection.py index 941db91a..7a70b4ad 100644 --- a/src/apify_client/_resource_clients/webhook_dispatch_collection.py +++ b/src/apify_client/_resource_clients/webhook_dispatch_collection.py @@ -2,12 +2,18 @@ from typing import Any +from apify_client._docs import docs_group from apify_client._models import ListOfWebhookDispatches, WebhookDispatchList from apify_client._resource_clients._resource_client import ResourceClient, ResourceClientAsync +@docs_group('Resource clients') class WebhookDispatchCollectionClient(ResourceClient): - """Sub-client for listing webhook dispatches.""" + """Sub-client for the webhook dispatch collection. + + Provides methods to manage webhook dispatches, e.g. list them. Obtain an instance via an appropriate method on the + `ApifyClient` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'webhook-dispatches') @@ -36,8 +42,13 @@ def list( return WebhookDispatchList.model_validate(result).data +@docs_group('Resource clients') class WebhookDispatchCollectionClientAsync(ResourceClientAsync): - """Async sub-client for listing webhook dispatches.""" + """Sub-client for the webhook dispatch collection. + + Provides methods to manage webhook dispatches, e.g. list them. Obtain an instance via an appropriate method on the + `ApifyClientAsync` class. + """ def __init__(self, *args: Any, **kwargs: Any) -> None: resource_path = kwargs.pop('resource_path', 'webhook-dispatches') diff --git a/src/apify_client/_resource_clients/status_message_watcher.py b/src/apify_client/_status_message_watcher.py similarity index 68% rename from src/apify_client/_resource_clients/status_message_watcher.py rename to src/apify_client/_status_message_watcher.py index 33f760b8..138c2207 100644 --- a/src/apify_client/_resource_clients/status_message_watcher.py +++ b/src/apify_client/_status_message_watcher.py @@ -8,6 +8,7 @@ from threading import Thread from typing import TYPE_CHECKING, Self +from apify_client._docs import docs_group from apify_client._utils import to_seconds if TYPE_CHECKING: @@ -18,12 +19,8 @@ from apify_client._resource_clients import RunClient, RunClientAsync -class StatusMessageWatcher: - """Utility class for logging status messages from another Actor run. - - Status message is logged at fixed time intervals, and there is no guarantee that all messages will be logged, - especially in cases of frequent status message changes. - """ +class StatusMessageWatcherBase: + """Base class for polling and logging Actor run status messages.""" _force_propagate = False # This is final sleep time to try to get the last status and status message of finished Actor run. @@ -32,12 +29,6 @@ class StatusMessageWatcher: _final_sleep_time_s = 6 def __init__(self, *, to_logger: logging.Logger, check_period: timedelta = timedelta(seconds=5)) -> None: - """Initialize `StatusMessageWatcher`. - - Args: - to_logger: The logger to which the status message will be redirected. - check_period: The period with which the status message will be polled. - """ if self._force_propagate: to_logger.propagate = True self._to_logger = to_logger @@ -66,8 +57,17 @@ def _log_run_data(self, run_data: Run | None) -> bool: return True -class StatusMessageWatcherAsync(StatusMessageWatcher): - """Async variant of `StatusMessageWatcher` that is logging in task.""" +@docs_group('Other') +class StatusMessageWatcherAsync(StatusMessageWatcherBase): + """Polls and logs Actor run status messages in an asyncio task. + + The status message and status of the Actor run are polled at a fixed interval and forwarded to the provided logger + whenever they change. There is no guarantee that every intermediate status message will be captured, especially + when messages change rapidly. + + Can be used as an async context manager, which automatically starts and cancels the polling task. Alternatively, + call `start` and `stop` manually. Obtain an instance via `RunClientAsync.get_status_message_watcher`. + """ def __init__( self, *, run_client: RunClientAsync, to_logger: logging.Logger, check_period: timedelta = timedelta(seconds=1) @@ -75,16 +75,19 @@ def __init__( """Initialize `StatusMessageWatcherAsync`. Args: - run_client: The client for run that will be used to get a status and message. - to_logger: The logger to which the status message will be redirected. - check_period: The period with which the status message will be polled. + run_client: The run client used to poll the Actor run status and status message. + to_logger: The logger to which the status messages will be forwarded. + check_period: How often to poll the status message. """ super().__init__(to_logger=to_logger, check_period=check_period) self._run_client = run_client self._logging_task: Task | None = None def start(self) -> Task: - """Start the logging task. The caller has to handle any cleanup by manually calling the `stop` method.""" + """Start the polling task. + + The caller is responsible for cleanup by calling the `stop` method when done. + """ if self._logging_task and not self._logging_task.done(): raise RuntimeError('Logging task already active') self._logging_task = asyncio.create_task(self._log_changed_status_message()) @@ -123,18 +126,27 @@ async def _log_changed_status_message(self) -> None: await asyncio.sleep(self._check_period) -class StatusMessageWatcherSync(StatusMessageWatcher): - """Sync variant of `StatusMessageWatcher` that is logging in thread.""" +@docs_group('Other') +class StatusMessageWatcher(StatusMessageWatcherBase): + """Polls and logs Actor run status messages in a background thread. + + The status message and status of the Actor run are polled at a fixed interval and forwarded to the provided logger + whenever they change. There is no guarantee that every intermediate status message will be captured, especially + when messages change rapidly. + + Can be used as a context manager, which automatically starts and stops the polling thread. Alternatively, + call `start` and `stop` manually. Obtain an instance via `RunClient.get_status_message_watcher`. + """ def __init__( self, *, run_client: RunClient, to_logger: logging.Logger, check_period: timedelta = timedelta(seconds=1) ) -> None: - """Initialize `StatusMessageWatcherSync`. + """Initialize `StatusMessageWatcher`. Args: - run_client: The client for run that will be used to get a status and message. - to_logger: The logger to which the status message will be redirected. - check_period: The period with which the status message will be polled. + run_client: The run client used to poll the Actor run status and status message. + to_logger: The logger to which the status messages will be forwarded. + check_period: How often to poll the status message. """ super().__init__(to_logger=to_logger, check_period=check_period) self._run_client = run_client @@ -142,7 +154,10 @@ def __init__( self._stop_logging = False def start(self) -> Thread: - """Start the logging thread. The caller has to handle any cleanup by manually calling the `stop` method.""" + """Start the polling thread. + + The caller is responsible for cleanup by calling the `stop` method when done. + """ if self._logging_thread: raise RuntimeError('Logging thread already active') self._stop_logging = False @@ -151,7 +166,7 @@ def start(self) -> Thread: return self._logging_thread def stop(self) -> None: - """Signal the _logging_thread thread to stop logging and wait for it to finish.""" + """Signal the logging thread to stop logging and wait for it to finish.""" if not self._logging_thread: raise RuntimeError('Logging thread is not active') time.sleep(self._final_sleep_time_s) @@ -161,14 +176,14 @@ def stop(self) -> None: self._stop_logging = False def __enter__(self) -> Self: - """Start the logging task within the context. Exiting the context will cancel the logging task.""" + """Start the logging thread within the context. Exiting the context will stop the logging thread.""" self.start() return self def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None ) -> None: - """Cancel the logging task.""" + """Stop the logging thread.""" self.stop() def _log_changed_status_message(self) -> None: diff --git a/src/apify_client/_resource_clients/streamed_log.py b/src/apify_client/_streamed_log.py similarity index 74% rename from src/apify_client/_resource_clients/streamed_log.py rename to src/apify_client/_streamed_log.py index edc83304..f57ba074 100644 --- a/src/apify_client/_resource_clients/streamed_log.py +++ b/src/apify_client/_streamed_log.py @@ -9,36 +9,21 @@ from threading import Thread from typing import TYPE_CHECKING, Self, cast +from apify_client._docs import docs_group + if TYPE_CHECKING: from types import TracebackType from apify_client._resource_clients import LogClient, LogClientAsync -class StreamedLog: - """Utility class for streaming logs from another Actor. - - It uses buffer to deal with possibly chunked logs. Chunked logs are stored in buffer. Chunks are expected to contain - specific markers that indicate the start of the log message. Each time a new chunk with complete split marker - arrives, the buffer is processed, logged and emptied. - - This works only if the logs have datetime marker in ISO format. For example, `2025-05-12T15:35:59.429Z` This is the - default log standard for the Actors. - """ +class StreamedLogBase: + """Base class for streaming and buffering chunked Actor run logs.""" # Test related flag to enable propagation of logs to the `caplog` fixture during tests. _force_propagate = False def __init__(self, to_logger: logging.Logger, *, from_start: bool = True) -> None: - """Initialize `StreamedLog`. - - Args: - to_logger: The logger to which the logs will be redirected. - from_start: If `True`, all logs from the start of the Actor run will be redirected. If `False`, only newly - arrived logs will be redirected. This can be useful for redirecting only a small portion of relevant - logs for long-running Actors in stand-by. - - """ if self._force_propagate: to_logger.propagate = True self._to_logger = to_logger @@ -94,17 +79,37 @@ def _guess_log_level_from_message(message: str) -> int: return logging.INFO -class StreamedLogSync(StreamedLog): - """Sync variant of `StreamedLog` that is logging in threads.""" +@docs_group('Other') +class StreamedLog(StreamedLogBase): + """Streams Actor run log output to a Python logger in a background thread. + + The log stream is consumed in a background thread and each log message is forwarded to the provided logger with + an appropriate log level inferred from the message content. + + Can be used as a context manager, which automatically starts and stops the streaming thread. Alternatively, + call `start` and `stop` manually. Obtain an instance via `RunClient.get_streamed_log`. + """ def __init__(self, log_client: LogClient, *, to_logger: logging.Logger, from_start: bool = True) -> None: + """Initialize `StreamedLog`. + + Args: + log_client: The log client used to stream raw log data from the Actor run. + to_logger: The logger to which the log messages will be forwarded. + from_start: If `True`, all logs from the start of the Actor run will be streamed. If `False`, only newly + arrived logs will be streamed. This can be useful for long-running Actors in stand-by mode where only + recent logs are relevant. + """ super().__init__(to_logger=to_logger, from_start=from_start) self._log_client = log_client self._streaming_thread: Thread | None = None self._stop_logging = False def start(self) -> Thread: - """Start the streaming thread. The caller has to handle any cleanup by manually calling the `stop` method.""" + """Start the streaming thread. + + The caller is responsible for cleanup by calling the `stop` method when done. + """ if self._streaming_thread: raise RuntimeError('Streaming thread already active') self._stop_logging = False @@ -146,16 +151,36 @@ def _stream_log(self) -> None: return -class StreamedLogAsync(StreamedLog): - """Async variant of `StreamedLog` that is logging in tasks.""" +@docs_group('Other') +class StreamedLogAsync(StreamedLogBase): + """Streams Actor run log output to a Python logger in an asyncio task. + + The log stream is consumed in a background asyncio task and each log message is forwarded to the provided logger + with an appropriate log level inferred from the message content. + + Can be used as an async context manager, which automatically starts and cancels the streaming task. Alternatively, + call `start` and `stop` manually. Obtain an instance via `RunClientAsync.get_streamed_log`. + """ def __init__(self, log_client: LogClientAsync, *, to_logger: logging.Logger, from_start: bool = True) -> None: + """Initialize `StreamedLogAsync`. + + Args: + log_client: The async log client used to stream raw log data from the Actor run. + to_logger: The logger to which the log messages will be forwarded. + from_start: If `True`, all logs from the start of the Actor run will be streamed. If `False`, only newly + arrived logs will be streamed. This can be useful for long-running Actors in stand-by mode where only + recent logs are relevant. + """ super().__init__(to_logger=to_logger, from_start=from_start) self._log_client = log_client self._streaming_task: Task | None = None def start(self) -> Task: - """Start the streaming task. The caller has to handle any cleanup by manually calling the `stop` method.""" + """Start the streaming task. + + The caller is responsible for cleanup by calling the `stop` method when done. + """ if self._streaming_task and not self._streaming_task.done(): raise RuntimeError('Streaming task already active') self._streaming_task = asyncio.create_task(self._stream_log()) diff --git a/src/apify_client/errors.py b/src/apify_client/errors.py index bd4e2205..ba167d33 100644 --- a/src/apify_client/errors.py +++ b/src/apify_client/errors.py @@ -2,28 +2,46 @@ from typing import TYPE_CHECKING +from apify_client._docs import docs_group + if TYPE_CHECKING: import impit +@docs_group('Errors') class ApifyClientError(Exception): - """Base class for errors specific to the Apify API Client.""" + """Base class for all Apify API client errors. + + All custom exceptions defined by this package inherit from this class, making it convenient + to catch any client-related error with a single except clause. + """ +@docs_group('Errors') class ApifyApiError(ApifyClientError): - """Error from Apify API responses (rate limits, validation errors, internal errors). - - Thrown when HTTP request succeeds but API returns an error response. Rate limit and internal errors are - retried automatically, while validation errors are thrown immediately for user correction. + """Error raised when the Apify API returns an error response. + + This error is raised when an HTTP request to the Apify API succeeds at the transport level + but the server returns an error status code. Rate limit (HTTP 429) and server errors (HTTP 5xx) + are retried automatically before this error is raised, while client errors (HTTP 4xx) are raised + immediately. + + Attributes: + message: The error message from the API response. + type: The error type identifier from the API response (e.g. `record-not-found`). + status_code: The HTTP status code of the error response. + attempt: The attempt number when the error was raised. + http_method: The HTTP method of the failed request. + data: Additional error data from the API response. """ def __init__(self, response: impit.Response, attempt: int, method: str = 'GET') -> None: - """Initialize an API error from a failed response. + """Initialize the API error from a failed response. Args: - response: The failed API response. - attempt: The attempt number when the request failed. - method: The HTTP method used. + response: The failed HTTP response from the Apify API. + attempt: The attempt number when the request failed (1-indexed). + method: The HTTP method of the failed request. """ self.message: str | None = None self.type: str | None = None @@ -55,17 +73,20 @@ def __init__(self, response: impit.Response, attempt: int, method: str = 'GET') self.http_method = method +@docs_group('Errors') class InvalidResponseBodyError(ApifyClientError): - """Error when response body cannot be parsed (e.g., partial JSON). + """Error raised when a response body cannot be parsed. - Commonly occurs when only partial JSON is received. Usually resolved by retrying the request. + This typically occurs when the API returns a partial or malformed JSON response, for example + due to a network interruption. The client retries such requests automatically, so this error + is only raised after all retry attempts have been exhausted. """ def __init__(self, response: impit.Response) -> None: - """Initialize a new instance. + """Initialize the error from an unparsable response. Args: - response: The response that failed to parse. + response: The HTTP response whose body could not be parsed. """ super().__init__('Response body could not be parsed') diff --git a/tests/integration/test_webhook.py b/tests/integration/test_webhook.py index abd15451..7d26fd4f 100644 --- a/tests/integration/test_webhook.py +++ b/tests/integration/test_webhook.py @@ -1,9 +1,4 @@ -"""Unified tests for webhook (sync + async). - -Webhook CRUD tests bind to a specific already-completed run (actor_run_id) instead of to an actor (actor_id). -This prevents webhooks from firing when other integration tests run the same actor, which would cause -"Webhook was removed" error emails. -""" +"""Unified tests for webhook (sync + async).""" from __future__ import annotations @@ -14,7 +9,15 @@ from ._utils import maybe_await -from apify_client._models import ActorJobStatus, WebhookEventType +from apify_client._models import ( + ActorJobStatus, + ListOfRuns, + ListOfWebhookDispatches, + ListOfWebhooks, + Webhook, + WebhookDispatch, + WebhookEventType, +) HELLO_WORLD_ACTOR = 'apify/hello-world' @@ -27,14 +30,17 @@ async def _get_finished_run_id(client: ApifyClient | ApifyClientAsync) -> str: waits for it to finish. """ runs_page = await maybe_await(client.actor(HELLO_WORLD_ACTOR).runs().list(limit=1, status=ActorJobStatus.SUCCEEDED)) - assert runs_page is not None + + assert isinstance(runs_page, ListOfRuns) if len(runs_page.items) > 0: return runs_page.items[0].id # No completed runs found - start one and wait for it to finish run = await maybe_await(client.actor(HELLO_WORLD_ACTOR).call()) - assert run is not None + + assert isinstance(run, ListOfRuns) + return run.id @@ -42,9 +48,7 @@ async def test_list_webhooks(client: ApifyClient | ApifyClientAsync) -> None: """Test listing webhooks.""" webhooks_page = await maybe_await(client.webhooks().list(limit=10)) - assert webhooks_page is not None - assert webhooks_page.items is not None - # User may have 0 webhooks + assert isinstance(webhooks_page, ListOfWebhooks) assert isinstance(webhooks_page.items, list) @@ -52,8 +56,7 @@ async def test_list_webhooks_pagination(client: ApifyClient | ApifyClientAsync) """Test listing webhooks with pagination.""" webhooks_page = await maybe_await(client.webhooks().list(limit=5, offset=0)) - assert webhooks_page is not None - assert webhooks_page.items is not None + assert isinstance(webhooks_page, ListOfWebhooks) assert isinstance(webhooks_page.items, list) @@ -70,15 +73,15 @@ async def test_webhook_create_and_get(client: ApifyClient | ApifyClientAsync) -> is_ad_hoc=True, ) ) - webhook_client = client.webhook(created_webhook.id) try: - assert created_webhook is not None - assert created_webhook.id is not None + assert isinstance(created_webhook, Webhook) # Get the same webhook + webhook_client = client.webhook(created_webhook.id) retrieved_webhook = await maybe_await(webhook_client.get()) - assert retrieved_webhook is not None + + assert isinstance(retrieved_webhook, Webhook) assert retrieved_webhook.id == created_webhook.id finally: await maybe_await(webhook_client.delete()) @@ -97,6 +100,7 @@ async def test_webhook_update(client: ApifyClient | ApifyClientAsync) -> None: is_ad_hoc=True, ) ) + assert isinstance(created_webhook, Webhook) webhook_client = client.webhook(created_webhook.id) try: @@ -107,6 +111,7 @@ async def test_webhook_update(client: ApifyClient | ApifyClientAsync) -> None: actor_run_id=run_id, ) ) + assert isinstance(updated_webhook, Webhook) assert str(updated_webhook.request_url) == 'https://httpbin.org/anything' finally: await maybe_await(webhook_client.delete()) @@ -125,12 +130,13 @@ async def test_webhook_test(client: ApifyClient | ApifyClientAsync) -> None: is_ad_hoc=True, ) ) + assert isinstance(created_webhook, Webhook) webhook_client = client.webhook(created_webhook.id) try: # Test webhook (creates a dispatch with dummy payload) dispatch = await maybe_await(webhook_client.test()) - assert dispatch is not None + assert isinstance(dispatch, WebhookDispatch) assert dispatch.id is not None finally: await maybe_await(webhook_client.delete()) @@ -149,6 +155,8 @@ async def test_webhook_dispatches(client: ApifyClient | ApifyClientAsync) -> Non is_ad_hoc=True, ) ) + + assert isinstance(created_webhook, Webhook) webhook_client = client.webhook(created_webhook.id) try: @@ -157,9 +165,9 @@ async def test_webhook_dispatches(client: ApifyClient | ApifyClientAsync) -> Non # List dispatches for this webhook dispatches = await maybe_await(webhook_client.dispatches().list()) - assert dispatches is not None - assert dispatches.items is not None + assert isinstance(dispatches, ListOfWebhookDispatches) assert len(dispatches.items) > 0 + finally: await maybe_await(webhook_client.delete()) @@ -177,6 +185,7 @@ async def test_webhook_delete(client: ApifyClient | ApifyClientAsync) -> None: is_ad_hoc=True, ) ) + assert isinstance(created_webhook, Webhook) webhook_client = client.webhook(created_webhook.id) # Delete webhook diff --git a/tests/unit/test_logging.py b/tests/unit/test_logging.py index de9eb318..83635b06 100644 --- a/tests/unit/test_logging.py +++ b/tests/unit/test_logging.py @@ -14,7 +14,8 @@ from apify_client import ApifyClient, ApifyClientAsync from apify_client._logging import RedirectLogFormatter from apify_client._models import ActorJobStatus -from apify_client._resource_clients import StatusMessageWatcher, StreamedLog +from apify_client._status_message_watcher import StatusMessageWatcherBase +from apify_client._streamed_log import StreamedLogBase if TYPE_CHECKING: from collections.abc import Iterator @@ -212,18 +213,18 @@ def mock_api(httpserver: HTTPServer) -> None: @pytest.fixture def propagate_stream_logs() -> None: """Enable propagation of logs to the caplog fixture.""" - StreamedLog._force_propagate = True - StatusMessageWatcher._force_propagate = True + StreamedLogBase._force_propagate = True + StatusMessageWatcherBase._force_propagate = True logging.getLogger(f'apify.{_MOCKED_ACTOR_NAME}-{_MOCKED_RUN_ID}').setLevel(logging.DEBUG) @pytest.fixture def reduce_final_timeout_for_status_message_redirector() -> None: - """Reduce timeout used by the `StatusMessageWatcher`. + """Reduce timeout used by the status message watcher. This timeout makes sense on the platform, but in tests it is better to reduce it to speed up the tests. """ - StatusMessageWatcher._final_sleep_time_s = 2 + StatusMessageWatcherBase._final_sleep_time_s = 2 @pytest.mark.parametrize( @@ -247,7 +248,7 @@ async def test_redirected_logs_async( run_client = ApifyClientAsync(token='mocked_token', api_url=api_url).run(run_id=_MOCKED_RUN_ID) - with patch('apify_client._resource_clients.streamed_log.datetime') as mocked_datetime: + with patch('apify_client._streamed_log.datetime') as mocked_datetime: # Mock `now()` so that it has timestamp bigger than the first 3 logs mocked_datetime.now.return_value = datetime.fromisoformat('2025-05-13T07:24:14.132+00:00') streamed_log = await run_client.get_streamed_log(from_start=log_from_start) @@ -287,7 +288,7 @@ def test_redirected_logs_sync( run_client = ApifyClient(token='mocked_token', api_url=api_url).run(run_id=_MOCKED_RUN_ID) - with patch('apify_client._resource_clients.streamed_log.datetime') as mocked_datetime: + with patch('apify_client._streamed_log.datetime') as mocked_datetime: # Mock `now()` so that it has timestamp bigger than the first 3 logs mocked_datetime.now.return_value = datetime.fromisoformat('2025-05-13T07:24:14.132+00:00') streamed_log = run_client.get_streamed_log(from_start=log_from_start) diff --git a/website/transformDocs.js b/website/transformDocs.js index e1c993c2..12994d9f 100644 --- a/website/transformDocs.js +++ b/website/transformDocs.js @@ -55,34 +55,44 @@ const TYPEDOC_KINDS = { } const GROUP_ORDER = [ - 'Main Classes', - 'Main Clients', - 'Resource Clients', - 'Async Resource Clients', - 'Helper Classes', + 'Apify API clients', + 'HTTP clients', + 'Resource clients', 'Errors', - 'Constructors', - 'Methods', - 'Properties', - 'Constants', - 'Enumeration Members' + 'Models', + 'Other', ]; const groupSort = (g1, g2) => { - if(GROUP_ORDER.includes(g1) && GROUP_ORDER.includes(g2)){ - return GROUP_ORDER.indexOf(g1) - GROUP_ORDER.indexOf(g2) - } + const i1 = GROUP_ORDER.indexOf(g1); + const i2 = GROUP_ORDER.indexOf(g2); + // Both known – sort by defined order + if (i1 !== -1 && i2 !== -1) return i1 - i2; + // Unknown groups go after known ones + if (i1 !== -1) return -1; + if (i2 !== -1) return 1; + // Both unknown – alphabetical return g1.localeCompare(g2); }; +// Extract the docs_group name from a docspec member's decorators +function getDocsGroup(member) { + const decoration = member.decorations?.find(d => d.name === 'docs_group'); + if (decoration?.args) { + const match = decoration.args.match(/['"](.+?)['"]/); + if (match) return match[1]; + } + return undefined; +} + function getGroupName(object) { + // If a docs_group was extracted from the Python decorator, use it + if (object.docsGroup) { + return object.docsGroup; + } + + // Fallback grouping for sub-members (methods, properties, etc.) inside classes const groupPredicates = { - 'Errors': (x) => x.name.toLowerCase().includes('error'), - 'Main Classes': (x) => ['Actor', 'Dataset', 'KeyValueStore', 'RequestQueue'].includes(x.name), - 'Main Clients': (x) => ['ApifyClient', 'ApifyClientAsync'].includes(x.name), - 'Async Resource Clients': (x) => x.name.toLowerCase().includes('async'), - 'Resource Clients': (x) => x.kindString === 'Class' && x.name.toLowerCase().includes('client'), - 'Helper Classes': (x) => x.kindString === 'Class', 'Methods': (x) => x.kindString === 'Method', 'Constructors': (x) => x.kindString === 'Constructor', 'Properties': (x) => x.kindString === 'Property', @@ -90,11 +100,11 @@ function getGroupName(object) { 'Enumeration Members': (x) => x.kindString === 'Enumeration Member', }; - const [group] = Object.entries(groupPredicates).find( + const found = Object.entries(groupPredicates).find( ([_, predicate]) => predicate(object) ); - return group; + return found ? found[0] : 'Other'; } // Strips the Optional[] type from the type string, and replaces generic types with just the main type @@ -158,9 +168,17 @@ function extractArgsAndReturns(docstring) { return { parameters, returns }; } -// Objects with decorators named 'ignore_docs' or with empty docstrings will be ignored +// Objects with decorators named 'ignore_docs' or without docstrings will be ignored, +// unless they have a 'docs_group' decorator which explicitly marks them for documentation function isHidden(member) { - return member.decorations?.some(d => d.name === 'ignore_docs') || member.name === 'ignore_docs' || !member.docstring?.content; + if (member.decorations?.some(d => d.name === 'ignore_docs') || member.name === 'ignore_docs') { + return true; + } + // Members with @docs_group are always visible (even without docstrings) + if (member.decorations?.some(d => d.name === 'docs_group')) { + return false; + } + return !member.docstring?.content; } // Each object in the Typedoc structure has an unique ID, @@ -205,11 +223,15 @@ function convertObject(obj, parent, module) { moduleName = moduleShortcuts[fullName].replace(`.${member.name}`, ''); } + // Extract docs_group from the Python decorator (if present) + const docsGroup = getDocsGroup(member); + // Create the Typedoc member object let typedocMember = { id: oid++, name: member.name, module: moduleName, // This is an extension to the original Typedoc structure, to support showing where the member is exported from + docsGroup, ...typedocKind, flags: {}, comment: member.docstring ? {