From 580711cbef280c15c2de5a90b7b9f82b3a9ffb95 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Mon, 25 Jul 2022 10:08:09 -0400 Subject: [PATCH 01/30] First attempt at showing item_types. Issue, don't know the bundle. --- planet/cli/data.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/planet/cli/data.py b/planet/cli/data.py index 5f1385035..346a0a772 100644 --- a/planet/cli/data.py +++ b/planet/cli/data.py @@ -19,6 +19,7 @@ from planet import data_filter, DataClient from planet.clients.data import SEARCH_SORT, SEARCH_SORT_DEFAULT, STATS_INTERVAL +from planet.specs import get_item_types from . import types from .cmds import coro, translate_exceptions @@ -26,6 +27,8 @@ from .options import limit, pretty from .session import CliSession +ITEM_TYPES = get_item_types('analytic') + @asynccontextmanager async def data_client(ctx): From 29ab2d087a99c378468744988f2df5bca414ae10 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Mon, 25 Jul 2022 10:50:41 -0400 Subject: [PATCH 02/30] Working soluition, but perhaps should exist in planet.clients.data.DataClient.get_item_type --- planet/cli/data.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/planet/cli/data.py b/planet/cli/data.py index 346a0a772..47dd6c98a 100644 --- a/planet/cli/data.py +++ b/planet/cli/data.py @@ -19,7 +19,7 @@ from planet import data_filter, DataClient from planet.clients.data import SEARCH_SORT, SEARCH_SORT_DEFAULT, STATS_INTERVAL -from planet.specs import get_item_types +from planet.specs import get_item_types, get_product_bundles from . import types from .cmds import coro, translate_exceptions @@ -27,7 +27,11 @@ from .options import limit, pretty from .session import CliSession -ITEM_TYPES = get_item_types('analytic') +BUNDLES = get_product_bundles() +all_item_types = [] +for bundle in BUNDLES: + all_item_types += [*get_item_types(bundle)] +ITEM_TYPES = set(all_item_types) @asynccontextmanager @@ -233,7 +237,7 @@ def filter(ctx, @click.pass_context @translate_exceptions @coro -@click.argument("item_types", type=types.CommaSeparatedString()) +@click.argument('item_types', type=click.Choice(ITEM_TYPES)) @click.argument("filter", type=types.JSON(), default="-", required=False) @limit @click.option('--name', type=str, help='Name of the saved search.') @@ -272,7 +276,7 @@ async def search(ctx, item_types, filter, limit, name, sort, pretty): @translate_exceptions @coro @click.argument('name') -@click.argument("item_types", type=types.CommaSeparatedString()) +@click.argument('item_types', type=click.Choice(ITEM_TYPES)) @click.argument("filter", type=types.JSON(), default="-", required=False) @click.option('--daily-email', is_flag=True, @@ -303,7 +307,7 @@ async def search_create(ctx, name, item_types, filter, daily_email, pretty): @click.pass_context @translate_exceptions @coro -@click.argument("item_types", type=types.CommaSeparatedString()) +@click.argument('item_types', type=click.Choice(ITEM_TYPES)) @click.argument('interval', type=click.Choice(STATS_INTERVAL)) @click.argument("filter", type=types.JSON(), default="-", required=False) async def stats(ctx, item_types, interval, filter): From 0ee5357deece7ab0064a58d7316577eeb0bd97ec Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Thu, 28 Jul 2022 10:47:15 -0400 Subject: [PATCH 03/30] Changed arg type to the new comma sep. choice. --- planet/cli/data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/planet/cli/data.py b/planet/cli/data.py index 47dd6c98a..e5127ddf9 100644 --- a/planet/cli/data.py +++ b/planet/cli/data.py @@ -237,7 +237,7 @@ def filter(ctx, @click.pass_context @translate_exceptions @coro -@click.argument('item_types', type=click.Choice(ITEM_TYPES)) +@click.argument('item_types', type=types.CommaSeparatedChoice(ITEM_TYPES)) @click.argument("filter", type=types.JSON(), default="-", required=False) @limit @click.option('--name', type=str, help='Name of the saved search.') @@ -276,7 +276,7 @@ async def search(ctx, item_types, filter, limit, name, sort, pretty): @translate_exceptions @coro @click.argument('name') -@click.argument('item_types', type=click.Choice(ITEM_TYPES)) +@click.argument('item_types', type=types.CommaSeparatedChoice(ITEM_TYPES)) @click.argument("filter", type=types.JSON(), default="-", required=False) @click.option('--daily-email', is_flag=True, @@ -307,7 +307,7 @@ async def search_create(ctx, name, item_types, filter, daily_email, pretty): @click.pass_context @translate_exceptions @coro -@click.argument('item_types', type=click.Choice(ITEM_TYPES)) +@click.argument('item_types', type=types.CommaSeparatedChoice(ITEM_TYPES)) @click.argument('interval', type=click.Choice(STATS_INTERVAL)) @click.argument("filter", type=types.JSON(), default="-", required=False) async def stats(ctx, item_types, interval, filter): From 08e0e57c5399f6845db77584ca3cb0de41e086b9 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Thu, 28 Jul 2022 10:48:03 -0400 Subject: [PATCH 04/30] Added new comma separeted choice class. Work in progress. Doesn't display choices, but doesn't break. --- planet/cli/types.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/planet/cli/types.py b/planet/cli/types.py index 94ced2359..a109f616b 100644 --- a/planet/cli/types.py +++ b/planet/cli/types.py @@ -32,13 +32,11 @@ def convert(self, value, param, ctx) -> List[str]: if convstr == '': self.fail('Entry cannot be an empty string.') - convlist = [part.strip() for part in convstr.split(",")] for v in convlist: if not v: self.fail(f'Empty entry encountered in "{value}".') - return convlist @@ -57,6 +55,24 @@ def convert(self, value, param, ctx) -> List[float]: return ret +class CommaSeparatedChoice(click.types.StringParamType): + def __init__(self, *args, **kwargs): + self.choice = click.types.Choice(*args, **kwargs) + + def get_metavar(self, param: "click.Parameter"): + self.choice.get_metavar(param) + + def convert(self, value, param, ctx) -> List[float]: + bad_values = CommaSeparatedString().convert(value, param, ctx) + # import pdb; pdb.set_trace() + if len(bad_values) > 1: + values = [v.strip().split("'")[1] for v in bad_values] + else: + values = bad_values + converted = [self.choice.convert(v, param, ctx) for v in values] + return converted + + class JSON(click.ParamType): """JSON specified as a string, json file filename, or stdin.""" name = 'JSON' From e00716a4bd6032d1d22b7439fbbb663ecd9b0bd0 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Fri, 5 Aug 2022 10:30:43 -0400 Subject: [PATCH 05/30] Removed item_type retrieval, added epilog to search functions, replaced CommaSeparatedChoice with CommaSeparatedString. --- planet/cli/data.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/planet/cli/data.py b/planet/cli/data.py index e5127ddf9..9b49e1b76 100644 --- a/planet/cli/data.py +++ b/planet/cli/data.py @@ -19,7 +19,7 @@ from planet import data_filter, DataClient from planet.clients.data import SEARCH_SORT, SEARCH_SORT_DEFAULT, STATS_INTERVAL -from planet.specs import get_item_types, get_product_bundles +from planet.specs import get_item_types from . import types from .cmds import coro, translate_exceptions @@ -27,11 +27,7 @@ from .options import limit, pretty from .session import CliSession -BUNDLES = get_product_bundles() -all_item_types = [] -for bundle in BUNDLES: - all_item_types += [*get_item_types(bundle)] -ITEM_TYPES = set(all_item_types) +valid_item_string = "Valid entries for ITEM_TYPES: " + "|".join(get_item_types()) @asynccontextmanager @@ -233,11 +229,11 @@ def filter(ctx, echo_json(filt, pretty) -@data.command() +@data.command(epilog=valid_item_string) @click.pass_context @translate_exceptions @coro -@click.argument('item_types', type=types.CommaSeparatedChoice(ITEM_TYPES)) +@click.argument('item_types', type=types.CommaSeparatedString()) @click.argument("filter", type=types.JSON(), default="-", required=False) @limit @click.option('--name', type=str, help='Name of the saved search.') @@ -271,12 +267,12 @@ async def search(ctx, item_types, filter, limit, name, sort, pretty): echo_json(item, pretty) -@data.command() +@data.command(epilog=valid_item_string) @click.pass_context @translate_exceptions @coro @click.argument('name') -@click.argument('item_types', type=types.CommaSeparatedChoice(ITEM_TYPES)) +@click.argument('item_types', type=types.CommaSeparatedString()) @click.argument("filter", type=types.JSON(), default="-", required=False) @click.option('--daily-email', is_flag=True, @@ -303,11 +299,11 @@ async def search_create(ctx, name, item_types, filter, daily_email, pretty): echo_json(items, pretty) -@data.command() +@data.command(epilog=valid_item_string) @click.pass_context @translate_exceptions @coro -@click.argument('item_types', type=types.CommaSeparatedChoice(ITEM_TYPES)) +@click.argument('item_types', type=types.CommaSeparatedString()) @click.argument('interval', type=click.Choice(STATS_INTERVAL)) @click.argument("filter", type=types.JSON(), default="-", required=False) async def stats(ctx, item_types, interval, filter): From e07a35a70179bdd82a1d4743f17286ee48180ae7 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Fri, 5 Aug 2022 10:31:16 -0400 Subject: [PATCH 06/30] Removed the CommaSeparatedChoice class. --- planet/cli/types.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/planet/cli/types.py b/planet/cli/types.py index a109f616b..fb070a4e7 100644 --- a/planet/cli/types.py +++ b/planet/cli/types.py @@ -55,24 +55,6 @@ def convert(self, value, param, ctx) -> List[float]: return ret -class CommaSeparatedChoice(click.types.StringParamType): - def __init__(self, *args, **kwargs): - self.choice = click.types.Choice(*args, **kwargs) - - def get_metavar(self, param: "click.Parameter"): - self.choice.get_metavar(param) - - def convert(self, value, param, ctx) -> List[float]: - bad_values = CommaSeparatedString().convert(value, param, ctx) - # import pdb; pdb.set_trace() - if len(bad_values) > 1: - values = [v.strip().split("'")[1] for v in bad_values] - else: - values = bad_values - converted = [self.choice.convert(v, param, ctx) for v in values] - return converted - - class JSON(click.ParamType): """JSON specified as a string, json file filename, or stdin.""" name = 'JSON' From 089443bcaf94d87b4bfda091b0967e36218a4dc0 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Fri, 5 Aug 2022 10:32:38 -0400 Subject: [PATCH 07/30] Added validation to get_match(), moved item_type retrival to get_item_types(). --- planet/specs.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/planet/specs.py b/planet/specs.py index f3971ab6a..013763d8a 100644 --- a/planet/specs.py +++ b/planet/specs.py @@ -93,10 +93,10 @@ def get_match(test_entry, spec_entries): is hard to remember but must be exact otherwise the API throws an exception.''' try: - match = next(t for t in spec_entries - if t.lower() == test_entry.lower()) + match = next(e for e in spec_entries + if e.lower() == test_entry.lower()) except (StopIteration): - raise NoMatchException + raise NoMatchException('{test_entry} should be one of {spec_entries}') return match @@ -107,10 +107,18 @@ def get_product_bundles(): return spec['bundles'].keys() -def get_item_types(product_bundle): +def get_item_types(product_bundle=None): '''Get item types supported by Orders API for the given product bundle.''' spec = _get_product_bundle_spec() - return spec['bundles'][product_bundle]['assets'].keys() + if product_bundle: + item_types = spec['bundles'][product_bundle]['assets'].keys() + else: + product_bundle = get_product_bundles() + all_item_types = [] + for bundle in product_bundle: + all_item_types += [*spec['bundles'][bundle]['assets'].keys()] + item_types = set(all_item_types) + return item_types def _get_product_bundle_spec(): From 5043b0f70f9fac4455fce39461b09962ba757d4c Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Fri, 5 Aug 2022 10:33:37 -0400 Subject: [PATCH 08/30] Fixed test - test shouldn't have had a tuple, should have a comma separated string. --- tests/integration/test_data_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_data_cli.py b/tests/integration/test_data_cli.py index 60a1a6439..22c99a853 100644 --- a/tests/integration/test_data_cli.py +++ b/tests/integration/test_data_cli.py @@ -376,7 +376,7 @@ def test_data_search_cmd_filter_invalid_json(invoke, item_types, filter): @respx.mock @pytest.mark.parametrize( - "item_types", ['PSScene', 'SkySatScene', ('PSScene', 'SkySatScene')]) + "item_types", ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) def test_data_search_cmd_filter_success(invoke, item_types): """Test for planet data search_quick. Test with multiple item_types. Test should succeed as filter contains valid JSON.""" From 7082c57ae30b72a4f1482c82ca87ee1e0291d575 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Fri, 5 Aug 2022 11:20:34 -0400 Subject: [PATCH 09/30] Fixed linting. --- tests/integration/test_data_cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_data_cli.py b/tests/integration/test_data_cli.py index 22c99a853..8465af4f9 100644 --- a/tests/integration/test_data_cli.py +++ b/tests/integration/test_data_cli.py @@ -375,8 +375,8 @@ def test_data_search_cmd_filter_invalid_json(invoke, item_types, filter): @respx.mock -@pytest.mark.parametrize( - "item_types", ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) +@pytest.mark.parametrize("item_types", + ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) def test_data_search_cmd_filter_success(invoke, item_types): """Test for planet data search_quick. Test with multiple item_types. Test should succeed as filter contains valid JSON.""" From 95fc7880844ca9f93943ef4cb3b7c7c23c2bf821 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Fri, 5 Aug 2022 11:23:35 -0400 Subject: [PATCH 10/30] Fixed linting. --- planet/cli/data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/planet/cli/data.py b/planet/cli/data.py index 9b49e1b76..b6918be38 100644 --- a/planet/cli/data.py +++ b/planet/cli/data.py @@ -27,7 +27,8 @@ from .options import limit, pretty from .session import CliSession -valid_item_string = "Valid entries for ITEM_TYPES: " + "|".join(get_item_types()) +valid_item_string = "Valid entries for ITEM_TYPES: " + "|".join( + get_item_types()) @asynccontextmanager From 427189ec13aba7cedbf3d56ee589e2c404db54e5 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Tue, 9 Aug 2022 14:04:52 -0400 Subject: [PATCH 11/30] Undo accidental string change. --- planet/cli/data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/planet/cli/data.py b/planet/cli/data.py index b6918be38..57e0e7e6b 100644 --- a/planet/cli/data.py +++ b/planet/cli/data.py @@ -234,7 +234,7 @@ def filter(ctx, @click.pass_context @translate_exceptions @coro -@click.argument('item_types', type=types.CommaSeparatedString()) +@click.argument("item_types", type=types.CommaSeparatedString()) @click.argument("filter", type=types.JSON(), default="-", required=False) @limit @click.option('--name', type=str, help='Name of the saved search.') @@ -273,7 +273,7 @@ async def search(ctx, item_types, filter, limit, name, sort, pretty): @translate_exceptions @coro @click.argument('name') -@click.argument('item_types', type=types.CommaSeparatedString()) +@click.argument("item_types", type=types.CommaSeparatedString()) @click.argument("filter", type=types.JSON(), default="-", required=False) @click.option('--daily-email', is_flag=True, @@ -304,7 +304,7 @@ async def search_create(ctx, name, item_types, filter, daily_email, pretty): @click.pass_context @translate_exceptions @coro -@click.argument('item_types', type=types.CommaSeparatedString()) +@click.argument("item_types", type=types.CommaSeparatedString()) @click.argument('interval', type=click.Choice(STATS_INTERVAL)) @click.argument("filter", type=types.JSON(), default="-", required=False) async def stats(ctx, item_types, interval, filter): From eab311fdac80c825091e8d58288754fbce8fc0ac Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Tue, 9 Aug 2022 14:06:19 -0400 Subject: [PATCH 12/30] Undo accidental string change. --- planet/cli/types.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/planet/cli/types.py b/planet/cli/types.py index fb070a4e7..27846b601 100644 --- a/planet/cli/types.py +++ b/planet/cli/types.py @@ -29,11 +29,9 @@ def convert(self, value, param, ctx) -> List[str]: convlist = value else: convstr = super().convert(value, param, ctx) - if convstr == '': self.fail('Entry cannot be an empty string.') convlist = [part.strip() for part in convstr.split(",")] - for v in convlist: if not v: self.fail(f'Empty entry encountered in "{value}".') From 60a73775a52e0f7b78c3481ad4ba52e78ad844de Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Tue, 9 Aug 2022 15:43:37 -0400 Subject: [PATCH 13/30] Added test for epilog. --- tests/integration/test_data_cli.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/integration/test_data_cli.py b/tests/integration/test_data_cli.py index 8465af4f9..cf5bcccac 100644 --- a/tests/integration/test_data_cli.py +++ b/tests/integration/test_data_cli.py @@ -23,6 +23,7 @@ import pytest from planet.cli import cli +from planet.specs import get_item_types LOGGER = logging.getLogger(__name__) @@ -55,6 +56,23 @@ def test_data_command_registered(invoke): # Add other sub-commands here. +def test_data_search_command_registered(invoke): + """planet-data command prints help and usage message.""" + runner = CliRunner() + result = invoke(["search", "--help"], runner=runner) + all_item_types = [a for a in get_item_types()] + assert result.exit_code == 0 + assert "Usage" in result.output + assert "limit" in result.output + assert "name" in result.output + assert "sort" in result.output + assert "pretty" in result.output + assert "help" in result.output + for a in all_item_types: + assert a in result.output.replace('\n', '').replace(' ', '') + # Add other sub-commands here. + + PERMISSION_FILTER = {"type": "PermissionFilter", "config": ["assets:download"]} STD_QUALITY_FILTER = { "type": "StringInFilter", From 76adcdf32b14ffe7d5b064e6ea01700ce097a1ca Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Tue, 9 Aug 2022 15:53:29 -0400 Subject: [PATCH 14/30] Tests to test the check_item_type callback function in data CLI. --- tests/unit/test_data_item_type.py | 56 +++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/unit/test_data_item_type.py diff --git a/tests/unit/test_data_item_type.py b/tests/unit/test_data_item_type.py new file mode 100644 index 000000000..f5107d3bc --- /dev/null +++ b/tests/unit/test_data_item_type.py @@ -0,0 +1,56 @@ +# Copyright 2020 Planet Labs, Inc. +# 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.data import check_item_types + +LOGGER = logging.getLogger(__name__) + + +class MockContext: + + def __init__(self): + self.obj = {} + + +@pytest.mark.parametrize("item_type", + [ + 'myd09ga', + 'sentinel1', + 'rescene', + 'myd09gq', + 'psorthotile', + 'landsat8l1g', + 'reorthotile', + 'sentinel2l1c', + 'skysatscene', + 'skysatcollect', + 'mod09ga', + 'psscene3band', + 'mod09gq', + 'psscene4band', + 'psscene' + ]) +def test_item_type_success(item_type): + ctx = MockContext() + with pytest.raises(click.BadParameter): + check_item_types(ctx, 'item_type', item_type) + + +def test_item_type_fail(): + ctx = MockContext() + with pytest.raises(click.BadParameter): + check_item_types(ctx, 'item_type', "bad_item_type") From a6f98db165154150259ab3591bac5db3a0f67547 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Tue, 9 Aug 2022 16:00:28 -0400 Subject: [PATCH 15/30] Fixed broken test, item_type should be a comma sep. string, not a tuple. --- tests/integration/test_data_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_data_cli.py b/tests/integration/test_data_cli.py index cf5bcccac..492427e3a 100644 --- a/tests/integration/test_data_cli.py +++ b/tests/integration/test_data_cli.py @@ -649,7 +649,7 @@ def test_data_stats_invalid_interval(invoke, item_types, interval, exit_code): @respx.mock @pytest.mark.parametrize( - "item_types", ['PSScene', 'SkySatScene', ('PSScene', 'SkySatScene')]) + "item_types", ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) @pytest.mark.parametrize("interval", ['hour', 'day', 'week', 'month', 'year']) def test_data_stats_success(invoke, item_types, interval): """Test for planet data stats. Test with multiple item_types. From e2056e2310512e4c124e56c0445bcbd5ea041e29 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Tue, 9 Aug 2022 16:03:06 -0400 Subject: [PATCH 16/30] Fixed broken test, item_type should be a comma sep. string, not a tuple. --- tests/integration/test_data_cli.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/test_data_cli.py b/tests/integration/test_data_cli.py index 492427e3a..701c75470 100644 --- a/tests/integration/test_data_cli.py +++ b/tests/integration/test_data_cli.py @@ -377,7 +377,7 @@ def test_data_filter_update(invoke, assert_and_filters_equal, default_filters): @pytest.mark.asyncio @pytest.mark.parametrize("filter", ['{1:1}', '{"foo"}']) @pytest.mark.parametrize( - "item_types", ['PSScene', 'SkySatScene', ('PSScene', 'SkySatScene')]) + "item_types", ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) def test_data_search_cmd_filter_invalid_json(invoke, item_types, filter): """Test for planet data search_quick. Test with multiple item_types. Test should fail as filter does not contain valid JSON.""" @@ -514,7 +514,7 @@ def test_data_search_cmd_limit(invoke, @pytest.mark.asyncio @pytest.mark.parametrize("filter", ['{1:1}', '{"foo"}']) @pytest.mark.parametrize( - "item_types", ['PSScene', 'SkySatScene', ('PSScene', 'SkySatScene')]) + "item_types", ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) def test_data_search_create_filter_invalid_json(invoke, item_types, filter): """Test for planet data search_create. Test with multiple item_types. Test should fail as filter does not contain valid JSON.""" @@ -533,7 +533,7 @@ def test_data_search_create_filter_invalid_json(invoke, item_types, filter): @respx.mock @pytest.mark.parametrize( - "item_types", ['PSScene', 'SkySatScene', ('PSScene', 'SkySatScene')]) + "item_types", ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) def test_data_search_create_filter_success(invoke, item_types): """Test for planet data search_create. Test with multiple item_types. Test should succeed as filter contains valid JSON.""" @@ -620,7 +620,7 @@ def test_data_stats_invalid_filter(invoke, filter): @respx.mock @pytest.mark.parametrize( - "item_types", ['PSScene', 'SkySatScene', ('PSScene', 'SkySatScene')]) + "item_types", ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) @pytest.mark.parametrize("interval, exit_code", [(None, 1), ('hou', 2), ('hour', 0)]) def test_data_stats_invalid_interval(invoke, item_types, interval, exit_code): From 3840db0265b4cf9bebb7e86cb1d40760a2e462b8 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Tue, 9 Aug 2022 16:08:05 -0400 Subject: [PATCH 17/30] Set lowering command in set difference. Otherwise, it makes all item_types lower and breaks tests. --- planet/cli/data.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/planet/cli/data.py b/planet/cli/data.py index 57e0e7e6b..cedc7c411 100644 --- a/planet/cli/data.py +++ b/planet/cli/data.py @@ -62,6 +62,15 @@ def assets_to_filter(ctx, param, assets: List[str]) -> Optional[dict]: return data_filter.asset_filter(assets) if assets else None +def check_item_types(ctx, param, value): + all_item_types = get_item_types() + set_diff = set([v.lower() for v in value]) - set([a.lower() for a in all_item_types]) + if set_diff: + raise click.BadParameter(f'{value} should be one of {all_item_types}') + else: + return value + + def date_range_to_filter(ctx, param, values) -> Optional[List[dict]]: def _func(obj): @@ -234,7 +243,9 @@ def filter(ctx, @click.pass_context @translate_exceptions @coro -@click.argument("item_types", type=types.CommaSeparatedString()) +@click.argument("item_types", + type=types.CommaSeparatedString(), + callback=check_item_types) @click.argument("filter", type=types.JSON(), default="-", required=False) @limit @click.option('--name', type=str, help='Name of the saved search.') @@ -273,7 +284,9 @@ async def search(ctx, item_types, filter, limit, name, sort, pretty): @translate_exceptions @coro @click.argument('name') -@click.argument("item_types", type=types.CommaSeparatedString()) +@click.argument("item_types", + type=types.CommaSeparatedString(), + callback=check_item_types) @click.argument("filter", type=types.JSON(), default="-", required=False) @click.option('--daily-email', is_flag=True, @@ -304,7 +317,9 @@ async def search_create(ctx, name, item_types, filter, daily_email, pretty): @click.pass_context @translate_exceptions @coro -@click.argument("item_types", type=types.CommaSeparatedString()) +@click.argument("item_types", + type=types.CommaSeparatedString(), + callback=check_item_types) @click.argument('interval', type=click.Choice(STATS_INTERVAL)) @click.argument("filter", type=types.JSON(), default="-", required=False) async def stats(ctx, item_types, interval, filter): From ba48f629c94ec9ee76504a893fa3a3134974d12e Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Tue, 9 Aug 2022 16:10:05 -0400 Subject: [PATCH 18/30] Fixed linting. --- planet/cli/data.py | 3 ++- tests/integration/test_data_cli.py | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/planet/cli/data.py b/planet/cli/data.py index cedc7c411..f45b76324 100644 --- a/planet/cli/data.py +++ b/planet/cli/data.py @@ -64,7 +64,8 @@ def assets_to_filter(ctx, param, assets: List[str]) -> Optional[dict]: def check_item_types(ctx, param, value): all_item_types = get_item_types() - set_diff = set([v.lower() for v in value]) - set([a.lower() for a in all_item_types]) + set_diff = set([v.lower() + for v in value]) - set([a.lower() for a in all_item_types]) if set_diff: raise click.BadParameter(f'{value} should be one of {all_item_types}') else: diff --git a/tests/integration/test_data_cli.py b/tests/integration/test_data_cli.py index 701c75470..1436277a9 100644 --- a/tests/integration/test_data_cli.py +++ b/tests/integration/test_data_cli.py @@ -376,8 +376,8 @@ def test_data_filter_update(invoke, assert_and_filters_equal, default_filters): @respx.mock @pytest.mark.asyncio @pytest.mark.parametrize("filter", ['{1:1}', '{"foo"}']) -@pytest.mark.parametrize( - "item_types", ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) +@pytest.mark.parametrize("item_types", + ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) def test_data_search_cmd_filter_invalid_json(invoke, item_types, filter): """Test for planet data search_quick. Test with multiple item_types. Test should fail as filter does not contain valid JSON.""" @@ -513,8 +513,8 @@ def test_data_search_cmd_limit(invoke, @respx.mock @pytest.mark.asyncio @pytest.mark.parametrize("filter", ['{1:1}', '{"foo"}']) -@pytest.mark.parametrize( - "item_types", ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) +@pytest.mark.parametrize("item_types", + ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) def test_data_search_create_filter_invalid_json(invoke, item_types, filter): """Test for planet data search_create. Test with multiple item_types. Test should fail as filter does not contain valid JSON.""" @@ -532,8 +532,8 @@ def test_data_search_create_filter_invalid_json(invoke, item_types, filter): @respx.mock -@pytest.mark.parametrize( - "item_types", ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) +@pytest.mark.parametrize("item_types", + ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) def test_data_search_create_filter_success(invoke, item_types): """Test for planet data search_create. Test with multiple item_types. Test should succeed as filter contains valid JSON.""" @@ -619,8 +619,8 @@ def test_data_stats_invalid_filter(invoke, filter): @respx.mock -@pytest.mark.parametrize( - "item_types", ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) +@pytest.mark.parametrize("item_types", + ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) @pytest.mark.parametrize("interval, exit_code", [(None, 1), ('hou', 2), ('hour', 0)]) def test_data_stats_invalid_interval(invoke, item_types, interval, exit_code): @@ -648,8 +648,8 @@ def test_data_stats_invalid_interval(invoke, item_types, interval, exit_code): @respx.mock -@pytest.mark.parametrize( - "item_types", ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) +@pytest.mark.parametrize("item_types", + ['PSScene', 'SkySatScene', 'PSScene, SkySatScene']) @pytest.mark.parametrize("interval", ['hour', 'day', 'week', 'month', 'year']) def test_data_stats_success(invoke, item_types, interval): """Test for planet data stats. Test with multiple item_types. From 02f48607a37740361f4280e30a62b32698940049 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 10 Aug 2022 08:45:56 -0400 Subject: [PATCH 19/30] Undo extra spaces. --- planet/cli/types.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/planet/cli/types.py b/planet/cli/types.py index 27846b601..94ced2359 100644 --- a/planet/cli/types.py +++ b/planet/cli/types.py @@ -29,12 +29,16 @@ def convert(self, value, param, ctx) -> List[str]: convlist = value else: convstr = super().convert(value, param, ctx) + if convstr == '': self.fail('Entry cannot be an empty string.') + convlist = [part.strip() for part in convstr.split(",")] + for v in convlist: if not v: self.fail(f'Empty entry encountered in "{value}".') + return convlist From fa8c6fdbdc6c3d732604b18f711597888ea45174 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 10 Aug 2022 08:57:54 -0400 Subject: [PATCH 20/30] Removed copyright 2020 statement, since developed in 2022. --- tests/unit/test_data_item_type.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/test_data_item_type.py b/tests/unit/test_data_item_type.py index f5107d3bc..76b8e6ece 100644 --- a/tests/unit/test_data_item_type.py +++ b/tests/unit/test_data_item_type.py @@ -1,4 +1,3 @@ -# Copyright 2020 Planet Labs, Inc. # Copyright 2022 Planet Labs PBC. # # Licensed under the Apache License, Version 2.0 (the "License"); From 2a83f674599ae03c67f6e4ad2681a9b6fab3e5b6 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 10 Aug 2022 08:58:55 -0400 Subject: [PATCH 21/30] Added test for get_item_types without bundle. --- tests/unit/test_specs.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_specs.py b/tests/unit/test_specs.py index 6f721e9de..7bf83a2fa 100644 --- a/tests/unit/test_specs.py +++ b/tests/unit/test_specs.py @@ -22,7 +22,24 @@ TEST_PRODUCT_BUNDLE = 'visual' # must be a valid item type for TEST_PRODUCT_BUNDLE -TEST_ITEM_TYPE = 'PSScene' +TEST_ITEM_TYPE = 'PSScene'' +ALL_ITEM_TYPES = [ + 'PSOrthoTile', + 'Sentinel1', + 'REOrthoTile', + 'PSScene', + 'PSScene4Band', + 'Landsat8L1G', + 'PSScene3Band', + 'REScene', + 'MOD09GA', + 'MYD09GA', + 'MOD09GQ', + 'SkySatCollect', + 'Sentinel2L1C', + 'MYD09GQ', + 'SkySatScene' +] def test_get_type_match(): @@ -90,6 +107,12 @@ def test_get_product_bundles(): assert TEST_PRODUCT_BUNDLE in bundles -def test_get_item_types(): +def test_get_item_types_with_bundle(): item_types = specs.get_item_types(TEST_PRODUCT_BUNDLE) assert TEST_ITEM_TYPE in item_types + + +def test_get_item_types_without_bundle(): + item_types = specs.get_item_types() + for item in item_types: + assert item in ALL_ITEM_TYPES From b488cc3c1b46e67ad40bcf7e86cedf1d46484dbe Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 10 Aug 2022 08:59:52 -0400 Subject: [PATCH 22/30] Removed accidentally added extra single quote. --- tests/unit/test_specs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_specs.py b/tests/unit/test_specs.py index 7bf83a2fa..8049116a6 100644 --- a/tests/unit/test_specs.py +++ b/tests/unit/test_specs.py @@ -22,7 +22,7 @@ TEST_PRODUCT_BUNDLE = 'visual' # must be a valid item type for TEST_PRODUCT_BUNDLE -TEST_ITEM_TYPE = 'PSScene'' +TEST_ITEM_TYPE = 'PSScene' ALL_ITEM_TYPES = [ 'PSOrthoTile', 'Sentinel1', From e0a75720c4be247fb27a876cd00406253826eb9f Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 10 Aug 2022 09:09:45 -0400 Subject: [PATCH 23/30] More explicit in get_item_types and added docs. --- planet/cli/data.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/planet/cli/data.py b/planet/cli/data.py index f45b76324..416f09e0e 100644 --- a/planet/cli/data.py +++ b/planet/cli/data.py @@ -62,14 +62,17 @@ def assets_to_filter(ctx, param, assets: List[str]) -> Optional[dict]: return data_filter.asset_filter(assets) if assets else None -def check_item_types(ctx, param, value): +def check_item_types(ctx, param, item_types): + # Get all item types available all_item_types = get_item_types() - set_diff = set([v.lower() - for v in value]) - set([a.lower() for a in all_item_types]) + # Set difference between given item types and all item types + set_diff = set([item.lower() for item in item_types]) - set( + [a.lower() for a in all_item_types]) if set_diff: - raise click.BadParameter(f'{value} should be one of {all_item_types}') + raise click.BadParameter( + f'{item_types} should be one of {all_item_types}') else: - return value + return item_types def date_range_to_filter(ctx, param, values) -> Optional[List[dict]]: From 2417a828c1c4820d8fdc6c8f7895df5495f7f063 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 10 Aug 2022 09:14:50 -0400 Subject: [PATCH 24/30] Removed duplicated variables. --- planet/cli/data.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/planet/cli/data.py b/planet/cli/data.py index 416f09e0e..ab19548f7 100644 --- a/planet/cli/data.py +++ b/planet/cli/data.py @@ -19,7 +19,7 @@ from planet import data_filter, DataClient from planet.clients.data import SEARCH_SORT, SEARCH_SORT_DEFAULT, STATS_INTERVAL -from planet.specs import get_item_types +from tests.unit.test_specs import ALL_ITEM_TYPES from . import types from .cmds import coro, translate_exceptions @@ -27,8 +27,7 @@ from .options import limit, pretty from .session import CliSession -valid_item_string = "Valid entries for ITEM_TYPES: " + "|".join( - get_item_types()) +valid_item_string = "Valid entries for ITEM_TYPES: " + "|".join(ALL_ITEM_TYPES) @asynccontextmanager @@ -62,15 +61,13 @@ def assets_to_filter(ctx, param, assets: List[str]) -> Optional[dict]: return data_filter.asset_filter(assets) if assets else None -def check_item_types(ctx, param, item_types): - # Get all item types available - all_item_types = get_item_types() +def check_item_types(ctx, param, item_types) -> Optional[List[dict]]: # Set difference between given item types and all item types set_diff = set([item.lower() for item in item_types]) - set( - [a.lower() for a in all_item_types]) + [a.lower() for a in ALL_ITEM_TYPES]) if set_diff: raise click.BadParameter( - f'{item_types} should be one of {all_item_types}') + f'{item_types} should be one of {ALL_ITEM_TYPES}') else: return item_types From a6c7f80d37b792725bd73878b6088258ad0a08bc Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 10 Aug 2022 09:15:16 -0400 Subject: [PATCH 25/30] Improved docs. --- tests/integration/test_data_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_data_cli.py b/tests/integration/test_data_cli.py index 1436277a9..f146cf0fb 100644 --- a/tests/integration/test_data_cli.py +++ b/tests/integration/test_data_cli.py @@ -57,7 +57,7 @@ def test_data_command_registered(invoke): def test_data_search_command_registered(invoke): - """planet-data command prints help and usage message.""" + """planet-data search command prints help and usage message.""" runner = CliRunner() result = invoke(["search", "--help"], runner=runner) all_item_types = [a for a in get_item_types()] From 994931baf29552bdd6c2be53a2ec07d7a2ca0908 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 10 Aug 2022 09:17:50 -0400 Subject: [PATCH 26/30] Got all item types from planet.specs.get_item_types, not from a test. --- planet/cli/data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/planet/cli/data.py b/planet/cli/data.py index ab19548f7..ab461d617 100644 --- a/planet/cli/data.py +++ b/planet/cli/data.py @@ -19,7 +19,7 @@ from planet import data_filter, DataClient from planet.clients.data import SEARCH_SORT, SEARCH_SORT_DEFAULT, STATS_INTERVAL -from tests.unit.test_specs import ALL_ITEM_TYPES +from planet.specs import get_item_types from . import types from .cmds import coro, translate_exceptions @@ -27,6 +27,7 @@ from .options import limit, pretty from .session import CliSession +ALL_ITEM_TYPES = get_item_types() valid_item_string = "Valid entries for ITEM_TYPES: " + "|".join(ALL_ITEM_TYPES) From ff259a3e343044e108509c30d3400bd14b66839e Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 10 Aug 2022 09:21:20 -0400 Subject: [PATCH 27/30] Improved docs. --- planet/specs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planet/specs.py b/planet/specs.py index 013763d8a..f87eb8779 100644 --- a/planet/specs.py +++ b/planet/specs.py @@ -108,7 +108,7 @@ def get_product_bundles(): def get_item_types(product_bundle=None): - '''Get item types supported by Orders API for the given product bundle.''' + '''If given product bundle, get specific item types supported by Orders API. Otherwise, get all item types supported by Orders API.''' spec = _get_product_bundle_spec() if product_bundle: item_types = spec['bundles'][product_bundle]['assets'].keys() From 7bd17c10e7afdcb10a2910a70f7357104b9a7e28 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 10 Aug 2022 09:22:28 -0400 Subject: [PATCH 28/30] Improved docs linting. --- planet/specs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/planet/specs.py b/planet/specs.py index f87eb8779..0f8c1550b 100644 --- a/planet/specs.py +++ b/planet/specs.py @@ -108,7 +108,8 @@ def get_product_bundles(): def get_item_types(product_bundle=None): - '''If given product bundle, get specific item types supported by Orders API. Otherwise, get all item types supported by Orders API.''' + '''If given product bundle, get specific item types supported by Orders + API. Otherwise, get all item types supported by Orders API.''' spec = _get_product_bundle_spec() if product_bundle: item_types = spec['bundles'][product_bundle]['assets'].keys() From 28851ed8187c37e8b6c4c070bf94ff5f2ac86ac5 Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 10 Aug 2022 14:04:54 -0400 Subject: [PATCH 29/30] Update tests/unit/test_specs.py Co-authored-by: Sean Gillies --- tests/unit/test_specs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_specs.py b/tests/unit/test_specs.py index 8049116a6..21b4e5717 100644 --- a/tests/unit/test_specs.py +++ b/tests/unit/test_specs.py @@ -108,7 +108,7 @@ def test_get_product_bundles(): def test_get_item_types_with_bundle(): - item_types = specs.get_item_types(TEST_PRODUCT_BUNDLE) + item_types = specs.get_item_types(product_bundle=TEST_PRODUCT_BUNDLE) assert TEST_ITEM_TYPE in item_types From 9ca54c25cf1edf7bcaacb215d929d54c8180392a Mon Sep 17 00:00:00 2001 From: Kevin Lacaille Date: Wed, 10 Aug 2022 14:24:32 -0400 Subject: [PATCH 30/30] Fixed success test not fail on BadParameter. --- tests/unit/test_data_item_type.py | 38 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/unit/test_data_item_type.py b/tests/unit/test_data_item_type.py index 76b8e6ece..41d7b78d5 100644 --- a/tests/unit/test_data_item_type.py +++ b/tests/unit/test_data_item_type.py @@ -25,28 +25,28 @@ def __init__(self): self.obj = {} -@pytest.mark.parametrize("item_type", +@pytest.mark.parametrize("item_types", [ - 'myd09ga', - 'sentinel1', - 'rescene', - 'myd09gq', - 'psorthotile', - 'landsat8l1g', - 'reorthotile', - 'sentinel2l1c', - 'skysatscene', - 'skysatcollect', - 'mod09ga', - 'psscene3band', - 'mod09gq', - 'psscene4band', - 'psscene' + 'PSScene3Band', + 'MOD09GQ', + 'MYD09GA', + 'REOrthoTile', + 'SkySatCollect', + 'SkySatScene', + 'MYD09GQ', + 'Landsat8L1G', + 'Sentinel2L1C', + 'MOD09GA', + 'Sentinel1', + 'PSScene', + 'PSOrthoTile', + 'PSScene4Band', + 'REScene' ]) -def test_item_type_success(item_type): +def test_item_type_success(item_types): ctx = MockContext() - with pytest.raises(click.BadParameter): - check_item_types(ctx, 'item_type', item_type) + result = check_item_types(ctx, 'item_types', [item_types]) + assert result == [item_types] def test_item_type_fail():