Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subscriptions API Hosting Support for Sentinel Hub #1023

Merged
merged 1 commit into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 6 additions & 1 deletion planet/cli/subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,6 @@ async def list_subscription_results_cmd(ctx,
help='Source JSON. Can be a string, filename, or - for stdin.')
@click.option(
'--delivery',
required=True,
type=types.JSON(),
help=("Delivery configuration, including credentials for a cloud "
"storage provider, to enable cloud delivery of data. Can be a "
Expand All @@ -262,6 +261,10 @@ async def list_subscription_results_cmd(ctx,
'--tools',
type=types.JSON(),
help='Toolchain JSON. Can be a string, filename, or - for stdin.')
@click.option(
'--hosting',
type=types.JSON(),
help='Hosting JSON. Can be a string, a filename, or - for stdin.')
@click.option(
'--clip-to-source',
is_flag=True,
Expand All @@ -273,6 +276,7 @@ def request(name,
delivery,
notifications,
tools,
hosting,
clip_to_source,
pretty):
"""Generate a subscriptions request.
Expand All @@ -287,6 +291,7 @@ def request(name,
delivery,
notifications=notifications,
tools=tools,
hosting=hosting,
clip_to_source=clip_to_source)
echo_json(res, pretty)

Expand Down
41 changes: 35 additions & 6 deletions planet/subscription_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@

def build_request(name: str,
source: Mapping,
delivery: Mapping,
delivery: Optional[Mapping] = None,
notifications: Optional[Mapping] = None,
tools: Optional[List[Mapping]] = None,
hosting: Optional[Mapping] = None,
clip_to_source: Optional[bool] = False) -> dict:
"""Construct a Subscriptions API request.

Expand All @@ -65,6 +66,7 @@ def build_request(name: str,
notifications: Specify notifications via email/webhook.
tools: Tools to apply to the products. The order of operation
is determined by the service.
hosting: A hosting destination e.g. Sentinel Hub.
clip_to_source: whether to clip to the source geometry or not
(the default). If True a clip configuration will be added to
the list of requested tools unless an existing clip tool
Expand Down Expand Up @@ -106,17 +108,20 @@ def build_request(name: str,

delivery = amazon_s3(ACCESS_KEY_ID, SECRET_ACCESS_KEY, "test", "us-east-1")

hosting = sentinel_hub("2716077c-191e-4e47-9e3f-01c9c429f88d")

subscription_request = build_request(
"test_subscription", source=source, delivery=delivery
"test_subscription", source=source, delivery=delivery, hosting=hosting
)
```
"""
# Because source and delivery are Mappings we must make copies for
# Because source is a Mapping we must make copies for
# the function's return value. dict() shallow copies a Mapping
# and returns a new dict.
details = {
"name": name, "source": dict(source), "delivery": dict(delivery)
}
details = {"name": name, "source": dict(source)}

if delivery:
details['delivery'] = dict(delivery)

if notifications:
details['notifications'] = dict(notifications)
Expand Down Expand Up @@ -145,6 +150,9 @@ def build_request(name: str,

details['tools'] = tool_list

if hosting:
details['hosting'] = dict(hosting)

return details


Expand Down Expand Up @@ -735,3 +743,24 @@ def cloud_filter_tool(
}

return _tool("cloud_filter", result)


def _hosting(type: str, parameters: dict) -> dict:
return {"type": type, "parameters": parameters}


def sentinel_hub(collection_id: Optional[str]) -> dict:
"""Specify a Sentinel Hub hosting destination.

Requires the user to have a Sentinel Hub account linked with their Planet
account. Subscriptions API will create a new collection to deliver data to
if collection_id is omitted from the request.

Parameters:
collection_id: Sentinel Hub collection
"""

parameters = {}
if collection_id:
parameters['collection_id'] = collection_id
return _hosting("sentinel_hub", parameters)
12 changes: 12 additions & 0 deletions tests/integration/test_subscriptions_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,18 @@ async def test_create_subscription_success():
assert sub['name'] == 'test'


@pytest.mark.anyio
@create_mock
async def test_create_subscription_with_hosting_success():
"""Subscription is created, description has the expected items."""
async with Session() as session:
client = SubscriptionsClient(session, base_url=TEST_URL)
sub = await client.create_subscription({
'name': 'test', 'source': 'test', 'hosting': 'yes, please'
})
assert sub['name'] == 'test'


@pytest.mark.anyio
@failing_api_mock
async def test_cancel_subscription_failure():
Expand Down
7 changes: 6 additions & 1 deletion tests/integration/test_subscriptions_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,16 @@ def test_subscriptions_create_failure(invoke):
# It must be updated when we begin to test against a more strict
# imitation of the Planet Subscriptions API.
GOOD_SUB_REQUEST = {'name': 'lol', 'delivery': True, 'source': 'wut'}
GOOD_SUB_REQUEST_WITH_HOSTING = {
'name': 'lol', 'source': 'wut', 'hosting': True
}


@pytest.mark.parametrize('cmd_arg, runner_input',
[('-', json.dumps(GOOD_SUB_REQUEST)),
(json.dumps(GOOD_SUB_REQUEST), None)])
(json.dumps(GOOD_SUB_REQUEST), None),
('-', json.dumps(GOOD_SUB_REQUEST_WITH_HOSTING)),
(json.dumps(GOOD_SUB_REQUEST_WITH_HOSTING), None)])
@create_mock
def test_subscriptions_create_success(invoke, cmd_arg, runner_input):
"""Subscriptions creation succeeds with a valid subscription request."""
Expand Down
53 changes: 53 additions & 0 deletions tests/unit/test_subscription_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,59 @@ def test_build_request_clip_to_source_failure(geom_geojson):
)


def test_build_request_host_sentinel_hub_with_collection(geom_geojson):
source = {
"type": "catalog",
"parameters": {
"geometry": geom_geojson,
"start_time": "2021-03-01T00:00:00Z",
"end_time": "2023-11-01T00:00:00Z",
"rrule": "FREQ=MONTHLY;BYMONTH=3,4,5,6,7,8,9,10",
"item_types": ["PSScene"],
"asset_types": ["ortho_analytic_4b"]
}
}

hosting = {"type": "sentinel-hub"}

res = subscription_request.build_request('test',
source=source,
hosting=hosting)

expected = {"name": "test", "source": source, "hosting": hosting}

assert res == expected


def test_build_request_host_sentinel_hub_no_collection(geom_geojson):
source = {
"type": "catalog",
"parameters": {
"geometry": geom_geojson,
"start_time": "2021-03-01T00:00:00Z",
"end_time": "2023-11-01T00:00:00Z",
"rrule": "FREQ=MONTHLY;BYMONTH=3,4,5,6,7,8,9,10",
"item_types": ["PSScene"],
"asset_types": ["ortho_analytic_4b"]
}
}

hosting = {
"type": "sentinel-hub",
"parameters": {
"collection_id": "4c9af036-4274-4a97-bf0d-eb2a7853330d"
}
}

res = subscription_request.build_request('test',
source=source,
hosting=hosting)

expected = {"name": "test", "source": source, "hosting": hosting}

assert res == expected


def test_catalog_source_success(geom_geojson):
res = subscription_request.catalog_source(
item_types=["PSScene"],
Expand Down
Loading