Async SDK User Guide
This guide is for the Planet Async SDK for Python users who want to use asynchronous Python code to search, order, customize, and deliver Planet imagery and data. If you’re new to Python, you may want to choose the no-code option of using the command-line interface (CLI), or the non-async Planet Python SDK. If you're ready to get started using Planet data in your asyncio-based applications, read ahead.
This guide walks you through the steps:
- Authenticate—authenticate to Planet services to verify your permissions to data.
- Create a session—set up a context for calling on Planet servers and receiving data back.
- Create an order—build an orders client, send the request within the session context, and download it when it’s ready.
- Collect and list data—handle the potentially large number of results from a search for imagery.
- Query the data catalog—search the catalog based on a filter, activate the assets you want, and download and validate it when it’s ready.
Install the Planet Python SDK¶
Use a package manager (such as pip) to install the Planet Python SDK:
pip install planet
Authenticate with Planet services¶
An SDK Session requires authentication to communicate with Planet services. The
details of authentication are managed with the planet.Auth
class, and are configured when a Session is created. Default behavior
shares the responsibility of managing authentication sessions with the planet auth
CLI utility, which stores authentication sessions in the user's home directory.
The default authentication behavior of the Session can be modified by providing an
Auth instance when creating the Session. There are many different ways to
create an Auth instance, depending on the use case. See
Client Authentication Overview
and Authentication with the SDK for more details concerning
Planet Insights Platform authentication with the SDK. For general information on
how to authenticate to Planet APIs, please see the
Authentication section of Planet's
platform documentation.
For example, a program may wish create the Auth instance prior to setting up
the Session to guide the user towards external setup steps:
import asyncio
import sys
from planet import Auth, Session
auth = Auth.from_user_default_session()
async def main():
if not auth.is_initialized():
print("Login required. Execute the following command:\n\n\tplanet auth login\n")
sys.exit(99)
async with Session(auth=auth) as sess:
# perform operations here
pass
asyncio.run(main())
Create your first session¶
Communication with the Planet services is provided by way of the Session class. The Session class automatically implements rate limiting that allows for smooth asynchronous communication with Planet servers. Note that this rate-limiting only works when one Session is being used. Employing multiple sessions will cause the rate-limiting to be bypassed and can cause collisions.
To use Session, it’s recommended to use it as a context manager. In this way, your session is clearly defined and is handled as part of the context. For example, the context manager handles automatic cleanup of connections when the context is left, such as when an exception occurs.
import asyncio
import os
from planet import Session
async def main():
async with Session() as sess:
# perform operations here
pass
asyncio.run(main())
Alternatively, you may need to manually manage the lifecycle of the session, for example to reuse it across multiple asynchronous functions. In this case, you may consider using await Session.aclose() and close that Session explicitly:
async def main():
sess = Session()
# perform operations here
await sess.aclose()
asyncio.run(main())
Use asyncio to order Planet data¶
As noted above, to ensure your session is properly managed and cleaned up when it’s no longer needed, create a session using the Session class and use it as a context manager.
The proper implementation of the Session class:
- uses it as a context manager, with rate limiting which allows for smooth asynchronous communication with Planet servers
- works with one
Sessionat a time—multiple sessions bypass rate-limiting and can cause collisions
Caution
Do not use multiple Sessions as it can cause collisions and bypass rate-limiting.
For an example of using asyncio to order Planet data with a rate-limited session, you can see in the Create and download multiple orders example. The main function in that example creates a client session and pulls in multiple request objects (an area of interest in Iowa and another in Oregon that were created in a create_requests() function). The requests are dynamically generated and queued. Finally, orders are delivered into your preferred location (using a create_and_download() function).
import asyncio
import os
import planet
.
.
.
async def main():
async with planet.Session() as sess:
client = sess.client('orders')
requests = create_requests()
await asyncio.gather(*[
create_and_download(client, request, DOWNLOAD_DIR)
for request in requests
])
if __name__ == '__main__':
asyncio.run(main())
Create an order¶
The Orders Client mostly mirrors the Planet Orders API. This SDK provides additional abilities, for example to poll for order completion and to download an entire order.
Create an order request¶
As a first step in ordering, you create an order request object. This request object is transmitted to the Planet service as a JSON object. The SDK provides a way for you to build up that object: planet.order_request.build_request(). The following code returns an order request object, with the values you’ve provided for:
- a name for your order
- what product to order—in this example,
PSSceneitems withanalytic_udm2product bundle asset types - what tools to use—here, the clip tool with the area of interest (AOI) to clip within
def create_request():
# This is your area of interest for the Orders clipping tool
oregon_aoi = {
"type":
"Polygon",
"coordinates": [[[-117.558734, 45.229745], [-117.452447, 45.229745],
[-117.452447, 45.301865], [-117.558734, 45.301865],
[-117.558734, 45.229745]]]
}
# In practice, you will use a Data API search to find items, but
# for this example take them as given.
oregon_items = ['20200909_182525_1014', '20200909_182524_1014']
oregon_order = planet.order_request.build_request(
name='oregon_order',
products=[
planet.order_request.product(item_ids=oregon_items,
product_bundle='analytic_8b_udm2',
item_type='PSScene'
fallback_bundle='analytic_udm2,analytic_3b_udm2')
],
tools=[planet.order_request.clip_tool(aoi=oregon_aoi)])
return oregon_order
This would be equivalent to a manually created JSON object with the following description:
{
"name":"oregon_order”,
"products":[
{
"Item_ids":["20200909_182525_1014",
"20200909_182524_1014"],
"item_type":"PSScene",
"product_bundle":"analytic_8b_udm2,analytic_udm2,analytic_3b_udm2"
}
],
"tools": [
{
"clip": {
"aoi": {
"type": "Polygon",
"coordinates": [
[[-117.558734, 45.229745], [-117.452447, 45.229745],
[-117.452447, 45.301865], [-117.558734, 45.301865],
[-117.558734, 45.229745]]
]
}
}
}
]
}
Once the order request is built, create an order within the context of a Session with the OrdersClient create_order() function and pass the order request object in:
async def main():
async with Session() as sess:
cl = sess.client('orders')
order = await cl.create_order(request)
asyncio.run(main())
So given the order object created with the call to create_request(), above, the following code creates an Orders API client and uses that client to create and order.
async def main():
# Create a session and client
# The Orders API client is also a subclass of the Session
# class, so it has all the same methods.
async with planet.Session() as sess:
# 'orders' is the service name for the Orders API.
cl = sess.client('orders')
request = create_request()
order = await cl.create_order(request)
If you run the code now, it appears as if nothing happens, because the order is being created and more importantly because you haven’t described where the order should be delivered to.
At this point, you can go into your account dashboard and select My Orders to see the order. And if the order is finished, you can select “download” (the default delivery mechanism) to view the order, see a preview of the clipping tool, and download the assets.
But of course, the point is to use the SDK to also deliver the ordered assets to a specified location automatically, which the next section describes.
Waiting and downloading an order¶
After creating an order client, there is typically a waiting period before the assets can be downloaded. During this time, the order is being processed and customized according to the specifications provided in the order request. To monitor the progress of the order creation process and determine when the assets are ready for download, you can use the wait method provided by the Orders API client.
With wait and download, it is often desired to track progress as these
processes can take a long time. To track the progress of the order, the following example code uses a progress bar from the reporting module to report the wait status. The download_order method has built-in reporting capabilities, so we don’t need to use a progress bar for the download process.
from planet import reporting
async def create_wait_and_download():
async with Session() as sess:
cl = sess.client('orders')
with reporting.StateBar(state='creating') as bar:
# create order
order = await cl.create_order(request)
bar.update(state='created', order_id=order['id'])
# poll
await cl.wait(order['id'], callback=bar.update_state)
# download
await cl.download_order(order['id'])
asyncio.run(create_poll_and_download())
Following on the the request object you created with the create_request(), above, the following function provides the status as you wait for the file to be downloaded to the current directory:
async def create_and_download(client, order_detail, directory):
with planet.reporting.StateBar(state='creating') as reporter:
order = await client.create_order(order_detail)
reporter.update(state='created', order_id=order['id'])
await client.wait(order['id'], callback=reporter.update_state)
await client.download_order(order['id'], directory, progress_bar=True)
async def main():
async with planet.Session() as sess:
cl = sess.client('orders')
# Create the order request
request = create_request()
# Create and download the order
order = await create_and_download(cl, request, DOWNLOAD_DIR)
Now, instead of going to your account dashboard to see the order request running, you can see the status as output on your output. For example:
02:06 - order [id number] - state: running
And when your order is ready, it will download into the current directory, while writing status to the output:
order-id/PSScene/20200909_182524_1014_metadata.json: 100%|█| 0.00k/0.00k [0
order-id/PSScene/20200909_182524_1014_3B_AnalyticMS_metadata_clip.xml: 100%
order-id/PSScene/20200909_182524_1014_3B_udm2_clip.tif: 100%|█| 0.55k/0.55k
order-id/PSScene/20200909_182524_1014_3B_AnalyticMS_clip.tif: 100%|█| 25.5k
order-id/PSScene/20200909_182525_1014_metadata.json: 100%|█| 0.00k/0.00k [0
order-id/PSScene/20200909_182525_1014_3B_AnalyticMS_metadata_clip.xml: 100%
order-id/PSScene/20200909_182525_1014_3B_udm2_clip.tif: 100%|█| 0.52k/0.52k
order-id/PSScene/20200909_182525_1014_3B_AnalyticMS_clip.tif: 100%|█| 27.1k
order-id/manifest.json: 100%|██████████| 0.00k/0.00k [00:00<00:00, 788kB/s]
Validating checksums¶
Checksum validation provides for verification that the files in an order have been downloaded successfully and are not missing, corrupted, or changed. This functionality is included in the OrderClient, but does not require an instance of the class to be used.
To perform checksum validation:
from pathlib import Path
# path includes order id
order_path = Path('193e5bd1-dedc-4c65-a539-6bc70e55d928')
OrdersClient.validate_checksum(order_path, 'md5')
Collecting results¶
Some API calls, such as searching for imagery and listing orders, return a
varying, and potentially large, number of results. These API responses are
paged. The SDK manages paging internally and the associated client commands
return an asynchronous iterator over the results. These results can be
converted to a JSON blob using the collect command. When the results
represent GeoJSON features, the JSON blob is a GeoJSON FeatureCollection.
Otherwise, the JSON blob is a list of the individual results.
import asyncio
from planet import collect, Session
async def main():
async with Session() as sess:
client = sess.client('orders')
orders_list = collect(client.list_orders())
asyncio.run(main())
Alternatively, these results can be converted to a list directly with
orders_list = [o async for o in client.list_orders()]
Query the data catalog¶
The Data Client mostly mirrors the Data API, with the only difference being the addition of functionality to activate an asset, poll for when activation is complete, and download the asset.
async def main():
async with Session() as sess:
client = sess.client('data')
# perform operations here
asyncio.run(main())
Filter¶
When performing a quick search, creating or updating a saved search, or
requesting stats, the data search filter must be provided to the API
as a JSON blob. This JSON blob can be built up manually or by using the
data_filter module.
An example of creating the request JSON with data_filter:
from datetime import datetime
from planet import data_filter
sfilter = data_filter.and_filter([
data_filter.permission_filter(),
data_filter.date_range_filter('acquired', gt=datetime(2022, 6, 1, 1))
])
The same thing, expressed as a JSON blob:
sfilter = {
'type': 'AndFilter',
'config': [
{'type': 'PermissionFilter', 'config': ['assets:download']},
{
'type': 'DateRangeFilter',
'field_name': 'acquired',
'config': {'gt': '2022-06-01T01:00:00Z'}
}
]
}
Once the filter is built up, performing a search is done within
the context of a Session with the DataClient:
async def main():
async with Session() as sess:
cl = sess.client('data')
items = [i async for i in cl.search(['PSScene'], sfilter)]
asyncio.run(main())
Downloading an asset¶
Downloading an asset is a multi-step process involving: activating the asset, waiting for the asset to be active, downloading the asset, and, optionally, validating the downloaded file.
With wait and download, it is often desired to track progress as these
processes can take a long time. Therefore, in this example, we use a simple
print command to report wait status. download_asset has reporting built in.
async def download_and_validate():
async with Session() as sess:
cl = sess.client('data')
# get asset description
item_type_id = 'PSScene'
item_id = '20221003_002705_38_2461'
asset_type_id = 'ortho_analytic_4b'
asset = await cl.get_asset(item_type_id, item_id, asset_type_id)
# activate asset
await cl.activate_asset(asset)
# wait for asset to become active
asset = await cl.wait_asset(asset, callback=print)
# download asset
path = await cl.download_asset(asset)
# validate download file
cl.validate_checksum(asset, path)
Features API Collections and Features¶
The Python SDK now supports Features API Collections and Features (note: in the SDK and API, Features are often referred to as items in a collection).
Collections and Features/items that you create in in the SDK will be visible in Features API and Features Manager.
Creating a collection¶
You can use the Python SDK to create Features API collections.
async with Session() as sess:
client = FeaturesClient(sess)
new_collection = await client.create_collection(title="my collection", description="a new collection")
Listing collections¶
async with Session() as sess:
client = FeaturesClient(sess)
collections = client.list_collections()
async for collection in collections:
print(collection)
Listing features/items in a collection¶
async with Session() as sess:
client = FeaturesClient(sess)
items = client.list_items(collection_id)
async for item in items:
print(items)
Using items as geometries for other methods¶
You can pass collection items/features directly to other SDK methods. Any method that requires a geometry will accept a Features API Feature.
async with Session() as sess:
client = FeaturesClient(sess)
items = client.list_items(collection_id)
example_feature = await anext(items)
data_client = DataClient(sess)
results = data_client.search(["PSScene"], geometry=example_feature, limit=1)
async for result in results:
print(result)
API Exceptions¶
When errors occur, the Planet SDK for Python exception hierarchy is as follows:
- All exceptions inherit from the base exception called
PlanetError. - Client-side errors are raised as
ClientError. - Server-side errors are raised as specific exceptions based on the http code. These specific exceptions all inherit from
APIErrorand contain the original error message returned by the server.