1313# the License.
1414"""Functionality for preparing subscription requests."""
1515from datetime import datetime
16- from typing import Any , Dict , Optional , List
16+ from typing import Any , Dict , Optional , List , Mapping
1717
1818from . import geojson , specs
1919from .exceptions import ClientError
4545
4646
4747def build_request (name : str ,
48- source : dict ,
49- delivery : dict ,
50- notifications : Optional [dict ] = None ,
51- tools : Optional [List [dict ]] = None ) -> dict :
52- """Prepare a subscriptions request.
48+ source : Mapping ,
49+ delivery : Mapping ,
50+ notifications : Optional [Mapping ] = None ,
51+ tools : Optional [List [Mapping ]] = None ,
52+ clip_to_source = False ) -> dict :
53+ """Construct a Subscriptions API request.
5354
55+ The return value can be passed to
56+ [planet.clients.subscriptions.SubscriptionsClient.create_subscription][].
5457
58+ Parameters:
59+ name: Name of the subscription.
60+ source: A source for the subscription, i.e. catalog.
61+ delivery: A delivery mechanism e.g. GCS, AWS, Azure, or OCS.
62+ notifications: Specify notifications via email/webhook.
63+ tools: Tools to apply to the products. The order of operation
64+ is determined by the service.
65+ clip_to_source: whether to clip to the source geometry or not
66+ (the default). If True a clip configuration will be added to
67+ the list of requested tools unless an existing clip tool
68+ exists. NOTE: Not all data layers support clipping, please
69+ consult the Product reference before using this option.
70+ NOTE: the next version of the Subscription API will remove
71+ the clip tool option and always clip to the source geometry.
72+ Thus this is a preview of the next API version's default
73+ behavior.
74+
75+ Returns:
76+ A Python dict representation of a Subscriptions API request for
77+ a new subscription.
78+
79+ Raises:
80+ ClientError when a valid Subscriptions API request can't be
81+ constructed.
82+
83+ Examples:
5584 ```python
5685 >>> from datetime import datetime
5786 >>> from planet.subscription_request import (
@@ -72,36 +101,54 @@ def build_request(name: str,
72101 ... ACCESS_KEY_ID, SECRET_ACCESS_KEY, "test", "us-east-1")
73102 ...
74103 >>> subscription_request = build_request(
75- ... 'test_subscription', source, delivery)
104+ ... 'test_subscription', source=source, delivery= delivery)
76105 ...
77106
78107 ```
79-
80- Parameters:
81- name: Name of the subscription.
82- source: A source for the subscription, i.e. catalog.
83- delivery: A delivery mechanism e.g. GCS, AWS, Azure, or OCS.
84- notifications: Specify notifications via email/webhook.
85- tools: Tools to apply to the products. Order defines
86- the toolchain order of operatations.
87108 """
88- details = {"name" : name , "source" : source , "delivery" : delivery }
109+ # Because source and delivery are Mappings we must make copies for
110+ # the function's return value. dict() shallow copies a Mapping
111+ # and returns a new dict.
112+ details = {
113+ "name" : name , "source" : dict (source ), "delivery" : dict (delivery )
114+ }
89115
90116 if notifications :
91- details ['notifications' ] = notifications
117+ details ['notifications' ] = dict ( notifications )
92118
93119 if tools :
94- details ['tools' ] = tools
120+ tool_list = [dict (tool ) for tool in tools ]
121+
122+ # If clip_to_source is True a clip configuration will be added
123+ # to the list of requested tools unless an existing clip tool
124+ # exists. In that case an exception is raised. NOTE: the next
125+ # version of the Subscription API will remove the clip tool
126+ # option and always clip to the source geometry. Thus this is a
127+ # preview of the next API version's default behavior.
128+ if clip_to_source :
129+ if any (tool .get ('type' , None ) == 'clip' for tool in tool_list ):
130+ raise ClientError (
131+ "clip_to_source option conflicts with a configured clip tool."
132+ )
133+ else :
134+ tool_list .append ({
135+ 'type' : 'clip' ,
136+ 'parameters' : {
137+ 'aoi' : source ['parameters' ]['geometry' ]
138+ }
139+ })
140+
141+ details ['tools' ] = tool_list
95142
96143 return details
97144
98145
99146def catalog_source (
100147 item_types : List [str ],
101148 asset_types : List [str ],
102- geometry : dict ,
149+ geometry : Mapping ,
103150 start_time : datetime ,
104- filter : Optional [dict ] = None ,
151+ filter : Optional [Mapping ] = None ,
105152 end_time : Optional [datetime ] = None ,
106153 rrule : Optional [str ] = None ,
107154) -> dict :
@@ -142,7 +189,7 @@ def catalog_source(
142189 parameters = {
143190 "item_types" : item_types ,
144191 "asset_types" : asset_types ,
145- "geometry" : geojson .as_geom (geometry ),
192+ "geometry" : geojson .as_geom (dict ( geometry ) ),
146193 }
147194
148195 try :
@@ -151,7 +198,7 @@ def catalog_source(
151198 raise ClientError ('Could not convert start_time to an iso string' )
152199
153200 if filter :
154- parameters ['filter' ] = filter
201+ parameters ['filter' ] = dict ( filter )
155202
156203 if end_time :
157204 try :
@@ -348,7 +395,7 @@ def band_math_tool(b1: str,
348395 return _tool ('bandmath' , parameters )
349396
350397
351- def clip_tool (aoi : dict ) -> dict :
398+ def clip_tool (aoi : Mapping ) -> dict :
352399 """Specify a subscriptions API clip tool.
353400
354401 Imagery and udm files will be clipped to your area of interest. nodata
@@ -370,12 +417,12 @@ def clip_tool(aoi: dict) -> dict:
370417 """
371418 valid_types = ['Polygon' , 'MultiPolygon' ]
372419
373- geom = geojson .as_geom (aoi )
420+ geom = geojson .as_geom (dict ( aoi ) )
374421 if geom ['type' ].lower () not in [v .lower () for v in valid_types ]:
375422 raise ClientError (
376423 f'Invalid geometry type: { geom ["type" ]} is not in { valid_types } .' )
377424
378- return _tool ('clip' , {'aoi' : aoi })
425+ return _tool ('clip' , {'aoi' : geom })
379426
380427
381428def file_format_tool (file_format : str ) -> dict :
0 commit comments