Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
10 changes: 5 additions & 5 deletions docs/get-started/upgrading.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I mean by "centers the Session class". Creating one is the first thing we do and it's the manager for the context of a block of code.

client = OrdersClient(session)
client = session.client('orders')
result = await client.create_order(order)
# Process result
```
Expand All @@ -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()
Expand Down Expand Up @@ -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).
14 changes: 7 additions & 7 deletions docs/python/sdk-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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())
Expand All @@ -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)
Expand Down Expand Up @@ -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())
Expand All @@ -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())
Expand Down Expand Up @@ -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())
Expand All @@ -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'
Expand Down
3 changes: 2 additions & 1 deletion planet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@
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__ = [
'Auth',
'collect',
'DataClient'
'OrdersClient',
'SubscriptionsClient',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found that this was missing!

'order_request',
'reporting',
'Session',
Expand Down
2 changes: 2 additions & 0 deletions planet/clients/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
# limitations under the License.
from .data import DataClient
from .orders import OrdersClient
from .subscriptions import SubscriptionsClient

__all__ = [
'DataClient',
'OrdersClient',
'SubscriptionsClient',
]
31 changes: 31 additions & 0 deletions planet/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -413,6 +415,35 @@ 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'.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty nice, I think. A user is told that they should pass 'data', 'orders', or 'subscription' as the argument.

Screenshot from 2023-02-27 10-44-36

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This typing hint IS nice! This would be great to use this for all of the other arguments that have a set number of 'valid' entries, we already have them defined either at the top of the client module or in the specs module.


Returns:
A client instance.

Raises:
ClientError when no such client can be had.

"""
# To avoid circular dependency.
from planet.clients.data import DataClient
from planet.clients.orders import OrdersClient
from planet.clients.subscriptions import SubscriptionsClient
Copy link
Contributor Author

@sgillies sgillies Feb 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Session and clients aren't fully decoupled because the client modules import http. We could fix that in the future if it became important to do so. For now, we can tolerate some imports here. They don't have a significant impact on performance. By the time this method is called, those modules will already have been loaded.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to file a ticket for fixing that in the future?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not now. I'm not sure it's important. I changed the text above to reflect that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: the client modules import http in order to use http.Session in their type hints.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that!


try:
return {
'data': DataClient,
'orders': OrdersClient,
'subscriptions': SubscriptionsClient
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I'm statically configuring the mapping between names and clients here. Getting too fancy would be a mistake. We have no plans to have multiple clients per API or support user-supplied client plugins.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at my PR as if I was a reviewer, it occurs to me that this static mapping could be moved to planet/client/__init__.py. That would let us reduce those 3 imports to 1.

}[name](self, base_url=base_url)
except KeyError:
raise exceptions.ClientError("No such client.")


class AuthSession(BaseSession):
"""Synchronous connection to the Planet Auth service."""
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
'jsonschema',
'pyjwt>=2.1',
'tqdm>=4.56',
'typing-extensions',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module provides a backport of Literal for Python 3.7.

]

test_requires = ['pytest', 'pytest-asyncio==0.16', 'pytest-cov', 'respx==0.19']
Expand Down
23 changes: 23 additions & 0 deletions tests/unit/test_session.py
Original file line number Diff line number Diff line change
@@ -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')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage of the new code is 100%.