Repository: planetlabs/planet-client-python
Pull request #1092 (2025-01-16 16:10:00)

Housekeeping: Add GEE delivery option for Subscription, OCS delivery option for Orders, remove PS2 target sensor for harmonize tool
**Related Issue(s):**
N/A

Closes #
N/A

**Proposed Changes:**

For inclusion in changelog (if applicable):

1. SDK supports GEE delivery for Subscriptions
2. SDK supports OCS delivery for Orders
3. PS2 target sensor removed from harmonize tool (already not supported in the API)

Not intended for changelog:

1. Added tests for new deliveries, added tests for recently added composite tool group by parameter, fixed some typos

**Diff of User Interface**

Old behavior: 
1. Couldn't create a subscription with gee delivery
2. Couldn't create an order with ocs delivery
3. Couldn't create an order or sub with harmonize tool using PS2 target sensor (would always fail as it is unsupported in both APIs)

New behavior:
1. Can create a subscription with gee delivery
2. Can create an order with ocs delivery
3. No option to use PS2 as target sensor for harmonize tool



**PR Checklist:**

- [X] This PR is as small and focused as possible
- [X] If this PR includes proposed changes for inclusion in the changelog, the title of this PR summarizes those changes and is ready for inclusion in the Changelog.
- [X] I have updated docstrings for function changes and docs in the 'docs' folder for user interface / behavior changes
- [X] This PR does not break any examples or I have updated them

**(Optional) @mentions for Notifications:**



Patch:
diff --git a/planet/order_request.py b/planet/order_request.py
index 861f684..fd59685 100644
--- a/planet/order_request.py
+++ b/planet/order_request.py
@@ -320,6 +320,38 @@ def google_earth_engine(project: str, collection: str) -> dict:
     return {'google_earth_engine': cloud_details}
 
 
