diff --git a/docs/python/sdk-reference.md b/docs/python/sdk-reference.md index dab105c1..d45f43df 100644 --- a/docs/python/sdk-reference.md +++ b/docs/python/sdk-reference.md @@ -14,6 +14,10 @@ title: Python SDK API Reference rendering: show_root_full_path: false +## ::: planet.OrdersAPI + rendering: + show_root_full_path: false + ## ::: planet.order_request rendering: show_root_full_path: false @@ -22,6 +26,10 @@ title: Python SDK API Reference rendering: show_root_full_path: false +## ::: planet.DataAPI + rendering: + show_root_full_path: false + ## ::: planet.data_filter rendering: show_root_full_path: false @@ -30,6 +38,10 @@ title: Python SDK API Reference rendering: show_root_full_path: false +## ::: planet.SubscriptionsAPI + rendering: + show_root_full_path: false + ## ::: planet.subscription_request rendering: show_root_full_path: false diff --git a/planet/__init__.py b/planet/__init__.py index 98f6b6c7..0464d110 100644 --- a/planet/__init__.py +++ b/planet/__init__.py @@ -16,7 +16,7 @@ from . import data_filter, order_request, reporting, subscription_request from .__version__ import __version__ # NOQA from .auth import Auth -from .clients import DataClient, OrdersClient, SubscriptionsClient # NOQA +from .clients import DataAPI, DataClient, OrdersAPI, OrdersClient, SubscriptionsAPI, SubscriptionsClient # NOQA from .io import collect from .client import Planet @@ -25,12 +25,15 @@ 'Auth', 'collect', 'DataClient', + 'DataAPI', 'data_filter', 'OrdersClient', + 'OrdersAPI', 'order_request', 'reporting', 'Planet', 'Session', 'SubscriptionsClient', + 'SubscriptionsAPI', 'subscription_request' ] diff --git a/planet/clients/__init__.py b/planet/clients/__init__.py index d93b3c7a..182cac8c 100644 --- a/planet/clients/__init__.py +++ b/planet/clients/__init__.py @@ -12,11 +12,18 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from .data import DataClient -from .orders import OrdersClient -from .subscriptions import SubscriptionsClient +from .data import DataAPI, DataClient +from .orders import OrdersAPI, OrdersClient +from .subscriptions import SubscriptionsAPI, SubscriptionsClient -__all__ = ['DataClient', 'OrdersClient', 'SubscriptionsClient'] +__all__ = [ + 'DataAPI', + 'DataClient', + 'OrdersAPI', + 'OrdersClient', + 'SubscriptionsAPI', + 'SubscriptionsClient' +] # Organize client classes by their module name to allow lookup. _client_directory = { diff --git a/planet/clients/data.py b/planet/clients/data.py index 85943523..c98e3c89 100644 --- a/planet/clients/data.py +++ b/planet/clients/data.py @@ -27,6 +27,7 @@ from ..models import Paged, StreamingBody from ..specs import validate_data_item_type from ..geojson import as_geom_or_ref +from planet.clients.decorators import docstring BASE_URL = f'{PLANET_BASE_URL}/data/v1/' SEARCHES_PATH = '/searches' @@ -720,6 +721,7 @@ def search( except StopAsyncIteration: pass + @docstring(DataClient.create_search.__doc__) def create_search( self, item_types: List[str], @@ -728,37 +730,6 @@ def create_search( enable_email: bool = False, geometry: Optional[Dict] = None, ) -> Dict: - """Create a new saved structured item search. - - To filter to items you have access to download which are of standard - (aka not test) quality, use the following: - - ```python - >>> from planet import data_filter - >>> data_filter.and_filter([ - ... data_filter.permission_filter(), - ... data_filter.std_quality_filter() - >>> ]) - - ``` - - To avoid filtering out any imagery, supply a blank AndFilter, which can - be created with `data_filter.and_filter([])`. - - - Parameters: - item_types: The item types to include in the search. - search_filter: Structured search criteria. - name: The name of the saved search. - enable_email: Send a daily email when new results are added. - geometry: A feature reference or a GeoJSON - - Returns: - Description of the saved search. - - Raises: - planet.exceptions.APIError: On API error. - """ return self._client.call_sync( self._client.create_search(item_types, search_filter, @@ -766,6 +737,7 @@ def create_search( enable_email, geometry)) + @docstring(DataClient.update_search.__doc__) def update_search(self, search_id: str, item_types: List[str], @@ -773,19 +745,6 @@ def update_search(self, name: str, enable_email: bool = False, geometry: Optional[dict] = None) -> Dict[str, Any]: - """Update an existing saved search. - - Parameters: - search_id: Saved search identifier. - item_types: The item types to include in the search. - search_filter: Structured search criteria. - name: The name of the saved search. - enable_email: Send a daily email when new results are added. - geometry: A feature reference or a GeoJSON - - Returns: - Description of the saved search. - """ return self._client.call_sync( self._client.update_search(search_id, item_types, @@ -794,26 +753,11 @@ def update_search(self, enable_email, geometry)) + @docstring(DataClient.list_searches.__doc__) def list_searches(self, sort: str = LIST_SORT_DEFAULT, search_type: str = LIST_SEARCH_TYPE_DEFAULT, limit: int = 100) -> Iterator[Dict[str, Any]]: - """Iterate through list of searches available to the user. - - Parameters: - sort: Field and direction to order results by. - search_type: Filter to specified search type. - limit: Maximum number of results to return. When set to 0, no - maximum is applied. - - Yields: - Description of a search. - - Raises: - planet.exceptions.APIError: On API error. - planet.exceptions.ClientError: If sort or search_type are not - valid. - """ results = self._client.list_searches(sort, search_type, limit) try: @@ -822,57 +766,19 @@ def list_searches(self, except StopAsyncIteration: pass + @docstring(DataClient.delete_search.__doc__) def delete_search(self, search_id: str): - """Delete an existing saved search. - - Parameters: - search_id: Saved search identifier. - - Raises: - planet.exceptions.APIError: On API error. - """ return self._client.call_sync(self._client.delete_search(search_id)) + @docstring(DataClient.get_search.__doc__) def get_search(self, search_id: str) -> Dict: - """Get a saved search by id. - - Parameters: - search_id: Stored search identifier. - - Returns: - Saved search details. - - Raises: - planet.exceptions.APIError: On API error. - """ return self._client.call_sync(self._client.get_search(search_id)) + @docstring(DataClient.run_search.__doc__) def run_search(self, search_id: str, sort: Optional[str] = None, limit: int = 100) -> Iterator[Dict[str, Any]]: - """Iterate over results from a saved search. - - Note: - The name of this method is based on the API's method name. This - method provides iteration over results, it does not get a - single result description or return a list of descriptions. - - Parameters: - search_id: Stored search identifier. - sort: Field and direction to order results by. Valid options are - given in SEARCH_SORT. - limit: Maximum number of results to return. When set to 0, no - maximum is applied. - - Yields: - Description of an item. - - Raises: - planet.exceptions.APIError: On API error. - planet.exceptions.ClientError: If search_id or sort is not valid. - """ - results = self._client.run_search(search_id, sort, limit) try: @@ -881,83 +787,31 @@ def run_search(self, except StopAsyncIteration: pass + @docstring(DataClient.get_stats.__doc__) def get_stats(self, item_types: List[str], search_filter: Dict[str, Any], interval: str) -> Dict[str, Any]: - """Get item search statistics. - - Parameters: - item_types: The item types to include in the search. - search_filter: Structured search criteria. - interval: The size of the histogram date buckets. - - Returns: - A full JSON description of the returned statistics result - histogram. - - Raises: - planet.exceptions.APIError: On API error. - planet.exceptions.ClientError: If interval is not valid. - """ return self._client.call_sync( self._client.get_stats(item_types, search_filter, interval)) + @docstring(DataClient.list_item_assets.__doc__) def list_item_assets(self, item_type_id: str, item_id: str) -> Dict[str, Any]: - """List all assets available for an item. - - An asset describes a product that can be derived from an item's source - data, and can be used for various analytic, visual or other purposes. - These are referred to as asset_types. - - Parameters: - item_type_id: Item type identifier. - item_id: Item identifier. - - Returns: - Descriptions of available assets as a dictionary with asset_type_id - as keys and asset description as value. - - Raises: - planet.exceptions.APIError: On API error. - """ return self._client.call_sync( self._client.list_item_assets(item_type_id, item_id)) + @docstring(DataClient.get_asset.__doc__) def get_asset(self, item_type_id: str, item_id: str, asset_type_id: str) -> Dict[str, Any]: - """Get an item asset description. - - Parameters: - item_type_id: Item type identifier. - item_id: Item identifier. - asset_type_id: Asset type identifier. - - Returns: - Description of the asset. - - Raises: - planet.exceptions.APIError: On API error. - planet.exceptions.ClientError: If asset type identifier is not - valid. - """ return self._client.call_sync( self._client.get_asset(item_type_id, item_id, asset_type_id)) + @docstring(DataClient.activate_asset.__doc__) def activate_asset(self, asset: Dict[str, Any]): - """Activate an item asset. - - Parameters: - asset: Description of the asset. Obtained from get_asset(). - - Raises: - planet.exceptions.APIError: On API error. - planet.exceptions.ClientError: If asset description is not - valid. - """ return self._client.call_sync(self._client.activate_asset(asset)) + @docstring(DataClient.wait_asset.__doc__) def wait_asset( self, asset: dict, @@ -965,60 +819,16 @@ def wait_asset( max_attempts: int = WAIT_MAX_ATTEMPTS, callback: Optional[Callable[[str], None]] = None) -> Dict[Any, Any]: - """Wait for an item asset to be active. - - Prior to waiting for the asset to be active, be sure to activate the - asset with activate_asset(). - - Parameters: - asset: Description of the asset. Obtained from get_asset(). - delay: Time (in seconds) between polls. - max_attempts: Maximum number of polls. When set to 0, no limit - is applied. - callback: Function that handles state progress updates. - - Returns: - Last received description of the asset. - - Raises: - planet.exceptions.APIError: On API error. - planet.exceptions.ClientError: If asset_type_id is not valid or is - not available or if the maximum number of attempts is reached - before the asset is active. - """ return self._client.call_sync( self._client.wait_asset(asset, delay, max_attempts, callback)) + @docstring(DataClient.download_asset.__doc__) def download_asset(self, asset: dict, filename: Optional[str] = None, directory: Path = Path('.'), overwrite: bool = False, progress_bar: bool = True) -> Path: - """Download an asset. - - The asset must be active before it can be downloaded. This can be - achieved with activate_asset() followed by wait_asset(). - - If overwrite is False and the file already exists, download will be - skipped and the file path will be returned as usual. - - Parameters: - asset: Description of the asset. Obtained from get_asset() or - wait_asset(). - filename: Custom name to assign to downloaded file. - directory: Base directory for file download. - overwrite: Overwrite any existing files. - progress_bar: Show progress bar during download. - - Returns: - Path to downloaded file. - - Raises: - planet.exceptions.APIError: On API error. - planet.exceptions.ClientError: If asset is not active or asset - description is not valid. - """ return self._client.call_sync( self._client.download_asset(asset, filename, @@ -1027,20 +837,6 @@ def download_asset(self, progress_bar)) @staticmethod + @docstring(DataClient.validate_checksum.__doc__) def validate_checksum(asset: Dict[str, Any], filename: Path): - """Validate checksum of downloaded file - - Compares checksum calculated from the file against the value provided - in the asset. - - - Parameters: - asset: Description of the asset. Obtained from get_asset() or - wait_asset(). - filename: Full path to downloaded file. - - Raises: - planet.exceptions.ClientError: If the file does not exist or if - checksums do not match. - """ return DataClient.validate_checksum(asset, filename) diff --git a/planet/clients/decorators.py b/planet/clients/decorators.py new file mode 100644 index 00000000..28217ef6 --- /dev/null +++ b/planet/clients/decorators.py @@ -0,0 +1,7 @@ +def docstring(s: str): + + def decorator(func): + func.__doc__ = s + return func + + return decorator diff --git a/planet/clients/orders.py b/planet/clients/orders.py index 62f734bc..2d264ad5 100644 --- a/planet/clients/orders.py +++ b/planet/clients/orders.py @@ -26,6 +26,7 @@ from ..constants import PLANET_BASE_URL from ..http import Session from ..models import Paged, StreamingBody +from planet.clients.decorators import docstring BASE_URL = f'{PLANET_BASE_URL}/compute/ops' STATS_PATH = '/stats/orders/v2' @@ -556,87 +557,30 @@ def main(): """ return self._client.call_sync(self._client.create_order(request)) + @docstring(OrdersClient.get_order.__doc__) def get_order(self, order_id: str) -> Dict: - """Get order details by Order ID. - - Parameters: - order_id: The ID of the order - - Returns: - JSON description of the order - - Raises: - planet.exceptions.ClientError: If order_id is not a valid UUID. - planet.exceptions.APIError: On API error. - """ return self._client.call_sync(self._client.get_order(order_id)) + @docstring(OrdersClient.cancel_order.__doc__) def cancel_order(self, order_id: str) -> Dict[str, Any]: - """Cancel a queued order. - - Parameters: - order_id: The ID of the order - - Returns: - Results of the cancel request - - Raises: - planet.exceptions.ClientError: If order_id is not a valid UUID. - planet.exceptions.APIError: On API error. - """ return self._client.call_sync(self._client.cancel_order(order_id)) + @docstring(OrdersClient.cancel_orders.__doc__) def cancel_orders(self, order_ids: Optional[List[str]] = None) -> Dict[str, Any]: - """Cancel queued orders in bulk. - - Parameters: - order_ids: The IDs of the orders. If empty or None, all orders in a - pre-running state will be cancelled. - - Returns: - Results of the bulk cancel request - - Raises: - planet.exceptions.ClientError: If an entry in order_ids is not a - valid UUID. - planet.exceptions.APIError: On API error. - """ return self._client.call_sync(self._client.cancel_orders(order_ids)) + @docstring(OrdersClient.aggregated_order_stats.__doc__) def aggregated_order_stats(self) -> Dict[str, Any]: - """Get aggregated counts of active orders. - - Returns: - Aggregated order counts - - Raises: - planet.exceptions.APIError: On API error. - """ return self._client.call_sync(self._client.aggregated_order_stats()) + @docstring(OrdersClient.download_asset.__doc__) def download_asset(self, location: str, filename: Optional[str] = None, directory: Path = Path('.'), overwrite: bool = False, progress_bar: bool = True) -> Path: - """Download ordered asset. - - Parameters: - location: Download location url including download token. - filename: Custom name to assign to downloaded file. - directory: Base directory for file download. This directory will be - created if it does not already exist. - overwrite: Overwrite any existing files. - progress_bar: Show progress bar during download. - - Returns: - Path to downloaded file. - - Raises: - planet.exceptions.APIError: On API error. - """ return self._client.call_sync( self._client.download_asset(location, filename, @@ -644,130 +588,36 @@ def download_asset(self, overwrite, progress_bar)) + @docstring(OrdersClient.download_order.__doc__) def download_order(self, order_id: str, directory: Path = Path('.'), overwrite: bool = False, progress_bar: bool = False) -> List[Path]: - """Download all assets in an order. - - Parameters: - order_id: The ID of the order. - directory: Base directory for file download. This directory must - already exist. - overwrite: Overwrite files if they already exist. - progress_bar: Show progress bar during download. - - Returns: - Paths to downloaded files. - - Raises: - planet.exceptions.APIError: On API error. - planet.exceptions.ClientError: If the order is not in a final - state. - """ return self._client.call_sync( self._client.download_order(order_id, directory, overwrite, progress_bar)) + @docstring(OrdersClient.validate_checksum.__doc__) def validate_checksum(self, directory: Path, checksum: str): - """Validate checksums of downloaded files against order manifest. - - For each file entry in the order manifest, the specified checksum given - in the manifest file will be validated against the checksum calculated - from the downloaded file. - - Parameters: - directory: Path to order directory. - checksum: The type of checksum hash- 'MD5' or 'SHA256'. - - Raises: - planet.exceptions.ClientError: If a file is missing or if checksums - do not match. - """ return self._client.validate_checksum(directory, checksum) + @docstring(OrdersClient.wait.__doc__) def wait(self, order_id: str, state: Optional[str] = None, delay: int = 5, max_attempts: int = 200, callback: Optional[Callable[[str], None]] = None) -> str: - """Wait until order reaches desired state. - - Returns the state of the order on the last poll. - - This function polls the Orders API to determine the order state, with - the specified delay between each polling attempt, until the - order reaches a final state, or earlier state, if specified. - If the maximum number of attempts is reached before polling is - complete, an exception is raised. Setting 'max_attempts' to zero will - result in no limit on the number of attempts. - - Setting 'delay' to zero results in no delay between polling attempts. - This will likely result in throttling by the Orders API, which has - a rate limit of 10 requests per second. If many orders are being - polled asynchronously, consider increasing the delay to avoid - throttling. - - By default, polling completes when the order reaches a final state. - If 'state' is given, polling will complete when the specified earlier - state is reached or passed. - - Example: - ```python - from planet.reporting import StateBar - - with StateBar() as bar: - wait(order_id, callback=bar.update_state) - ``` - - Parameters: - order_id: The ID of the order. - state: State prior to a final state that will end polling. - delay: Time (in seconds) between polls. - max_attempts: Maximum number of polls. Set to zero for no limit. - callback: Function that handles state progress updates. - - Returns - State of the order. - - Raises: - planet.exceptions.APIError: On API error. - planet.exceptions.ClientError: If order_id or state is not valid or - if the maximum number of attempts is reached before the - specified state or a final state is reached. - """ return self._client.call_sync( self._client.wait(order_id, state, delay, max_attempts, callback)) + @docstring(OrdersClient.list_orders.__doc__) def list_orders(self, state: Optional[str] = None, limit: int = 100) -> Iterator[dict]: - """Iterate over the list of stored orders. - - Order descriptions are sorted by creation date with the last created - order returned first. - - Note: - The name of this method is based on the API's method name. This - method provides iteration over results, it does not get a - single result description or return a list of descriptions. - - Parameters: - state: Filter orders to given state. - limit: Maximum number of results to return. When set to 0, no - maximum is applied. - - Yields: - Description of an order. - - Raises: - planet.exceptions.APIError: On API error. - planet.exceptions.ClientError: If state is not valid. - """ results = self._client.list_orders(state, limit) try: diff --git a/planet/clients/subscriptions.py b/planet/clients/subscriptions.py index 82194dc3..1436dd0b 100644 --- a/planet/clients/subscriptions.py +++ b/planet/clients/subscriptions.py @@ -5,6 +5,7 @@ from typing_extensions import Literal +from planet.clients.decorators import docstring from planet.exceptions import APIError, ClientError from planet.http import Session from planet.models import Paged @@ -379,33 +380,11 @@ def __init__(self, self._client = SubscriptionsClient(session, base_url) + @docstring(SubscriptionsClient.list_subscriptions.__doc__) def list_subscriptions(self, status: Optional[Sequence[str]] = None, source_type: Optional[str] = None, limit: int = 100) -> Iterator[Dict]: - """Iterate over list of account subscriptions with optional filtering. - - Note: - The name of this method is based on the API's method name. - This method provides iteration over subcriptions, it does - not return a list. - - Args: - status (Set[str]): pass subscriptions with status in this - set, filter out subscriptions with status not in this - set. - limit (int): limit the number of subscriptions in the - results. - TODO: user_id - - Yields: - dict: a description of a subscription. - - Raises: - APIError: on an API server error. - ClientError: on a client error. - """ - results = self._client.list_subscriptions(status, source_type, limit) try: @@ -414,91 +393,33 @@ def list_subscriptions(self, except StopAsyncIteration: pass + @docstring(SubscriptionsClient.create_subscription.__doc__) def create_subscription(self, request: Dict) -> Dict: - """Create a Subscription. - - Args: - request (dict): description of a subscription. - - Returns: - dict: description of created subscription. - - Raises: - APIError: on an API server error. - ClientError: on a client error. - """ return self._client.call_sync( self._client.create_subscription(request)) + @docstring(SubscriptionsClient.cancel_subscription.__doc__) def cancel_subscription(self, subscription_id: str) -> None: - """Cancel a Subscription. - - Args: - subscription_id (str): id of subscription to cancel. - - Returns: - None - - Raises: - APIError: on an API server error. - ClientError: on a client error. - """ return self._client.call_sync( self._client.cancel_subscription(subscription_id)) + @docstring(SubscriptionsClient.update_subscription.__doc__) def update_subscription(self, subscription_id: str, request: dict) -> dict: - """Update (edit) a Subscription via PUT. - - Args - subscription_id (str): id of the subscription to update. - request (dict): subscription content for update, full - payload is required. - - Returns: - dict: description of the updated subscription. - - Raises: - APIError: on an API server error. - ClientError: on a client error. - """ return self._client.call_sync( self._client.update_subscription(subscription_id, request)) + @docstring(SubscriptionsClient.patch_subscription.__doc__) def patch_subscription(self, subscription_id: str, request: Dict[str, Any]) -> Dict[str, Any]: - """Update (edit) a Subscription via PATCH. - - Args - subscription_id (str): id of the subscription to update. - request (dict): subscription content for update, only - attributes to update are required. - - Returns: - dict: description of the updated subscription. - - Raises: - APIError: on an API server error. - ClientError: on a client error. - """ return self._client.call_sync( self._client.patch_subscription(subscription_id, request)) + @docstring(SubscriptionsClient.get_subscription.__doc__) def get_subscription(self, subscription_id: str) -> Dict[str, Any]: - """Get a description of a Subscription. - - Args: - subscription_id (str): id of a subscription. - - Returns: - dict: description of the subscription. - - Raises: - APIError: on an API server error. - ClientError: on a client error. - """ return self._client.call_sync( self._client.get_subscription(subscription_id)) + @docstring(SubscriptionsClient.get_results.__doc__) def get_results( self, subscription_id: str, @@ -509,28 +430,6 @@ def get_results( "success"]]] = None, limit: int = 100, ) -> Iterator[Union[Dict[str, Any], str]]: - """Iterate over results of a Subscription. - - Notes: - The name of this method is based on the API's method name. This - method provides iteration over results, it does not get a - single result description or return a list of descriptions. - - Parameters: - subscription_id (str): id of a subscription. - status (Set[str]): pass result with status in this set, - filter out results with status not in this set. - limit (int): limit the number of subscriptions in the - results. When set to 0, no maximum is applied. - TODO: created, updated, completed, user_id - - Yields: - dict: description of a subscription results. - - Raises: - APIError: on an API server error. - ClientError: on a client error. - """ results = self._client.get_results(subscription_id, status, limit) try: @@ -539,6 +438,7 @@ def get_results( except StopAsyncIteration: pass + @docstring(SubscriptionsClient.get_results_csv.__doc__) def get_results_csv( self, subscription_id: str, @@ -548,21 +448,6 @@ def get_results_csv( "failed", "success"]]] = None ) -> Iterator[str]: - """Iterate over rows of results CSV for a Subscription. - - Parameters: - subscription_id (str): id of a subscription. - status (Set[str]): pass result with status in this set, - filter out results with status not in this set. - TODO: created, updated, completed, user_id - - Yields: - str: a row from a CSV file. - - Raises: - APIError: on an API server error. - ClientError: on a client error. - """ results = self._client.get_results_csv(subscription_id, status) # Note: retries are not implemented yet. This project has # retry logic for HTTP requests, but does not handle errors