Repository: planetlabs/planet-client-python
Pull request #984 (2023-07-11 23:46:10)

Release 2.1b1
This is 2.1b1


Patch:
diff --git a/CHANGES.txt b/CHANGES.txt
index 2c6d297..18b7033 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,11 @@
 2.1.0 (TBD)
 
+2.1b1 (2023-07-11)
+
 Added:
+- The request command of the subscriptions CLI has a new --clip-to-source
+  option (#988).
+- A new request-pv command has been added to the subscriptions CLi (#988).
 - Support for catalog source publishing stages (#977) and time range types
   (#978) have been added to subscription_request.catalog_source.
 - Add the option to get Planetary Variable subscription results as a CSV file
diff --git a/planet/__version__.py b/planet/__version__.py
index be7e81e..6fcc825 100644
--- a/planet/__version__.py
+++ b/planet/__version__.py
@@ -1,1 +1,1 @@
-__version__ = '2.1.0dev'
+__version__ = '2.1dev'
diff --git a/planet/cli/subscriptions.py b/planet/cli/subscriptions.py
index 62860c0..f5551e8 100644
--- a/planet/cli/subscriptions.py
+++ b/planet/cli/subscriptions.py
@@ -238,14 +238,32 @@ async def list_subscription_results_cmd(ctx,
     '--tools',
     type=types.JSON(),
     help='Toolchain JSON. Can be a string, filename, or - for stdin.')
+@click.option(
+    '--clip-to-source',
+    is_flag=True,
+    default=False,
+    help="Clip to the source geometry without specifying a clip tool.")
 @pretty
-def request(name, source, delivery, notifications, tools, pretty):
-    """Generate a subscriptions request."""
+def request(name,
+            source,
+            delivery,
+            notifications,
+            tools,
+            clip_to_source,
+            pretty):
+    """Generate a subscriptions request.
+
+    Note: the next version of the Subscription API will remove the clip
+    tool option and always clip to the source geometry. Thus the
+    --clip-to-source option is a preview of the next API version's
+    default behavior.
+    """
     res = subscription_request.build_request(name,
                                              source,
                                              delivery,
                                              notifications=notifications,
-                                             tools=tools)
+                                             tools=tools,
+                                             clip_to_source=clip_to_source)
     echo_json(res, pretty)
 
 
@@ -298,3 +316,49 @@ def request_catalog(item_types,
                                               rrule=rrule,
                                               filter=filter)
     echo_json(res, pretty)
+
+
+@subscriptions.command()  # type: ignore
+@translate_exceptions
+@click.option(
+    '--var-type',
+    required=True,
+    help='Planetary variable type.',
+    type=click.Choice([
+        "biomass_proxy",
+        "land_surface_temperature",
+        "soil_water_content",
+        "vegetation_optical_depth"
+    ]),
+)
+@click.option('--var-id', required=True, help='Planetary variable id.')
+@click.option(
+    '--geometry',
+    required=True,
+    type=types.JSON(),
+    help="""Geometry of the area of interest of the subscription that will be
+    used to determine matches. Can be a string, filename, or - for stdin.""")
+@click.option('--start-time',
+              required=True,
+              type=types.DateTime(),
+              help='Date and time to begin subscription.')
+@click.option('--end-time',
+              type=types.DateTime(),
+              help='Date and time to end subscription.')
+@pretty
+def request_pv(var_type, var_id, geometry, start_time, end_time, pretty):
+    """Generate a Planetary Variable subscription source.
+
+    Planetary Variables come in 4 types and are further subdivided
+    within these types. See [Subscribing to Planetary
+    Variables](https://developers.planet.com/docs/subscriptions/pvs-subs/#planetary-variables-types-and-ids)
+    for details.
+    """
+    res = subscription_request.planetary_variable_source(
+        var_type,
+        var_id,
+        geometry,
+        start_time,
+        end_time=end_time,
+    )
+    echo_json(res, pretty)
diff --git a/planet/subscription_request.py b/planet/subscription_request.py
index 75db02a..ca6bff2 100644
--- a/planet/subscription_request.py
+++ b/planet/subscription_request.py
@@ -81,30 +81,32 @@ def build_request(name: str,
             constructed.
 
     Examples:
+
     ```python
-    >>> from datetime import datetime
-    >>> from planet.subscription_request import (
-    ...     build_request, catalog_source, amazon_s3)
-    ...
-    ... geom = {
-    ...     "coordinates": [[[139.5648193359375,35.42374884923695],
-    ...                     [140.1031494140625,35.42374884923695],
-    ...                     [140.1031494140625,35.77102915686019],
-    ...                     [139.5648193359375,35.77102915686019],
-    ...                     [139.5648193359375,35.42374884923695]]],
-    ...     "type": "Polygon"
-    ... }
-    >>> source = catalog_source(
-    ...     ["PSScene"], ["ortho_analytic_4b"], geom, datetime(2021,3,1))
-
-    >>> delivery = amazon_s3(
-    ...     ACCESS_KEY_ID, SECRET_ACCESS_KEY, "test", "us-east-1")
-    ...
-    >>> subscription_request = build_request(
-    ...     'test_subscription', source=source, delivery=delivery)
-    ...
-
-    ```
+        from datetime import datetime
+        from planet.subscription_request import build_request, catalog_source, amazon_s3
+
+        geom = {
+            "coordinates": [
+                [
+                    [139.5648193359375, 35.42374884923695],
+                    [140.1031494140625, 35.42374884923695],
+                    [140.1031494140625, 35.77102915686019],
+                    [139.5648193359375, 35.77102915686019],
+                    [139.5648193359375, 35.42374884923695],
+                ]
+            ],
+            "type": "Polygon",
+        }
+
+        source = catalog_source(["PSScene"], ["ortho_analytic_4b"], geom, datetime(2021, 3, 1))
+
+        delivery = amazon_s3(ACCESS_KEY_ID, SECRET_ACCESS_KEY, "test", "us-east-1")
+
+        subscription_request = build_request(
+            "test_subscription", source=source, delivery=delivery
+        )
+        ```
     """
     # Because source and delivery are Mappings we must make copies for
     # the function's return value. dict() shallow copies a Mapping
@@ -116,8 +118,8 @@ def build_request(name: str,
     if notifications:
         details['notifications'] = dict(notifications)
 
-    if tools:
-        tool_list = [dict(tool) for tool in tools]
+    if tools or clip_to_source:
+        tool_list = [dict(tool) for tool in (tools or [])]
 
         # If clip_to_source is True a clip configuration will be added
         # to the list of requested tools unless an existing clip tool
@@ -179,6 +181,7 @@ def catalog_source(
             Only monthly recurrences are supported at this time.
         publishing_stages: A sequence of one or more of the values
             "preview", "standard", or "finalized".
+        time_range_type: "acquired" (new in 2.1.0) or "published".
 
     Returns:
         dict: a representation of a subscription source.
@@ -188,28 +191,30 @@ def catalog_source(
             configured.
 
     Examples:
-        ```pycon
-        >>> source = catalog_source(
-        ...     ["PSScene"],
-        ...     ["ortho_analytic_4b"],
-        ...     geometry={
-        ...         "type": "Polygon",
-        ...         "coordinates": [[[37.791595458984375, 14.84923123791421],
-        ...                         [37.90214538574219, 14.84923123791421],
-        ...                         [37.90214538574219, 14.945448293647944],
-        ...                         [37.791595458984375, 14.945448293647944],
-        ...                         [37.791595458984375, 14.84923123791421]]]
-        ...     },
-        ...     start_time=datetime(2021, 3, 1),
-        ...     publishing_stages=["standard"],
-        ...     time_range_type="acquired",
-        ... )
-        >>> request = build_request(
-        ...     "Standard PSScene Ortho Analytic",
-        ...     source=source,
-        ...     delivery={})
-        ```
 
+        ```python
+        source = catalog_source(
+            ["PSScene"],
+            ["ortho_analytic_4b"],
+            geometry={
+                "type": "Polygon",
+                "coordinates": [
+                    [
+                        [37.791595458984375, 14.84923123791421],
+                        [37.90214538574219, 14.84923123791421],
+                        [37.90214538574219, 14.945448293647944],
+                        [37.791595458984375, 14.945448293647944],
+                        [37.791595458984375, 14.84923123791421],
+                    ]
+                ],
+            },
+            start_time=datetime(2021, 3, 1),
+            publishing_stages=["standard"],
+            time_range_type="acquired",
+        )
+
+        request = build_request("Standard PSScene Ortho Analytic", source=source, delivery={})
+        ```
     """
     if len(item_types) > 1:
         raise ClientError(
@@ -297,24 +302,31 @@ def planetary_variable_source(
             configured.
 
     Examples:
+
         ```python
-        >>> source = planetary_variable_source(
-        ...     "soil_water_content",
-        ...     "SWC-AMSR2-C_V1.0_100",
-        ...     geometry={
-        ...         "type": "Polygon",
-        ...         "coordinates": [[[37.791595458984375, 14.84923123791421],
-        ...                         [37.90214538574219, 14.84923123791421],
-        ...                         [37.90214538574219, 14.945448293647944],
-        ...                         [37.791595458984375, 14.945448293647944],
-        ...                         [37.791595458984375, 14.84923123791421]]]
-        ...     },
-        ...     start_time=datetime(2021, 3, 1)
-        ... )
-        >>> request = build_request(
-        ...     "Soil Water Content",
-        ...     source=source,
-        ...     delivery={})
+        pv_source = planetary_variables_source(
+            "soil_water_content",
+            "SWC-AMSR2-C_V1.0_100",
+            geometry={
+                "type": "Polygon",
+                "coordinates": [
+                    [
+                        [37.791595458984375, 14.84923123791421],
+                        [37.90214538574219, 14.84923123791421],
+                        [37.90214538574219, 14.945448293647944],
+                        [37.791595458984375, 14.945448293647944],
+                        [37.791595458984375, 14.84923123791421],
+                    ]
+                ],
+            },
+            start_time=datetime(2021, 3, 1),
+        )
+
+        request = build_request(
+            "soil_water_subscription",
+            source=pv_source,
+            delivery={},
+        )
         ```
     """
     # TODO: validation of variable types and ids.


Test patch:
diff --git a/tests/integration/test_subscriptions_api.py b/tests/integration/test_subscriptions_api.py
index 7046c02..95d9580 100644
--- a/tests/integration/test_subscriptions_api.py
+++ b/tests/integration/test_subscriptions_api.py
@@ -1,5 +1,6 @@
 """Tests of the Planet Subscriptions API client."""
 
+import csv
 from datetime import datetime
 from itertools import zip_longest
 import json
@@ -288,7 +289,6 @@ async def test_get_results_csv():
     async with Session() as session:
         client = SubscriptionsClient(session, base_url=TEST_URL)
         results = [res async for res in client.get_results_csv("42")]
-        import csv
         rows = list(csv.reader(results))
         assert rows == [['id', 'status'], ['1234-abcd', 'SUCCESS']]
 
diff --git a/tests/integration/test_subscriptions_cli.py b/tests/integration/test_subscriptions_cli.py
index 5fe4b42..a1b3cea 100644
--- a/tests/integration/test_subscriptions_cli.py
+++ b/tests/integration/test_subscriptions_cli.py
@@ -252,7 +252,7 @@ def test_subscriptions_results_success(invoke, options, expected_count):
 
 
 def test_request_base_success(invoke, geom_geojson):
-    """Request command succeeds"""
+    """Request command succeeds."""
     source = json.dumps({
         "type": "catalog",
         "parameters": {
@@ -285,6 +285,32 @@ def test_request_base_success(invoke, geom_geojson):
     assert result.exit_code == 0  # success.
 
 
+def test_request_base_clip_to_source(invoke, geom_geojson):
+    """Clip to source using command line option."""
+    source = json.dumps({
+        "type": "catalog",
+        "parameters": {
+            "geometry": geom_geojson,
+            "start_time": "2021-03-01T00:00:00Z",
+            "item_types": ["PSScene"],
+            "asset_types": ["ortho_analytic_4b"]
+        }
+    })
+    result = invoke([
+        'request',
+        '--name=test',
+        f'--source={source}',
+        '--delivery={"type": "fake"}',
+        '--clip-to-source'
+    ])
+
+    assert result.exit_code == 0  # success.
+    req = json.loads(result.output)
+    tool = req["tools"][0]
+    assert tool["type"] == "clip"
+    assert tool["parameters"]["aoi"] == geom_geojson
+
+
 def test_request_catalog_success(invoke, geom_geojson):
     """Request-catalog command succeeds"""
     source = {
@@ -314,3 +340,19 @@ def test_subscriptions_results_csv(invoke):
     result = invoke(['results', 'test', '--csv'])
     assert result.exit_code == 0  # success.
     assert result.output.splitlines() == ['id,status', '1234-abcd,SUCCESS']
+
+
+def test_request_pv_success(invoke, geom_geojson):
+    """Request-pv command succeeds"""
+    result = invoke([
+        'request-pv',
+        '--var-type=biomass_proxy',
+        '--var-id=BIOMASS-PROXY_V3.0_10',
+        f"--geometry={json.dumps(geom_geojson)}",
+        '--start-time=2021-03-01T00:00:00'
+    ])
+
+    assert result.exit_code == 0  # success.
+    source = json.loads(result.output)
+    assert source["type"] == "biomass_proxy"
+    assert source["parameters"]["id"] == "BIOMASS-PROXY_V3.0_10"