diff --git a/CHANGES.txt b/CHANGES.txt index 0e5970a70..fb8f86469 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,9 @@ +2.0b2 (TBD) + +Added: +- The Session class can now construct clients by name with its client method + (#858). + 2.0.0-beta.1 (2022-12-07) Changed: diff --git a/docs/get-started/upgrading.md b/docs/get-started/upgrading.md index 7edf21f66..a82fafa2d 100644 --- a/docs/get-started/upgrading.md +++ b/docs/get-started/upgrading.md @@ -24,7 +24,7 @@ The best way of doing this is wrapping any code that invokes a client class in a ```python async with Session() as session: - client = OrdersClient(session) + client = session.client('orders') result = await client.create_order(order) # Process result ``` @@ -40,12 +40,12 @@ In V2, all `*Client` methods (for example, `DataClient().search`, `OrderClient() ```python import asyncio from datetime import datetime -from planet import Session, DataClient +from planet import Session from planet import data_filter as filters async def do_search(): async with Session() as session: - client = DataClient(session) + client = session.client('data') date_filter = filters.date_range_filter('acquired', gte=datetime.fromisoformat("2022-11-18"), lte=datetime.fromisoformat("2022-11-21")) cloud_filter = filters.range_filter('cloud_cover', lte=0.1) download_filter = filters.permission_filter() @@ -74,11 +74,11 @@ Is now ```python async with Session() as session: - items = [i async for i in planet.DataClient(session).search(["PSScene"], all_filters)] + items = [i async for i in session.client('data').search(["PSScene"], all_filters)] ``` ## Orders API -The Orders API capabilities in V1 were quite primitive, but those that did exist have been retained in much the same form; `ClientV1().create_order` becomes `OrderClient(session).create_order`. (As with the `DataClient`, you must also use `async` and `Session` with `OrderClient`.) +The Orders API capabilities in V1 were quite primitive, but those that did exist have been retained in much the same form; `ClientV1().create_order` becomes `OrdersClient(session).create_order`. (As with the `DataClient`, you must also use `async` and `Session` with `OrdersClient`.) Additionally, there is now also an order builder in `planet.order_request`, similar to the preexisting search filter builder. For more details on this, refer to the [Creating an Order](../../python/sdk-guide/#creating-an-order). diff --git a/docs/python/sdk-guide.md b/docs/python/sdk-guide.md index 4e1ac2c02..ba7a0e07e 100644 --- a/docs/python/sdk-guide.md +++ b/docs/python/sdk-guide.md @@ -116,7 +116,7 @@ from planet import OrdersClient async def main(): async with Session() as sess: - client = OrdersClient(sess) + client = sess.client('orders') # perform operations here asyncio.run(main()) @@ -198,7 +198,7 @@ the context of a `Session` with the `OrdersClient`: ```python async def main(): async with Session() as sess: - cl = OrdersClient(sess) + cl = sess.client('orders') order = await cl.create_order(request) asyncio.run(main()) @@ -222,7 +222,7 @@ from planet import reporting async def create_wait_and_download(): async with Session() as sess: - cl = OrdersClient(sess) + cl = sess.client('orders') with reporting.StateBar(state='creating') as bar: # create order order = await cl.create_order(request) @@ -272,7 +272,7 @@ from planet import collect, OrdersClient, Session async def main(): async with Session() as sess: - client = OrdersClient(sess) + client = sess.client('orders') orders_list = collect(client.list_orders()) asyncio.run(main()) @@ -297,7 +297,7 @@ from planet import DataClient async def main(): async with Session() as sess: - client = DataClient(sess) + client = sess.client('data') # perform operations here asyncio.run(main()) @@ -344,7 +344,7 @@ the context of a `Session` with the `DataClient`: ```python async def main(): async with Session() as sess: - cl = DataClient(sess) + cl = sess.client('data') items = [i async for i in cl.search(['PSScene'], sfilter)] asyncio.run(main()) @@ -364,7 +364,7 @@ print command to report wait status. `download_asset` has reporting built in. ```python async def download_and_validate(): async with Session() as sess: - cl = DataClient(sess) + cl = sess.client('data') # get asset description item_type_id = 'PSScene' diff --git a/planet/__init__.py b/planet/__init__.py index 6f081a6e8..fcaf68200 100644 --- a/planet/__init__.py +++ b/planet/__init__.py @@ -16,7 +16,7 @@ from . import order_request, reporting from .__version__ import __version__ # NOQA from .auth import Auth -from .clients import DataClient, OrdersClient # NOQA +from .clients import DataClient, OrdersClient, SubscriptionsClient # NOQA from .io import collect __all__ = [ @@ -24,6 +24,7 @@ 'collect', 'DataClient' 'OrdersClient', + 'SubscriptionsClient', 'order_request', 'reporting', 'Session', diff --git a/planet/clients/__init__.py b/planet/clients/__init__.py index 9fb246c9f..138726a7e 100644 --- a/planet/clients/__init__.py +++ b/planet/clients/__init__.py @@ -12,10 +12,20 @@ # 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 __all__ = [ 'DataClient', 'OrdersClient', + 'SubscriptionsClient', ] + +# Organize client classes by their module name to allow concise lookup. +_client_directory = { + 'data': DataClient, + 'orders': OrdersClient, + 'subscriptions': SubscriptionsClient +} diff --git a/planet/http.py b/planet/http.py index 97983c07d..0f649ea5e 100644 --- a/planet/http.py +++ b/planet/http.py @@ -22,7 +22,9 @@ import random import time from typing import AsyncGenerator, Optional + import httpx +from typing_extensions import Literal from .auth import Auth, AuthType from . import exceptions, models @@ -413,6 +415,29 @@ async def stream( finally: await response.aclose() + def client(self, + name: Literal['data', 'orders', 'subscriptions'], + base_url: Optional[str] = None) -> object: + """Get a client by its module name. + + Parameters: + name: one of 'data', 'orders', or 'subscriptions'. + + Returns: + A client instance. + + Raises: + ClientError when no such client can be had. + + """ + # To avoid circular dependency. + from planet.clients import _client_directory + + try: + return _client_directory[name](self, base_url=base_url) + except KeyError: + raise exceptions.ClientError("No such client.") + class AuthSession(BaseSession): """Synchronous connection to the Planet Auth service.""" diff --git a/setup.py b/setup.py index 125515461..85b94f932 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ 'jsonschema', 'pyjwt>=2.1', 'tqdm>=4.56', + 'typing-extensions', ] test_requires = ['pytest', 'pytest-asyncio==0.16', 'pytest-cov', 'respx==0.19'] diff --git a/tests/unit/test_session.py b/tests/unit/test_session.py new file mode 100644 index 000000000..bf5baca2c --- /dev/null +++ b/tests/unit/test_session.py @@ -0,0 +1,23 @@ +"""Session module tests.""" + +import pytest + +from planet import DataClient, OrdersClient, SubscriptionsClient, Session +from planet.exceptions import ClientError + + +@pytest.mark.parametrize("client_name,client_class", + [('data', DataClient), ('orders', OrdersClient), + ('subscriptions', SubscriptionsClient)]) +def test_session_get_client(client_name, client_class): + """Get a client from a session.""" + session = Session() + client = session.client(client_name) + assert isinstance(client, client_class) + + +def test_session_get_client_error(): + """Get an exception when no such client exists.""" + session = Session() + with pytest.raises(ClientError): + _ = session.client('bogus')