From 9ff679f42639d1270a10d3120c9d4143bca3570e Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 24 Aug 2022 11:42:27 -0400 Subject: [PATCH 01/29] Added function to get supported bundles for a given item_type --- planet/specs.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/planet/specs.py b/planet/specs.py index 9c08a0bb8..56bddc46e 100644 --- a/planet/specs.py +++ b/planet/specs.py @@ -60,9 +60,8 @@ def validate_bundle(bundle): return _validate_field(bundle, supported, 'product_bundle') -def validate_item_type(item_type, bundle): - validated_bundle = validate_bundle(bundle) - supported = get_item_types(validated_bundle) +def validate_item_type(item_type): + supported = get_supported_bundles(item_type) return _validate_field(item_type, supported, 'item_type') @@ -129,6 +128,20 @@ def get_item_types(product_bundle=None): return item_types +def get_supported_bundles(item_type): + spec = _get_product_bundle_spec() + + all_product_bundles = set(spec['bundles'].keys()) + + supported_bundles = [] + for bundle in all_product_bundles: + availible_item_types = set(spec['bundles'][bundle]['assets'].keys()) + if item_type.lower() in [x.lower() for x in availible_item_types]: + supported_bundles.append(bundle) + + return supported_bundles + + def _get_product_bundle_spec(): with open(DATA_DIR / PRODUCT_BUNDLE_SPEC_NAME) as f: data = json.load(f) From 165888c8471de260db5379b56bd91b66b4aa6138 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 21 Sep 2022 11:08:29 -0700 Subject: [PATCH 02/29] Now validates supported product bundles with item_type. --- planet/specs.py | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/planet/specs.py b/planet/specs.py index 56bddc46e..6e40a4195 100644 --- a/planet/specs.py +++ b/planet/specs.py @@ -61,8 +61,8 @@ def validate_bundle(bundle): def validate_item_type(item_type): - supported = get_supported_bundles(item_type) - return _validate_field(item_type, supported, 'item_type') + supported_item_types = get_item_types() + return _validate_field(item_type, supported_item_types, 'item_type') def validate_order_type(order_type): @@ -91,6 +91,27 @@ def _validate_field(value, supported, field_name): return value +def validate_supported_bundles(item_type, bundle): + spec = _get_product_bundle_spec() + + all_product_bundles = set(spec['bundles'].keys()) + + supported_bundles = [] + for product_bundle in all_product_bundles: + availible_item_types = set( + spec['bundles'][product_bundle]['assets'].keys()) + if item_type.lower() in [x.lower() for x in availible_item_types]: + supported_bundles.append(product_bundle) + + return _validate_field(bundle, supported_bundles, 'bundle') + + +def _get_product_bundle_spec(): + with open(DATA_DIR / PRODUCT_BUNDLE_SPEC_NAME) as f: + data = json.load(f) + return data + + def get_match(test_entry, spec_entries, field_name): '''Find and return matching spec entry regardless of capitalization. @@ -126,23 +147,3 @@ def get_item_types(product_bundle=None): for bundle in get_product_bundles())) return item_types - - -def get_supported_bundles(item_type): - spec = _get_product_bundle_spec() - - all_product_bundles = set(spec['bundles'].keys()) - - supported_bundles = [] - for bundle in all_product_bundles: - availible_item_types = set(spec['bundles'][bundle]['assets'].keys()) - if item_type.lower() in [x.lower() for x in availible_item_types]: - supported_bundles.append(bundle) - - return supported_bundles - - -def _get_product_bundle_spec(): - with open(DATA_DIR / PRODUCT_BUNDLE_SPEC_NAME) as f: - data = json.load(f) - return data From c2161a682c13730e9ecda388c3c015cff75d1803 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 21 Sep 2022 11:08:49 -0700 Subject: [PATCH 03/29] Now validates supported product bundles with item_type. --- planet/order_request.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/planet/order_request.py b/planet/order_request.py index 911927bc6..aaabb5ff1 100644 --- a/planet/order_request.py +++ b/planet/order_request.py @@ -113,8 +113,9 @@ def product(item_ids: List[str], are not valid bundles or if item_type is not valid for the given bundle or fallback bundle. ''' + item_type = specs.validate_item_type(item_type) validated_product_bundle = specs.validate_bundle(product_bundle) - item_type = specs.validate_item_type(item_type, validated_product_bundle) + specs.validate_supported_bundles(item_type, product_bundle) if fallback_bundle is not None: validated_fallback_bundle = specs.validate_bundle(fallback_bundle) From 5b1b0eced42173b5e7303170bc749fb8caba6198 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 21 Sep 2022 11:31:39 -0700 Subject: [PATCH 04/29] Moved validation of supported bundles into validate_bundle --- planet/specs.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/planet/specs.py b/planet/specs.py index 6e40a4195..5729b4070 100644 --- a/planet/specs.py +++ b/planet/specs.py @@ -55,9 +55,10 @@ def __str__(self): return f'{self.field_name} - \'{self.value}\' is not one of {self.opts}.' -def validate_bundle(bundle): - supported = get_product_bundles() - return _validate_field(bundle, supported, 'product_bundle') +def validate_bundle(item_type, bundle): + all_product_bundles = get_product_bundles() + validate_supported_bundles(item_type, bundle, all_product_bundles) + return _validate_field(bundle, all_product_bundles, 'product_bundle') def validate_item_type(item_type): @@ -91,11 +92,9 @@ def _validate_field(value, supported, field_name): return value -def validate_supported_bundles(item_type, bundle): +def validate_supported_bundles(item_type, bundle, all_product_bundles): spec = _get_product_bundle_spec() - all_product_bundles = set(spec['bundles'].keys()) - supported_bundles = [] for product_bundle in all_product_bundles: availible_item_types = set( From 01a6036f1c294010e47d6f6756ee4797d2da03e8 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 21 Sep 2022 11:32:38 -0700 Subject: [PATCH 05/29] Moved validation of supported bundles into validate_bundle --- planet/order_request.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/planet/order_request.py b/planet/order_request.py index aaabb5ff1..b589894c3 100644 --- a/planet/order_request.py +++ b/planet/order_request.py @@ -114,12 +114,11 @@ def product(item_ids: List[str], bundle or fallback bundle. ''' item_type = specs.validate_item_type(item_type) - validated_product_bundle = specs.validate_bundle(product_bundle) - specs.validate_supported_bundles(item_type, product_bundle) + validated_product_bundle = specs.validate_bundle(item_type, product_bundle) if fallback_bundle is not None: - validated_fallback_bundle = specs.validate_bundle(fallback_bundle) - specs.validate_item_type(item_type, validated_fallback_bundle) + item_type = specs.validate_item_type(item_type) + validated_fallback_bundle = specs.validate_bundle(item_type, fallback_bundle) validated_product_bundle = ','.join( [validated_product_bundle, validated_fallback_bundle]) From 755e1ee66ead3ec57064b3109e455f86006bb126 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 21 Sep 2022 11:52:57 -0700 Subject: [PATCH 06/29] Added tests for new validation bundle function. --- tests/unit/test_specs.py | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/tests/unit/test_specs.py b/tests/unit/test_specs.py index e7c17bf73..c6c0e2740 100644 --- a/tests/unit/test_specs.py +++ b/tests/unit/test_specs.py @@ -21,6 +21,37 @@ LOGGER = logging.getLogger(__name__) TEST_PRODUCT_BUNDLE = 'visual' +ALL_PRODUCT_BUNDLES = [ + 'analytic', + 'analytic_udm2', + 'analytic_3b_udm2', + 'analytic_5b', + 'analytic_5b_udm2', + 'analytic_8b_udm2', + 'visual', + 'uncalibrated_dn', + 'uncalibrated_dn_udm2', + 'basic_analytic', + 'basic_analytic_udm2', + 'basic_analytic_8b_udm2', + 'basic_uncalibrated_dn', + 'basic_uncalibrated_dn_udm2', + 'analytic_sr', + 'analytic_sr_udm2', + 'analytic_8b_sr_udm2', + 'basic_uncalibrated_dn_nitf', + 'basic_uncalibrated_dn_nitf_udm2', + 'basic_analytic_nitf', + 'basic_analytic_nitf_udm2', + 'basic_panchromatic', + 'basic_panchromatic_dn', + 'panchromatic', + 'panchromatic_dn', + 'panchromatic_dn_udm2', + 'pansharpened', + 'pansharpened_udm2', + 'basic_l1a_dn' +] # must be a valid item type for TEST_PRODUCT_BUNDLE TEST_ITEM_TYPE = 'PSScene' ALL_ITEM_TYPES = [ @@ -117,3 +148,16 @@ def test_get_item_types_without_bundle(): item_types = specs.get_item_types() for item in item_types: assert item in ALL_ITEM_TYPES + + +def test_validate_supported_bundles_success(): + validated_bundle = specs.validate_supported_bundles( + TEST_ITEM_TYPE, TEST_PRODUCT_BUNDLE, ALL_PRODUCT_BUNDLES) + assert validated_bundle in ALL_PRODUCT_BUNDLES + + +def test_validate_supported_bundles_fail(): + with pytest.raises(specs.SpecificationException): + specs.validate_supported_bundles(TEST_ITEM_TYPE, + 'analytic', + ALL_PRODUCT_BUNDLES) From 5390410648a84beb19d05bade4aac59ead9a96aa Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 21 Sep 2022 12:05:24 -0700 Subject: [PATCH 07/29] Reformatted tests for validate_bundle and validate_item_type for new inputs. --- tests/unit/test_specs.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/unit/test_specs.py b/tests/unit/test_specs.py index c6c0e2740..6405fd640 100644 --- a/tests/unit/test_specs.py +++ b/tests/unit/test_specs.py @@ -85,26 +85,26 @@ def test_get_type_match(): def test_validate_bundle_supported(): - assert 'analytic' == specs.validate_bundle('ANALYTIC') + assert 'visual' == specs.validate_bundle(TEST_ITEM_TYPE, 'VISUAL') def test_validate_bundle_notsupported(): with pytest.raises(specs.SpecificationException): - specs.validate_bundle('notsupported') + specs.validate_bundle(TEST_ITEM_TYPE, 'notsupported') -def test_validate_item_type_supported(): - assert 'PSOrthoTile' == specs.validate_item_type('psorthotile', 'analytic') +def test_validate_bundle_notsupported_item_type(): + with pytest.raises(specs.SpecificationException): + specs.validate_item_type('wha') -def test_validate_item_type_notsupported_bundle(): - with pytest.raises(specs.SpecificationException): - specs.validate_item_type('psorthotile', 'wha') +def test_validate_item_type_supported(): + assert 'PSOrthoTile' == specs.validate_item_type('psorthotile') def test_validate_item_type_notsupported_itemtype(): with pytest.raises(specs.SpecificationException): - specs.validate_item_type('notsupported', 'analytic') + specs.validate_item_type('notsupported') def test_validate_order_type_supported(): From 75e6b280c13fa6ae3f5715722c2a048e62b7b0b1 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 21 Sep 2022 12:15:25 -0700 Subject: [PATCH 08/29] linting --- planet/order_request.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/planet/order_request.py b/planet/order_request.py index b589894c3..b24a1ecab 100644 --- a/planet/order_request.py +++ b/planet/order_request.py @@ -118,7 +118,8 @@ def product(item_ids: List[str], if fallback_bundle is not None: item_type = specs.validate_item_type(item_type) - validated_fallback_bundle = specs.validate_bundle(item_type, fallback_bundle) + validated_fallback_bundle = specs.validate_bundle( + item_type, fallback_bundle) validated_product_bundle = ','.join( [validated_product_bundle, validated_fallback_bundle]) From 800cbc7a59c77a829bcffae23f4061ee37d9c05e Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Thu, 22 Sep 2022 09:47:06 -0700 Subject: [PATCH 09/29] testing --- planet/specs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/planet/specs.py b/planet/specs.py index 5729b4070..384b9c193 100644 --- a/planet/specs.py +++ b/planet/specs.py @@ -76,6 +76,7 @@ def validate_archive_type(archive_type): 'archive_type') + def validate_tool(tool): return _validate_field(tool, SUPPORTED_TOOLS, 'tool') From dd38950360813d2897fd10d8cbacd63689e7edc3 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Thu, 22 Sep 2022 10:45:14 -0700 Subject: [PATCH 10/29] linting again --- planet/specs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/planet/specs.py b/planet/specs.py index 384b9c193..5729b4070 100644 --- a/planet/specs.py +++ b/planet/specs.py @@ -76,7 +76,6 @@ def validate_archive_type(archive_type): 'archive_type') - def validate_tool(tool): return _validate_field(tool, SUPPORTED_TOOLS, 'tool') From ac9662e30237ee46fa3bf82122336458822653ba Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Thu, 22 Sep 2022 11:39:58 -0700 Subject: [PATCH 11/29] Allow get_product_bundle to accept item_type --- planet/specs.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/planet/specs.py b/planet/specs.py index 5729b4070..e24312f94 100644 --- a/planet/specs.py +++ b/planet/specs.py @@ -126,10 +126,22 @@ def get_match(test_entry, spec_entries, field_name): return match -def get_product_bundles(): +def get_product_bundles(item_type=None): '''Get product bundles supported by Orders API.''' spec = _get_product_bundle_spec() - return spec['bundles'].keys() + + if item_type: + all_product_bundles = get_product_bundles() + + supported_bundles = [] + for product_bundle in all_product_bundles: + availible_item_types = set( + spec['bundles'][product_bundle]['assets'].keys()) + if item_type.lower() in [x.lower() for x in availible_item_types]: + supported_bundles.append(product_bundle) + else: + supported_bundles = spec['bundles'].keys() + return supported_bundles def get_item_types(product_bundle=None): From 6df0acd968d8ec86e70986f0ca00d65d9585ee68 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Thu, 22 Sep 2022 11:40:43 -0700 Subject: [PATCH 12/29] Reordered options. --- planet/cli/orders.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/planet/cli/orders.py b/planet/cli/orders.py index 73138d54a..33d8cea4d 100644 --- a/planet/cli/orders.py +++ b/planet/cli/orders.py @@ -225,9 +225,10 @@ async def create(ctx, request: str, pretty): @click.pass_context @translate_exceptions @coro -@click.option('--name', +@click.option('--item-type', required=True, - help='Order name. Does not need to be unique.', + eager=True, + help='Specify an item type', type=click.STRING) @click.option( '--bundle', @@ -241,9 +242,9 @@ async def create(ctx, request: str, pretty): help='One or more comma-separated item IDs.', type=types.CommaSeparatedString(), required=True) -@click.option('--item-type', +@click.option('--name', required=True, - help='Specify an item type', + help='Order name. Does not need to be unique.', type=click.STRING) @click.option('--clip', type=types.JSON(), From e5a8a4ac778105c02bc460a72098fed750336b5d Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Thu, 22 Sep 2022 12:50:48 -0700 Subject: [PATCH 13/29] Made item type case insensitive. --- planet/cli/orders.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/planet/cli/orders.py b/planet/cli/orders.py index 33d8cea4d..a370c1e60 100644 --- a/planet/cli/orders.py +++ b/planet/cli/orders.py @@ -227,9 +227,9 @@ async def create(ctx, request: str, pretty): @coro @click.option('--item-type', required=True, - eager=True, help='Specify an item type', - type=click.STRING) + type=click.Choice(planet.specs.get_item_types(), + case_sensitive=False)) @click.option( '--bundle', multiple=False, From 4de98d6db744141f9cd7c2d8e0129df74d1b7544 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Thu, 22 Sep 2022 13:24:55 -0700 Subject: [PATCH 14/29] Added functionality to show valid bundles with item type in CLI --- planet/cli/orders.py | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/planet/cli/orders.py b/planet/cli/orders.py index a370c1e60..0d04740f1 100644 --- a/planet/cli/orders.py +++ b/planet/cli/orders.py @@ -221,6 +221,34 @@ async def create(ctx, request: str, pretty): echo_json(order, pretty) +def stash_item_type(ctx, param, value): + ctx.obj['item_type'] = value + return value + + +def bundle_cb(ctx, param, value): + item_type = ctx.obj['item_type'] + choices = planet.specs.get_product_bundles(item_type) + if value is None: + raise click.ClickException( + f"Missing option '--{param.name}': Choose from: {choices}.") + else: + return value + + +class Bundle(click.Choice): + name = "bundle" + + def __init__(self, case_sensitive: bool = True) -> None: + self.choices = [] + self.case_sensitive = case_sensitive + + def convert(self, value, param, ctx): + item_type = ctx.obj['item_type'] + self.choices = planet.specs.get_product_bundles(item_type) + super().convert(value, param, ctx) + + @orders.command() @click.pass_context @translate_exceptions @@ -229,14 +257,16 @@ async def create(ctx, request: str, pretty): required=True, help='Specify an item type', type=click.Choice(planet.specs.get_item_types(), - case_sensitive=False)) + case_sensitive=False), + callback=stash_item_type, + is_eager=True) @click.option( '--bundle', multiple=False, - required=True, + required=False, + callback=bundle_cb, help='Product bundle.', - type=click.Choice(planet.specs.get_product_bundles(), - case_sensitive=False), + type=Bundle(case_sensitive=False), ) @click.option('--id', help='One or more comma-separated item IDs.', From 4150665f6cb52848402907db59fccd42f4c47aef Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Thu, 22 Sep 2022 13:44:09 -0700 Subject: [PATCH 15/29] Reformatted error message to match click.Choice. Reordered inputs. --- planet/cli/orders.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/planet/cli/orders.py b/planet/cli/orders.py index 0d04740f1..3b46452a2 100644 --- a/planet/cli/orders.py +++ b/planet/cli/orders.py @@ -230,8 +230,9 @@ def bundle_cb(ctx, param, value): item_type = ctx.obj['item_type'] choices = planet.specs.get_product_bundles(item_type) if value is None: - raise click.ClickException( - f"Missing option '--{param.name}': Choose from: {choices}.") + msg = ("Choose from:\n\t{choices}").format( + choices=",\n\t".join(choices)) + raise click.ClickException(f"Missing option '--{param.name}'. {msg}") else: return value @@ -253,6 +254,10 @@ def convert(self, value, param, ctx): @click.pass_context @translate_exceptions @coro +@click.option('--name', + required=True, + help='Order name. Does not need to be unique.', + type=click.STRING) @click.option('--item-type', required=True, help='Specify an item type', @@ -272,10 +277,6 @@ def convert(self, value, param, ctx): help='One or more comma-separated item IDs.', type=types.CommaSeparatedString(), required=True) -@click.option('--name', - required=True, - help='Order name. Does not need to be unique.', - type=click.STRING) @click.option('--clip', type=types.JSON(), help="""Clip feature GeoJSON. Can be a json string, filename, @@ -303,11 +304,11 @@ def convert(self, value, param, ctx): @pretty async def request(ctx, name, + item_type, bundle, id, clip, tools, - item_type, email, cloudconfig, stac, From b34c74b749f96228891054f73f388afb39296f92 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Thu, 22 Sep 2022 13:55:03 -0700 Subject: [PATCH 16/29] Fixed silly return none bug --- planet/cli/orders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planet/cli/orders.py b/planet/cli/orders.py index 3b46452a2..11c673bca 100644 --- a/planet/cli/orders.py +++ b/planet/cli/orders.py @@ -247,7 +247,7 @@ def __init__(self, case_sensitive: bool = True) -> None: def convert(self, value, param, ctx): item_type = ctx.obj['item_type'] self.choices = planet.specs.get_product_bundles(item_type) - super().convert(value, param, ctx) + return super().convert(value, param, ctx) @orders.command() From ef3856a13a7c0e6971893ae04c5b03d0aa89fad2 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Thu, 22 Sep 2022 14:26:14 -0700 Subject: [PATCH 17/29] Renamed data CLI callback function tests. --- tests/unit/{test_data_item_type.py => test_data_callbacks.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/unit/{test_data_item_type.py => test_data_callbacks.py} (100%) diff --git a/tests/unit/test_data_item_type.py b/tests/unit/test_data_callbacks.py similarity index 100% rename from tests/unit/test_data_item_type.py rename to tests/unit/test_data_callbacks.py From 4312c3af237aa6ba8f16129a8230708ac66ea927 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Thu, 22 Sep 2022 14:26:39 -0700 Subject: [PATCH 18/29] Fixed assersion message --- tests/integration/test_orders_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_orders_cli.py b/tests/integration/test_orders_cli.py index f47c6bb13..3f26950f3 100644 --- a/tests/integration/test_orders_cli.py +++ b/tests/integration/test_orders_cli.py @@ -492,7 +492,7 @@ def test_cli_orders_request_item_type_invalid(invoke): '--item-type=invalid' ]) assert result.exit_code == 2 - assert 'Error: Invalid value: item_type' in result.output + assert "Error: Invalid value for '--item-type':" in result.output def test_cli_orders_request_id_empty(invoke): From ec3f864323eb4d75804e7305c3c965f537587965 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Thu, 22 Sep 2022 14:31:25 -0700 Subject: [PATCH 19/29] Added tests for invalid and incompatible bundles. --- tests/integration/test_orders_cli.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/integration/test_orders_cli.py b/tests/integration/test_orders_cli.py index 3f26950f3..3fc1d058c 100644 --- a/tests/integration/test_orders_cli.py +++ b/tests/integration/test_orders_cli.py @@ -495,6 +495,30 @@ def test_cli_orders_request_item_type_invalid(invoke): assert "Error: Invalid value for '--item-type':" in result.output +def test_cli_orders_request_product_bundle_invalid(invoke): + result = invoke([ + 'request', + '--name=test', + '--id=4500474_2133707_2021-05-20_2419', + '--bundle=invalid', + '--item-type=PSScene' + ]) + assert result.exit_code == 2 + assert "Error: Invalid value for '--bundle':" in result.output + + +def test_cli_orders_request_product_bundle_incompatible(invoke): + result = invoke([ + 'request', + '--name=test', + '--id=4500474_2133707_2021-05-20_2419', + '--bundle=analytic', + '--item-type=PSScene' + ]) + assert result.exit_code == 2 + assert "Error: Invalid value for '--bundle':" in result.output + + def test_cli_orders_request_id_empty(invoke): result = invoke([ 'request', From b9e6387b7eb8973c606016f8c5180d4e65232adb Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Thu, 22 Sep 2022 14:40:10 -0700 Subject: [PATCH 20/29] Added a new test for product bundle with item type. --- tests/unit/test_specs.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_specs.py b/tests/unit/test_specs.py index 6405fd640..f326f7696 100644 --- a/tests/unit/test_specs.py +++ b/tests/unit/test_specs.py @@ -134,11 +134,17 @@ def test_validate_file_format_notsupported(): specs.validate_archive_type('notsupported') -def test_get_product_bundles(): - bundles = specs.get_product_bundles() +def test_get_product_bundles_with_item_type(): + bundles = specs.get_product_bundles(item_type=TEST_ITEM_TYPE) assert TEST_PRODUCT_BUNDLE in bundles +def test_get_product_bundles_without_item_type(): + bundles = specs.get_product_bundles() + for bundle in bundles: + assert bundle in ALL_PRODUCT_BUNDLES + + def test_get_item_types_with_bundle(): item_types = specs.get_item_types(product_bundle=TEST_PRODUCT_BUNDLE) assert TEST_ITEM_TYPE in item_types From a682566d65ee48a1d7b823b88c07545041911afa Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Thu, 22 Sep 2022 14:45:54 -0700 Subject: [PATCH 21/29] Added test for stash_item_type --- tests/unit/test_orders_callbacks.py | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/unit/test_orders_callbacks.py diff --git a/tests/unit/test_orders_callbacks.py b/tests/unit/test_orders_callbacks.py new file mode 100644 index 000000000..f61f52680 --- /dev/null +++ b/tests/unit/test_orders_callbacks.py @@ -0,0 +1,33 @@ +# Copyright 2022 Planet Labs PBC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import logging +import pytest +import click +from planet.cli.orders import stash_item_type, bundle_cb, Bundle + +LOGGER = logging.getLogger(__name__) + +TEST_ITEM_TYPE = 'psscene' + + +class MockContext: + + def __init__(self): + self.obj = {} + + +def test_stash_item_type_success(): + ctx = MockContext() + stash_item_type(ctx, [], TEST_ITEM_TYPE) + assert ctx.obj == {'item_type': TEST_ITEM_TYPE} From dd869622eb5512ca6a06f2efaa6f8ede65888275 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Fri, 23 Sep 2022 09:26:50 -0700 Subject: [PATCH 22/29] Added tests for bunde_cb. --- tests/unit/test_orders_callbacks.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_orders_callbacks.py b/tests/unit/test_orders_callbacks.py index f61f52680..f77ea4676 100644 --- a/tests/unit/test_orders_callbacks.py +++ b/tests/unit/test_orders_callbacks.py @@ -14,7 +14,7 @@ import logging import pytest import click -from planet.cli.orders import stash_item_type, bundle_cb, Bundle +from planet.cli.orders import stash_item_type, bundle_cb LOGGER = logging.getLogger(__name__) @@ -27,7 +27,28 @@ def __init__(self): self.obj = {} +class Param(object): + + def __init__(self): + self.name = 'bundle' + + def test_stash_item_type_success(): ctx = MockContext() - stash_item_type(ctx, [], TEST_ITEM_TYPE) + stash_item_type(ctx, 'item_type', TEST_ITEM_TYPE) assert ctx.obj == {'item_type': TEST_ITEM_TYPE} + + +def test_bundle_cb_success(): + ctx = MockContext() + ctx.obj['item_type'] = TEST_ITEM_TYPE + result = bundle_cb(ctx, 'bundle', TEST_ITEM_TYPE) + assert result == TEST_ITEM_TYPE + + +def test_bundle_cb_fail(): + ctx = MockContext() + ctx.obj['item_type'] = TEST_ITEM_TYPE + param = Param() + with pytest.raises(click.ClickException): + bundle_cb(ctx, param, None) From f739198f37e46d75137425de5c03904f14791dbb Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Fri, 23 Sep 2022 10:33:38 -0700 Subject: [PATCH 23/29] More limited assertion on ctx.obj Co-authored-by: Sean Gillies --- tests/unit/test_orders_callbacks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_orders_callbacks.py b/tests/unit/test_orders_callbacks.py index f77ea4676..4c0775c34 100644 --- a/tests/unit/test_orders_callbacks.py +++ b/tests/unit/test_orders_callbacks.py @@ -36,7 +36,7 @@ def __init__(self): def test_stash_item_type_success(): ctx = MockContext() stash_item_type(ctx, 'item_type', TEST_ITEM_TYPE) - assert ctx.obj == {'item_type': TEST_ITEM_TYPE} + assert ctx.obj['item_type'] = TEST_ITEM_TYPE def test_bundle_cb_success(): From f5b497d4c79952bb981dda2664a818e2c3f973d9 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Fri, 23 Sep 2022 10:47:54 -0700 Subject: [PATCH 24/29] Updated docstring and function name for test_bundle test Co-authored-by: Sean Gillies --- tests/unit/test_orders_callbacks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_orders_callbacks.py b/tests/unit/test_orders_callbacks.py index 4c0775c34..970dcf5b2 100644 --- a/tests/unit/test_orders_callbacks.py +++ b/tests/unit/test_orders_callbacks.py @@ -46,7 +46,8 @@ def test_bundle_cb_success(): assert result == TEST_ITEM_TYPE -def test_bundle_cb_fail(): +def test_bundle_cb_missing_parameter(): + """If bundle option is missing, print helpful error using item type information.""" ctx = MockContext() ctx.obj['item_type'] = TEST_ITEM_TYPE param = Param() From 0e4d9d0a7587734d1a7aca38ef9f95243bf4df87 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Fri, 23 Sep 2022 10:48:15 -0700 Subject: [PATCH 25/29] Updated docstring and function name for test_bundle test Co-authored-by: Sean Gillies --- tests/unit/test_orders_callbacks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_orders_callbacks.py b/tests/unit/test_orders_callbacks.py index 970dcf5b2..430229a58 100644 --- a/tests/unit/test_orders_callbacks.py +++ b/tests/unit/test_orders_callbacks.py @@ -39,7 +39,8 @@ def test_stash_item_type_success(): assert ctx.obj['item_type'] = TEST_ITEM_TYPE -def test_bundle_cb_success(): +def test_bundle_cb_pass_through(): + """Do nothing if the bundle value is defined.""" ctx = MockContext() ctx.obj['item_type'] = TEST_ITEM_TYPE result = bundle_cb(ctx, 'bundle', TEST_ITEM_TYPE) From 03409b0348cf4bfbc979e9530252052361f97a12 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Fri, 23 Sep 2022 11:37:39 -0700 Subject: [PATCH 26/29] More explicit with bundle_cb tests. --- tests/unit/test_orders_callbacks.py | 44 ++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_orders_callbacks.py b/tests/unit/test_orders_callbacks.py index 430229a58..494777957 100644 --- a/tests/unit/test_orders_callbacks.py +++ b/tests/unit/test_orders_callbacks.py @@ -15,10 +15,42 @@ import pytest import click from planet.cli.orders import stash_item_type, bundle_cb +from planet.specs import get_product_bundles LOGGER = logging.getLogger(__name__) TEST_ITEM_TYPE = 'psscene' +ALL_PRODUCT_BUNDLES = [ + 'analytic', + 'analytic_udm2', + 'analytic_3b_udm2', + 'analytic_5b', + 'analytic_5b_udm2', + 'analytic_8b_udm2', + 'visual', + 'uncalibrated_dn', + 'uncalibrated_dn_udm2', + 'basic_analytic', + 'basic_analytic_udm2', + 'basic_analytic_8b_udm2', + 'basic_uncalibrated_dn', + 'basic_uncalibrated_dn_udm2', + 'analytic_sr', + 'analytic_sr_udm2', + 'analytic_8b_sr_udm2', + 'basic_uncalibrated_dn_nitf', + 'basic_uncalibrated_dn_nitf_udm2', + 'basic_analytic_nitf', + 'basic_analytic_nitf_udm2', + 'basic_panchromatic', + 'basic_panchromatic_dn', + 'panchromatic', + 'panchromatic_dn', + 'panchromatic_dn_udm2', + 'pansharpened', + 'pansharpened_udm2', + 'basic_l1a_dn' +] class MockContext: @@ -36,15 +68,14 @@ def __init__(self): def test_stash_item_type_success(): ctx = MockContext() stash_item_type(ctx, 'item_type', TEST_ITEM_TYPE) - assert ctx.obj['item_type'] = TEST_ITEM_TYPE + assert ctx.obj['item_type'] == TEST_ITEM_TYPE def test_bundle_cb_pass_through(): """Do nothing if the bundle value is defined.""" ctx = MockContext() ctx.obj['item_type'] = TEST_ITEM_TYPE - result = bundle_cb(ctx, 'bundle', TEST_ITEM_TYPE) - assert result == TEST_ITEM_TYPE + assert bundle_cb(ctx, 'bundle', 'anything') def test_bundle_cb_missing_parameter(): @@ -52,5 +83,10 @@ def test_bundle_cb_missing_parameter(): ctx = MockContext() ctx.obj['item_type'] = TEST_ITEM_TYPE param = Param() - with pytest.raises(click.ClickException): + + all_bundles = get_product_bundles() + all_bundles_formatted = [bundle + "\n\t" for bundle in all_bundles] + msg = f"Missing option '--bundle'. Choose from:\n{all_bundles_formatted}." + + with pytest.raises(click.ClickException, match=msg): bundle_cb(ctx, param, None) From 283af9a25a0f3743260fd0b5a84085ad864bf14e Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Tue, 27 Sep 2022 13:02:55 -0700 Subject: [PATCH 27/29] Removed unused tests. --- tests/unit/test_orders_callbacks.py | 92 ----------------------------- 1 file changed, 92 deletions(-) delete mode 100644 tests/unit/test_orders_callbacks.py diff --git a/tests/unit/test_orders_callbacks.py b/tests/unit/test_orders_callbacks.py deleted file mode 100644 index 494777957..000000000 --- a/tests/unit/test_orders_callbacks.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright 2022 Planet Labs PBC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import logging -import pytest -import click -from planet.cli.orders import stash_item_type, bundle_cb -from planet.specs import get_product_bundles - -LOGGER = logging.getLogger(__name__) - -TEST_ITEM_TYPE = 'psscene' -ALL_PRODUCT_BUNDLES = [ - 'analytic', - 'analytic_udm2', - 'analytic_3b_udm2', - 'analytic_5b', - 'analytic_5b_udm2', - 'analytic_8b_udm2', - 'visual', - 'uncalibrated_dn', - 'uncalibrated_dn_udm2', - 'basic_analytic', - 'basic_analytic_udm2', - 'basic_analytic_8b_udm2', - 'basic_uncalibrated_dn', - 'basic_uncalibrated_dn_udm2', - 'analytic_sr', - 'analytic_sr_udm2', - 'analytic_8b_sr_udm2', - 'basic_uncalibrated_dn_nitf', - 'basic_uncalibrated_dn_nitf_udm2', - 'basic_analytic_nitf', - 'basic_analytic_nitf_udm2', - 'basic_panchromatic', - 'basic_panchromatic_dn', - 'panchromatic', - 'panchromatic_dn', - 'panchromatic_dn_udm2', - 'pansharpened', - 'pansharpened_udm2', - 'basic_l1a_dn' -] - - -class MockContext: - - def __init__(self): - self.obj = {} - - -class Param(object): - - def __init__(self): - self.name = 'bundle' - - -def test_stash_item_type_success(): - ctx = MockContext() - stash_item_type(ctx, 'item_type', TEST_ITEM_TYPE) - assert ctx.obj['item_type'] == TEST_ITEM_TYPE - - -def test_bundle_cb_pass_through(): - """Do nothing if the bundle value is defined.""" - ctx = MockContext() - ctx.obj['item_type'] = TEST_ITEM_TYPE - assert bundle_cb(ctx, 'bundle', 'anything') - - -def test_bundle_cb_missing_parameter(): - """If bundle option is missing, print helpful error using item type information.""" - ctx = MockContext() - ctx.obj['item_type'] = TEST_ITEM_TYPE - param = Param() - - all_bundles = get_product_bundles() - all_bundles_formatted = [bundle + "\n\t" for bundle in all_bundles] - msg = f"Missing option '--bundle'. Choose from:\n{all_bundles_formatted}." - - with pytest.raises(click.ClickException, match=msg): - bundle_cb(ctx, param, None) From 21751cae1af580052b3a61b90dc6be8c195863e4 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Tue, 27 Sep 2022 13:03:32 -0700 Subject: [PATCH 28/29] Changed item type and bundle to arguments and removed unused functions and class. --- planet/cli/orders.py | 54 ++++++++------------------------------------ 1 file changed, 9 insertions(+), 45 deletions(-) diff --git a/planet/cli/orders.py b/planet/cli/orders.py index 11c673bca..11dbfb8a6 100644 --- a/planet/cli/orders.py +++ b/planet/cli/orders.py @@ -221,58 +221,22 @@ async def create(ctx, request: str, pretty): echo_json(order, pretty) -def stash_item_type(ctx, param, value): - ctx.obj['item_type'] = value - return value - - -def bundle_cb(ctx, param, value): - item_type = ctx.obj['item_type'] - choices = planet.specs.get_product_bundles(item_type) - if value is None: - msg = ("Choose from:\n\t{choices}").format( - choices=",\n\t".join(choices)) - raise click.ClickException(f"Missing option '--{param.name}'. {msg}") - else: - return value - - -class Bundle(click.Choice): - name = "bundle" - - def __init__(self, case_sensitive: bool = True) -> None: - self.choices = [] - self.case_sensitive = case_sensitive - - def convert(self, value, param, ctx): - item_type = ctx.obj['item_type'] - self.choices = planet.specs.get_product_bundles(item_type) - return super().convert(value, param, ctx) - - @orders.command() @click.pass_context @translate_exceptions @coro +@click.argument('item_type', + metavar='ITEM_TYPE', + type=click.Choice(planet.specs.get_item_types(), + case_sensitive=False)) +@click.argument('bundle', + metavar='BUNDLE', + type=click.Choice(planet.specs.get_product_bundles(), + case_sensitive=False)) @click.option('--name', required=True, help='Order name. Does not need to be unique.', type=click.STRING) -@click.option('--item-type', - required=True, - help='Specify an item type', - type=click.Choice(planet.specs.get_item_types(), - case_sensitive=False), - callback=stash_item_type, - is_eager=True) -@click.option( - '--bundle', - multiple=False, - required=False, - callback=bundle_cb, - help='Product bundle.', - type=Bundle(case_sensitive=False), -) @click.option('--id', help='One or more comma-separated item IDs.', type=types.CommaSeparatedString(), @@ -303,9 +267,9 @@ def convert(self, value, param, ctx): (STAC) format.""") @pretty async def request(ctx, - name, item_type, bundle, + name, id, clip, tools, From 66a5acefc46ca22a57e112cadd3256297b777c18 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Tue, 27 Sep 2022 13:04:00 -0700 Subject: [PATCH 29/29] Changed test for new arguments. --- tests/integration/test_orders_cli.py | 60 +++++++++++++--------------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/tests/integration/test_orders_cli.py b/tests/integration/test_orders_cli.py index 3fc1d058c..3610ad9e2 100644 --- a/tests/integration/test_orders_cli.py +++ b/tests/integration/test_orders_cli.py @@ -53,7 +53,6 @@ def stac_json(): def test_cli_orders_list_basic(invoke, order_descriptions): next_page_url = TEST_ORDERS_URL + '/blob/?page_marker=IAmATest' order1, order2, order3 = order_descriptions - page1_response = { "_links": { "_self": "string", "next": next_page_url @@ -462,10 +461,10 @@ def test_cli_orders_request_basic_success(expected_ids, stac_json): result = invoke([ 'request', + 'PSOrthoTile', + 'analytic', '--name=test', f'--id={id_string}', - '--bundle=analytic', - '--item-type=PSOrthoTile' ]) assert not result.exception @@ -486,47 +485,42 @@ def test_cli_orders_request_basic_success(expected_ids, def test_cli_orders_request_item_type_invalid(invoke): result = invoke([ 'request', + 'invalid' + 'analytic', '--name=test', '--id=4500474_2133707_2021-05-20_2419', - '--bundle=analytic', - '--item-type=invalid' ]) assert result.exit_code == 2 - assert "Error: Invalid value for '--item-type':" in result.output + assert "Usage: main orders request [OPTIONS] ITEM_TYPE BUNDLE" in result.output def test_cli_orders_request_product_bundle_invalid(invoke): result = invoke([ 'request', + 'PSScene' + 'invalid', '--name=test', '--id=4500474_2133707_2021-05-20_2419', - '--bundle=invalid', - '--item-type=PSScene' ]) assert result.exit_code == 2 - assert "Error: Invalid value for '--bundle':" in result.output + assert "Usage: main orders request [OPTIONS] ITEM_TYPE BUNDLE" in result.output def test_cli_orders_request_product_bundle_incompatible(invoke): result = invoke([ 'request', + 'PSScene', + 'analytic', '--name=test', '--id=4500474_2133707_2021-05-20_2419', - '--bundle=analytic', - '--item-type=PSScene' ]) assert result.exit_code == 2 - assert "Error: Invalid value for '--bundle':" in result.output + assert "Usage: main orders request [OPTIONS] ITEM_TYPE BUNDLE" in result.output def test_cli_orders_request_id_empty(invoke): - result = invoke([ - 'request', - '--name=test', - '--id=', - '--bundle=analytic', - '--item-type=PSOrthoTile', - ]) + result = invoke( + ['request', 'PSOrthoTile', 'analytic', '--name=test', '--id=']) assert result.exit_code == 2 assert 'Entry cannot be an empty string.' in result.output @@ -544,10 +538,10 @@ def test_cli_orders_request_clip_success(geom_fixture, result = invoke([ 'request', + 'PSOrthoTile', + 'analytic', '--name=test', '--id=4500474_2133707_2021-05-20_2419', - '--bundle=analytic', - '--item-type=PSOrthoTile', f'--clip={json.dumps(geom)}', ]) assert result.exit_code == 0 @@ -574,10 +568,10 @@ def test_cli_orders_request_clip_success(geom_fixture, def test_cli_orders_request_clip_invalid_geometry(invoke, point_geom_geojson): result = invoke([ 'request', + 'PSOrthoTile', + 'analytic', '--name=test', '--id=4500474_2133707_2021-05-20_2419', - '--bundle=analytic', - '--item-type=PSOrthoTile', f'--clip={json.dumps(point_geom_geojson)}' ]) assert result.exit_code == 2 @@ -591,10 +585,10 @@ def test_cli_orders_request_both_clip_and_tools(invoke, geom_geojson): # option values are valid json result = invoke([ 'request', + 'PSOrthoTile', + 'analytic', '--name=test', '--id=4500474_2133707_2021-05-20_2419', - '--bundle=analytic', - '--item-type=PSOrthoTile', f'--clip={json.dumps(geom_geojson)}', f'--tools={json.dumps(geom_geojson)}' ]) @@ -616,10 +610,10 @@ def test_cli_orders_request_cloudconfig(invoke, stac_json): result = invoke([ 'request', + 'PSOrthoTile', + 'analytic', '--name=test', '--id=4500474_2133707_2021-05-20_2419', - '--bundle=analytic', - '--item-type=PSOrthoTile', f'--cloudconfig={json.dumps(config_json)}', ]) assert result.exit_code == 0 @@ -643,10 +637,10 @@ def test_cli_orders_request_cloudconfig(invoke, stac_json): def test_cli_orders_request_email(invoke, stac_json): result = invoke([ 'request', + 'PSOrthoTile', + 'analytic', '--name=test', '--id=4500474_2133707_2021-05-20_2419', - '--bundle=analytic', - '--item-type=PSOrthoTile', '--email' ]) assert result.exit_code == 0 @@ -674,10 +668,10 @@ def test_cli_orders_request_tools(invoke, geom_geojson, stac_json): result = invoke([ 'request', + 'PSOrthoTile', + 'analytic', '--name=test', '--id=4500474_2133707_2021-05-20_2419', - '--bundle=analytic', - '--item-type=PSOrthoTile', f'--tools={json.dumps(tools_json)}' ]) @@ -702,10 +696,10 @@ def test_cli_orders_request_no_stac(invoke): result = invoke([ 'request', + 'PSOrthoTile', + 'analytic', '--name=test', '--id=4500474_2133707_2021-05-20_2419', - '--bundle=analytic', - '--item-type=PSOrthoTile', '--no-stac' ])