+def oracle_cloud_storage(customer_access_key_id: str,
+                         customer_secret_key: str,
+                         bucket: str,
+                         region: str,
+                         namespace: str,
+                         path_prefix: Optional[str] = None) -> dict:
+    """Oracle Cloud Storage configuration.
+
+    Parameters:
+        customer_access_key_id: Customer Access Key credentials.
+        customer_secret_key: Customer Secret Key credentials.
+        bucket: The name of the bucket that will receive the order output.
+        region: The region where the bucket lives in Oracle.
+        namespace: Object Storage namespace name.
+        path_prefix: Custom string to prepend to the files delivered to the
+            bucket. A slash (/) character will be treated as a "folder".
+            Any other characters will be added as a prefix to the files.
+    """
+    cloud_details = {
+        'customer_access_key_id': customer_access_key_id,
+        'customer_secret_key': customer_secret_key,
+        'bucket': bucket,
+        'region': region,
+        'namespace': namespace
+    }
+
+    if path_prefix:
+        cloud_details['path_prefix'] = path_prefix
+
+    return {'oracle_cloud_storage': cloud_details}
+
+
 def _tool(name: str, parameters: dict) -> dict:
     """Create the API spec representation of a tool.
 
@@ -380,11 +412,14 @@ def composite_tool(group_by: Optional[str] = None) -> dict:
     Parameters:
         group_by: (Optional) Defines how items are grouped to create one or more composites.
                   Supported values are:
-                  - "order": All input items are used to create a single composite output.
+                  - "order": All input items are used to create a single composite output. This is the default behavior.
                   - "strip_id": Input items are grouped by their strip_id to create multiple composites.
 
     Returns:
         dict: A dictionary representing the composite tool configuration.
+
+    Raises:
+        planet.exceptions.ClientError: If group_by is not one of "order" or "strip_id".
     """
     if group_by and group_by not in ["order", "strip_id"]:
         raise ClientError(
diff --git a/planet/specs.py b/planet/specs.py
index 93acc11..d14fdcb 100644
--- a/planet/specs.py
+++ b/planet/specs.py
@@ -33,7 +33,7 @@ SUPPORTED_TOOLS = [
 SUPPORTED_ORDER_TYPES = ['full', 'partial']
 SUPPORTED_ARCHIVE_TYPES = ['zip']
 SUPPORTED_FILE_FORMATS = ['COG', 'PL_NITF']
-HARMONIZE_TOOL_TARGET_SENSORS = ('Sentinel-2', 'PS2')
+HARMONIZE_TOOL_TARGET_SENSORS = ['Sentinel-2']
 BAND_MATH_PIXEL_TYPE = ('Auto', '8U', '16U', '16S', '32R')
 BAND_MATH_PIXEL_TYPE_DEFAULT = 'Auto'
 
@@ -112,9 +112,9 @@ def validate_supported_bundles(item_type, bundle, all_product_bundles):
 
     supported_bundles = []
     for product_bundle in all_product_bundles:
-        availible_item_types = set(
+        available_item_types = set(
             spec['bundles'][product_bundle]['assets'].keys())
-        if item_type.lower() in [x.lower() for x in availible_item_types]:
+        if item_type.lower() in [x.lower() for x in available_item_types]:
             supported_bundles.append(product_bundle)
 
     return _validate_field(bundle, supported_bundles, 'bundle')
diff --git a/planet/subscription_request.py b/planet/subscription_request.py
index f1d5718..e16fbf5 100644
--- a/planet/subscription_request.py
+++ b/planet/subscription_request.py
@@ -435,6 +435,23 @@ def azure_blob_storage(account: str,
     return _delivery('azure_blob_storage', parameters)
 
 
+def google_earth_engine(project: str, collection: str,
+                        credentials: str) -> dict:
+    """Delivery to Google Earth Engine.
+
+    Parameters:
+        project: GEE project name.
+        collection: GEE Image Collection name.
+        credentials: GEE service account credentials.
+    """
+    parameters = {
+        'project': project,
+        'collection': collection,
+        'credentials': credentials
+    }
+    return _delivery('google_earth_engine', parameters)
+
+
 def google_cloud_storage(credentials: str,
                          bucket: str,
                          path_prefix: Optional[str] = None) -> dict:


Test patch:
diff --git a/tests/unit/test_order_request.py b/tests/unit/test_order_request.py
index b3f0f8a..b87f362 100644
--- a/tests/unit/test_order_request.py
+++ b/tests/unit/test_order_request.py
@@ -240,6 +240,25 @@ def test_google_earth_engine():
     assert gee_config == expected
 
 
+def test_oracle_cloud_storage():
+    ocs_config = order_request.oracle_cloud_storage('ocs_access_key_id',
+                                                    'ocs_secret_access_key',
+                                                    'bucket',
+                                                    'region',
+                                                    'namespace')
+
+    expected = {
+        'oracle_cloud_storage': {
+            'customer_access_key_id': 'ocs_access_key_id',
+            'customer_secret_key': 'ocs_secret_access_key',
+            'bucket': 'bucket',
+            'region': 'region',
+            'namespace': 'namespace'
+        }
+    }
+    assert ocs_config == expected
+
+
 def test__tool():
     test_tool = order_request._tool('bandmath', 'jsonstring')
     assert test_tool == {'bandmath': 'jsonstring'}
@@ -267,6 +286,29 @@ def test_clip_tool_invalid(point_geom_geojson):
         order_request.clip_tool(point_geom_geojson)
 
 
+def test_composite_tool_no_group_by():
+    composite_tool = order_request.composite_tool()
+    expected = {'composite': {}}
+    assert composite_tool == expected
+
+
+def test_composite_tool_group_by_strip_id():
+    composite_tool = order_request.composite_tool(group_by='strip_id')
+    expected = {'composite': {'group_by': 'strip_id'}}
+    assert composite_tool == expected
+
+
+def test_composite_tool_group_by_order():
+    composite_tool = order_request.composite_tool(group_by='order')
+    expected = {'composite': {'group_by': 'order'}}
+    assert composite_tool == expected
+
+
+def test_composite_tool_group_by_invalid():
+    with pytest.raises(exceptions.ClientError):
+        order_request.composite_tool(group_by='invalid')
+
+
 def test_reproject_tool():
     rt = order_request.reproject_tool(projection='proj', resolution=5)
     expected = {'reproject': {'projection': 'proj', 'resolution': 5}}
diff --git a/tests/unit/test_subscription_request.py b/tests/unit/test_subscription_request.py
index 2aa12f0..6238b92 100644
--- a/tests/unit/test_subscription_request.py
+++ b/tests/unit/test_subscription_request.py
@@ -307,6 +307,21 @@ def test_azure_blob_storage_path_prefix_success():
     }
 
 
+def test_google_earth_engine_success():
+    res = subscription_request.google_earth_engine(project='project',
+                                                   collection='collection',
+                                                   credentials='cred')
+
+    assert res == {
+        "type": "google_earth_engine",
+        "parameters": {
+            "project": "project",
+            "collection": "collection",
+            "credentials": "cred"
+        }
+    }
+
+
 def test_google_cloud_storage_success():
     res = subscription_request.google_cloud_storage(credentials='cred',
                                                     bucket='bucket')