From fd9fb5577b1c6c1e620448d5ce4f80d94dbc5f32 Mon Sep 17 00:00:00 2001 From: Gerald Walter Irsiegler Date: Wed, 3 Jan 2024 10:24:41 +0100 Subject: [PATCH 1/9] Fix: Allow integer values for crs parsing in Bounding Box (#79) Co-authored-by: Gerald Walter Irsiegler --- openeo_pg_parser_networkx/pg_schema.py | 6 +++--- pyproject.toml | 2 +- tests/test_pg_parser.py | 22 +++++++++++++++++++++- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/openeo_pg_parser_networkx/pg_schema.py b/openeo_pg_parser_networkx/pg_schema.py index 994e237..d68a73f 100644 --- a/openeo_pg_parser_networkx/pg_schema.py +++ b/openeo_pg_parser_networkx/pg_schema.py @@ -110,7 +110,7 @@ class PGEdgeType(str, Enum): def parse_crs(v) -> pyproj.CRS: - if v is None or v.strip() == "": + if not isinstance(v, int) and (v is None or v.strip() == ""): return DEFAULT_CRS else: try: @@ -122,7 +122,7 @@ def parse_crs(v) -> pyproj.CRS: raise e -def crs_validator(field: str) -> classmethod: +def crs_validator(field: Union[str, int]) -> classmethod: decorator = validator(field, allow_reuse=True, pre=True, always=True) validator_func = decorator(parse_crs) return validator_func @@ -135,7 +135,7 @@ class BoundingBox(BaseModel, arbitrary_types_allowed=True): south: float base: Optional[float] height: Optional[float] - crs: Optional[str] + crs: Optional[Union[str, int]] # validators _parse_crs: classmethod = crs_validator('crs') diff --git a/pyproject.toml b/pyproject.toml index 904da81..188a577 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "openeo-pg-parser-networkx" -version = "2023.11.0" +version = "2024.1.1" description = "Parse OpenEO process graphs from JSON to traversible Python objects." authors = ["Lukas Weidenholzer ", "Sean Hoyal ", "Valentina Hutter ", "Gerald Irsiegler "] diff --git a/tests/test_pg_parser.py b/tests/test_pg_parser.py index 1391d3e..ccc45d3 100644 --- a/tests/test_pg_parser.py +++ b/tests/test_pg_parser.py @@ -121,9 +121,15 @@ def test_bounding_box(get_process_graph_with_args): assert parsed_arg.crs == pyproj.CRS.from_user_input('EPSG:2025').to_wkt() +def test_pydantic_loading(): + test_extent = {'west': 0, 'east': 10, 'south': 0, 'north': 10} + test_bb = BoundingBox(**test_extent) + assert test_bb.crs == DEFAULT_CRS + + def test_bounding_box_no_crs(get_process_graph_with_args): pg = get_process_graph_with_args( - {'spatial_extent': {'west': 0, 'east': 10, 'south': 0, 'north': 10, 'crs': ""}} + {'spatial_extent': {'west': 0, 'east': 10, 'south': 0, 'north': 10}} ) parsed_arg = ( ProcessGraph.parse_obj(pg) @@ -153,6 +159,20 @@ def test_bounding_box_with_faulty_crs(get_process_graph_with_args): ] +def test_bounding_box_int_crs(get_process_graph_with_args): + pg = get_process_graph_with_args( + {'spatial_extent': {'west': 0, 'east': 10, 'south': 0, 'north': 10, 'crs': 4326}} + ) + parsed_arg = ( + ProcessGraph.parse_obj(pg) + .process_graph[TEST_NODE_KEY] + .arguments["spatial_extent"] + ) + assert isinstance(parsed_arg, BoundingBox) + assert isinstance(parsed_arg.crs, str) + assert parsed_arg.crs == DEFAULT_CRS + + @pytest.mark.skip( reason="Not passing because of https://github.com/developmentseed/geojson-pydantic/issues/92" ) From 50e71097929086beca7377ac6d909831a2ecb87c Mon Sep 17 00:00:00 2001 From: Gerald Walter Irsiegler Date: Tue, 5 Mar 2024 14:55:36 +0100 Subject: [PATCH 2/9] Fix: Fixed same level from_node resolving (#84) --- openeo_pg_parser_networkx/resolving_utils.py | 8 + pyproject.toml | 2 +- resolved_gfm_graph.json | 1 + .../data/res_tests/resolved/resolved_gfm.json | 51 +++++ tests/data/res_tests/udps/gfm.json | 196 ++++++++++++++++++ .../res_tests/unresolved/unresolved_gfm.json | 19 ++ tests/test_pg_resolving.py | 35 +++- 7 files changed, 309 insertions(+), 3 deletions(-) create mode 100644 resolved_gfm_graph.json create mode 100644 tests/data/res_tests/resolved/resolved_gfm.json create mode 100644 tests/data/res_tests/udps/gfm.json create mode 100644 tests/data/res_tests/unresolved/unresolved_gfm.json diff --git a/openeo_pg_parser_networkx/resolving_utils.py b/openeo_pg_parser_networkx/resolving_utils.py index 465ff07..78d8ff8 100644 --- a/openeo_pg_parser_networkx/resolving_utils.py +++ b/openeo_pg_parser_networkx/resolving_utils.py @@ -185,6 +185,7 @@ def _fill_in_processes( _remap_names( process_graph=process_graph, process_replacement_id=process_replacement_id ) + _adjust_parameters( process_graph=process_graph, process_replacement_id=process_replacement_id, @@ -213,6 +214,13 @@ def _remap_names(process_graph, process_replacement_id): process_replacement_id ].pop(old_key) + for _, node in process_graph[process_replacement_id].items(): + for _, value in node['arguments'].items(): + if isinstance(value, dict) and 'from_node' in value.keys(): + value['from_node'] = next( + (t for t in name_remapping if t[1] == value['from_node']), None + )[0] + def _adjust_parameters(process_graph, process_replacement_id, arguments): ''' diff --git a/pyproject.toml b/pyproject.toml index 188a577..aef6198 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "openeo-pg-parser-networkx" -version = "2024.1.1" +version = "2024.3.1" description = "Parse OpenEO process graphs from JSON to traversible Python objects." authors = ["Lukas Weidenholzer ", "Sean Hoyal ", "Valentina Hutter ", "Gerald Irsiegler "] diff --git a/resolved_gfm_graph.json b/resolved_gfm_graph.json new file mode 100644 index 0000000..c3eebc4 --- /dev/null +++ b/resolved_gfm_graph.json @@ -0,0 +1 @@ +{"GFM_load1": {"process_id": "load_collection", "arguments": {"id": "GFM", "spatial_extent": {"west": 65.27044369351682, "east": 69.21281566288451, "south": 28.076233929760804, "north": 29.369117066086332}, "temporal_extent": ["2022-08-01T00:00:00Z", "2022-10-01T00:00:00Z"], "properties": {}}}, "GFM_reduce1": {"process_id": "reduce_dimension", "arguments": {"data": {"from_node": "GFM_load1"}, "reducer": {"process_graph": {"sum1": {"process_id": "sum", "arguments": {"data": {"from_parameter": "data"}}, "result": true}}}, "dimension": "time"}}, "GFM_save2": {"process_id": "save_result", "arguments": {"format": "GTIFF", "data": {"from_node": "GFM_reduce1"}}, "result": true}} diff --git a/tests/data/res_tests/resolved/resolved_gfm.json b/tests/data/res_tests/resolved/resolved_gfm.json new file mode 100644 index 0000000..af6ba82 --- /dev/null +++ b/tests/data/res_tests/resolved/resolved_gfm.json @@ -0,0 +1,51 @@ +{ + "GFM_load1": { + "process_id": "load_collection", + "arguments": { + "id": "GFM", + "spatial_extent": { + "west": 65.27044369351682, + "east": 69.21281566288451, + "south": 28.076233929760804, + "north": 29.369117066086332 + }, + "temporal_extent": [ + "2022-08-01T00:00:00Z", + "2022-10-01T00:00:00Z" + ], + "properties": {} + } + }, + "GFM_reduce1": { + "process_id": "reduce_dimension", + "arguments": { + "data": { + "from_node": "GFM_load1" + }, + "reducer": { + "process_graph": { + "sum1": { + "process_id": "sum", + "arguments": { + "data": { + "from_parameter": "data" + } + }, + "result": true + } + } + }, + "dimension": "time" + } + }, + "GFM_save2": { + "process_id": "save_result", + "arguments": { + "format": "GTIFF", + "data": { + "from_node": "GFM_reduce1" + } + }, + "result": true + } +} diff --git a/tests/data/res_tests/udps/gfm.json b/tests/data/res_tests/udps/gfm.json new file mode 100644 index 0000000..9d766a0 --- /dev/null +++ b/tests/data/res_tests/udps/gfm.json @@ -0,0 +1,196 @@ +{ + "id": "GFM", + "parameters": [ + { + "schema": { + "type": "array", + "subtype": "temporal-interval", + "title": "Single temporal interval", + "description": "Left-closed temporal interval, represented as two-element array with the following elements:\n\n1. The first element is the start of the temporal interval. The specified instance in time is **included** in the interval.\n2. The second element is the end of the temporal interval. The specified instance in time is **excluded** from the interval.\n\nThe specified temporal strings follow [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html). Although [RFC 3339 prohibits the hour to be '24'](https://www.rfc-editor.org/rfc/rfc3339.html#section-5.7), **this process allows the value '24' for the hour** of an end time in order to make it possible that left-closed time intervals can fully cover the day. `null` can be used to specify open intervals.", + "minItems": 2, + "maxItems": 2, + "items": { + "description": "Processes and implementations may choose to only implement a subset of the subtypes specified here. Clients must check what back-ends / processes actually support.", + "anyOf": [ + { + "type": "string", + "subtype": "date-time", + "format": "date-time", + "title": "Date with Time", + "description": "Date and time representation, as defined for `date-time` by [RFC 3339 in section 5.6](https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6)." + }, + { + "type": "string", + "subtype": "date", + "format": "date", + "title": "Date only", + "description": "Date only representation, as defined for `full-date` by [RFC 3339 in section 5.6](https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6). The time zone is UTC." + }, + { + "type": "string", + "subtype": "time", + "format": "time", + "title": "Time only", + "description": "Time only representation, as defined for `full-time` by [RFC 3339 in section 5.6](https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6). Although [RFC 3339 prohibits the hour to be '24'](https://www.rfc-editor.org/rfc/rfc3339.html#section-5.7), this definition allows the value '24' for the hour as end time in an interval in order to make it possible that left-closed time intervals can fully cover the day." + }, + { + "type": "string", + "subtype": "year", + "minLength": 4, + "maxLength": 4, + "pattern": "^\\d{4}$", + "title": "Year only", + "description": "Year representation, as defined for `date-fullyear` by [RFC 3339 in section 5.6](https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6)." + }, + { + "type": "null" + } + ] + }, + "examples": [ + [ + "2015-01-01T00:00:00Z", + "2016-01-01T00:00:00Z" + ], + [ + "2015-01-01", + "2016-01-01" + ], + [ + "00:00:00Z", + "12:00:00Z" + ], + [ + "2015-01-01", + null + ] + ] + }, + "name": "temporal", + "description": "Scrediption" + }, + { + "schema": { + "type": "object", + "subtype": "bounding-box", + "title": "Bounding Box", + "description": "A bounding box with the required fields `west`, `south`, `east`, `north` and optionally `base`, `height`, `crs`. The `crs` is a EPSG code, a WKT2:2018 string or a PROJ definition (deprecated).", + "required": [ + "west", + "south", + "east", + "north" + ], + "properties": { + "west": { + "description": "West (lower left corner, coordinate axis 1).", + "type": "number" + }, + "south": { + "description": "South (lower left corner, coordinate axis 2).", + "type": "number" + }, + "east": { + "description": "East (upper right corner, coordinate axis 1).", + "type": "number" + }, + "north": { + "description": "North (upper right corner, coordinate axis 2).", + "type": "number" + }, + "base": { + "description": "Base (optional, lower left corner, coordinate axis 3).", + "type": [ + "number", + "null" + ] + }, + "height": { + "description": "Height (optional, upper right corner, coordinate axis 3).", + "type": [ + "number", + "null" + ] + }, + "crs": { + "description": "Coordinate reference system of the extent, specified as as [EPSG code](http://www.epsg-registry.org/), [WKT2 (ISO 19162) string](http://docs.opengeospatial.org/is/18-010r7/18-010r7.html) or [PROJ definition (deprecated)](https://proj.org/usage/quickstart.html). Defaults to `4326` (EPSG code 4326) unless the client explicitly requests a different coordinate reference system.", + "anyOf": [ + { + "type": "integer", + "subtype": "epsg-code", + "title": "EPSG Code", + "description": "Specifies details about cartographic projections as [EPSG](http://www.epsg.org) code.", + "minimum": 1000, + "examples": [ + 3857 + ] + }, + { + "type": "string", + "subtype": "wkt2-definition", + "title": "WKT2 definition", + "description": "Specifies details about cartographic projections as WKT2 string. Refers to the latest WKT2 version (currently [WKT2:2018](http://docs.opengeospatial.org/is/18-010r7/18-010r7.html) / ISO 19162:2018) unless otherwise stated by the process." + }, + { + "type": "string", + "subtype": "proj-definition", + "title": "PROJ definition", + "description": "**DEPRECATED.** Specifies details about cartographic projections as [PROJ](https://proj.org/usage/quickstart.html) definition." + } + ], + "default": 4326 + } + } + }, + "name": "spatial", + "description": "epatial sxtant" + } + ], + "process_graph": { + "load1": { + "process_id": "load_collection", + "arguments": { + "id": "GFM", + "spatial_extent": { + "from_parameter": "spatial" + }, + "temporal_extent": { + "from_parameter": "temporal" + }, + "properties": {} + } + }, + "reduce1": { + "process_id": "reduce_dimension", + "arguments": { + "data": { + "from_node": "load1" + }, + "reducer": { + "process_graph": { + "sum1": { + "process_id": "sum", + "arguments": { + "data": { + "from_parameter": "data" + } + }, + "result": true + } + } + }, + "dimension": "time" + } + }, + "save2": { + "process_id": "save_result", + "arguments": { + "format": "GTIFF", + "data": { + "from_node": "reduce1" + } + }, + "result": true + } + } +} diff --git a/tests/data/res_tests/unresolved/unresolved_gfm.json b/tests/data/res_tests/unresolved/unresolved_gfm.json new file mode 100644 index 0000000..2c63866 --- /dev/null +++ b/tests/data/res_tests/unresolved/unresolved_gfm.json @@ -0,0 +1,19 @@ +{ + "GFM": { + "process_id": "GFM", + "arguments": { + "temporal": [ + "2022-08-01T00:00:00Z", + "2022-10-01T00:00:00Z" + ], + "spatial": { + "west": 65.27044369351682, + "east": 69.21281566288451, + "south": 28.076233929760804, + "north": 29.369117066086332 + } + }, + "result": true, + "namespace": "user" + } +} diff --git a/tests/test_pg_resolving.py b/tests/test_pg_resolving.py index 6edd640..42cea47 100644 --- a/tests/test_pg_resolving.py +++ b/tests/test_pg_resolving.py @@ -7,6 +7,7 @@ def get_udp(process_id: str, namespace: str) -> dict: + process_id = process_id.lower() with open(f'tests/data/res_tests/udps/{process_id}.json') as f: return dict(json.load(f)) @@ -23,6 +24,8 @@ def get_predefined_process_registry(): ('apply', {}), ('load_collection', {}), ('save_result', {}), + ('sum', {}), + ('reduce_dimension', {}), ] for process_id, spec in predefined_processes_specs: @@ -34,7 +37,7 @@ def get_predefined_process_registry(): def get_full_process_registry() -> ProcessRegistry: full_process_registry = get_predefined_process_registry() - for udp in ['w_add', 'valid_load', 'nested_add']: + for udp in ['w_add', 'valid_load', 'nested_add', 'gfm']: full_process_registry['user', udp] = Process( get_udp(udp, "user"), implementation=None, namespace="user" ) @@ -58,13 +61,25 @@ def unresolved_pg() -> dict: return dict(json.loads(f.read())) +@pytest.fixture +def unresolved_gfm_pg() -> dict: + with open('tests/data/res_tests/unresolved/unresolved_gfm.json') as f: + return dict(json.loads(f.read())) + + @pytest.fixture def correctly_resolved_pg() -> dict: with open('tests/data/res_tests/resolved/resolved_complex.json') as f: return dict(json.loads(f.read())) -def test_resolve_graph_withpredefined_process_registr( +@pytest.fixture +def correctly_resolved_gfm_pg() -> dict: + with open('tests/data/res_tests/resolved/resolved_gfm.json') as f: + return dict(json.loads(f.read())) + + +def test_resolve_graph_with_predefined_process_registry( predefined_process_registry: ProcessRegistry, unresolved_pg: dict, correctly_resolved_pg: dict, @@ -132,3 +147,19 @@ def test_resolve_graph_with_none_get_udp_spec( process_registry=predefined_process_registry, get_udp_spec=lambda x, y: None, ) + + +def test_resolve_gfm_graph_with_predefined_process_registry( + predefined_process_registry: ProcessRegistry, + unresolved_gfm_pg: dict, + correctly_resolved_gfm_pg: dict, +): + resolved_pg = resolving_utils.resolve_process_graph( + process_graph=unresolved_gfm_pg, + process_registry=predefined_process_registry, + get_udp_spec=get_udp, + ) + + with open('resolved_gfm_graph.json', 'w') as f: + json.dump(resolved_pg, f) + assert correctly_resolved_gfm_pg == resolved_pg From cbcbe85831ae8a047f69c58aaab402d413819256 Mon Sep 17 00:00:00 2001 From: Gerald Walter Irsiegler Date: Tue, 16 Apr 2024 15:47:41 +0200 Subject: [PATCH 3/9] Fix: fixed parsing of non-dict arguments for resolution (#85) --- openeo_pg_parser_networkx/resolving_utils.py | 6 +- pyproject.toml | 2 +- resolved_gfm_graph.json | 1 - .../res_tests/resolved/resolved_sen2like.json | 32 +++ .../udps/sen2like_original_outputs.json | 205 ++++++++++++++++++ .../unresolved/unresolved_sen2like.json | 23 ++ tests/test_pg_resolving.py | 29 ++- 7 files changed, 293 insertions(+), 5 deletions(-) delete mode 100644 resolved_gfm_graph.json create mode 100644 tests/data/res_tests/resolved/resolved_sen2like.json create mode 100644 tests/data/res_tests/udps/sen2like_original_outputs.json create mode 100644 tests/data/res_tests/unresolved/unresolved_sen2like.json diff --git a/openeo_pg_parser_networkx/resolving_utils.py b/openeo_pg_parser_networkx/resolving_utils.py index 78d8ff8..1cc0023 100644 --- a/openeo_pg_parser_networkx/resolving_utils.py +++ b/openeo_pg_parser_networkx/resolving_utils.py @@ -231,10 +231,14 @@ def _adjust_parameters(process_graph, process_replacement_id, arguments): for node_key, node in process_graph[process_replacement_id].items(): for arg_key, from_param in node['arguments'].items(): # Find from_parameter value in arguments list and replace with arguments[from_parameter value] value - if "from_parameter" in from_param: + if isinstance(from_param, dict) and "from_parameter" in from_param: process_graph[process_replacement_id][node_key]['arguments'][ arg_key ] = arguments[from_param['from_parameter']] + else: + process_graph[process_replacement_id][node_key]['arguments'][ + arg_key + ] = from_param def _adjust_references(input_graph): diff --git a/pyproject.toml b/pyproject.toml index aef6198..6eac78e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "openeo-pg-parser-networkx" -version = "2024.3.1" +version = "2024.4.0" description = "Parse OpenEO process graphs from JSON to traversible Python objects." authors = ["Lukas Weidenholzer ", "Sean Hoyal ", "Valentina Hutter ", "Gerald Irsiegler "] diff --git a/resolved_gfm_graph.json b/resolved_gfm_graph.json deleted file mode 100644 index c3eebc4..0000000 --- a/resolved_gfm_graph.json +++ /dev/null @@ -1 +0,0 @@ -{"GFM_load1": {"process_id": "load_collection", "arguments": {"id": "GFM", "spatial_extent": {"west": 65.27044369351682, "east": 69.21281566288451, "south": 28.076233929760804, "north": 29.369117066086332}, "temporal_extent": ["2022-08-01T00:00:00Z", "2022-10-01T00:00:00Z"], "properties": {}}}, "GFM_reduce1": {"process_id": "reduce_dimension", "arguments": {"data": {"from_node": "GFM_load1"}, "reducer": {"process_graph": {"sum1": {"process_id": "sum", "arguments": {"data": {"from_parameter": "data"}}, "result": true}}}, "dimension": "time"}}, "GFM_save2": {"process_id": "save_result", "arguments": {"format": "GTIFF", "data": {"from_node": "GFM_reduce1"}}, "result": true}} diff --git a/tests/data/res_tests/resolved/resolved_sen2like.json b/tests/data/res_tests/resolved/resolved_sen2like.json new file mode 100644 index 0000000..218467a --- /dev/null +++ b/tests/data/res_tests/resolved/resolved_sen2like.json @@ -0,0 +1,32 @@ +{ + "sen2like_original_outputs_load1": { + "process_id": "load_collection", + "arguments": { + "id": "SENTINEL2_L1C", + "spatial_extent": { + "west": -4.919340483677538, + "east": 36.248628266322456, + "south": 41.43373541041478, + "north": 53.27118132212786 + }, + "temporal_extent": [ + "2024-04-01T00:00:00Z", + "2024-04-09T00:00:00Z" + ], + "bands": [ + "bo1" + ], + "properties": {} + } + }, + "sen2like_original_outputs_sen2": { + "process_id": "sen2like", + "arguments": { + "data": { + "from_node": "sen2like_original_outputs_load1" + }, + "export_original_files": true + }, + "result": true + } +} diff --git a/tests/data/res_tests/udps/sen2like_original_outputs.json b/tests/data/res_tests/udps/sen2like_original_outputs.json new file mode 100644 index 0000000..ec91fef --- /dev/null +++ b/tests/data/res_tests/udps/sen2like_original_outputs.json @@ -0,0 +1,205 @@ +{ + "id": "sen2like_original_outputs", + "summary": "Create Sentinel 2 - like .SAFE outputs from Sentinel 2 L1C and Landsat 8 and 9 datasets. ", + "description": "Process sen2like and create Sentinel 2 - like .SAFE outputs from Sentinel 2 L1C and Landsat 8 and 9 datasets for all bands. Area of interest and time need to be specified. ", + "parameters": [ + { + "schema": { + "type": "object", + "subtype": "bounding-box", + "title": "Bounding Box", + "description": "A bounding box with the required fields `west`, `south`, `east`, `north` and optionally `base`, `height`, `crs`. The `crs` is a EPSG code, a WKT2:2018 string or a PROJ definition (deprecated).", + "required": [ + "west", + "south", + "east", + "north" + ], + "properties": { + "west": { + "description": "West (lower left corner, coordinate axis 1).", + "type": "number" + }, + "south": { + "description": "South (lower left corner, coordinate axis 2).", + "type": "number" + }, + "east": { + "description": "East (upper right corner, coordinate axis 1).", + "type": "number" + }, + "north": { + "description": "North (upper right corner, coordinate axis 2).", + "type": "number" + }, + "base": { + "description": "Base (optional, lower left corner, coordinate axis 3).", + "type": [ + "number", + "null" + ] + }, + "height": { + "description": "Height (optional, upper right corner, coordinate axis 3).", + "type": [ + "number", + "null" + ] + }, + "crs": { + "description": "Coordinate reference system of the extent, specified as as [EPSG code](http://www.epsg-registry.org/), [WKT2 (ISO 19162) string](http://docs.opengeospatial.org/is/18-010r7/18-010r7.html) or [PROJ definition (deprecated)](https://proj.org/usage/quickstart.html). Defaults to `4326` (EPSG code 4326) unless the client explicitly requests a different coordinate reference system.", + "anyOf": [ + { + "type": "integer", + "subtype": "epsg-code", + "title": "EPSG Code", + "description": "Specifies details about cartographic projections as [EPSG](http://www.epsg.org) code.", + "minimum": 1000, + "examples": [ + 3857 + ] + }, + { + "type": "string", + "subtype": "wkt2-definition", + "title": "WKT2 definition", + "description": "Specifies details about cartographic projections as WKT2 string. Refers to the latest WKT2 version (currently [WKT2:2018](http://docs.opengeospatial.org/is/18-010r7/18-010r7.html) / ISO 19162:2018) unless otherwise stated by the process." + }, + { + "type": "string", + "subtype": "proj-definition", + "title": "PROJ definition", + "description": "**DEPRECATED.** Specifies details about cartographic projections as [PROJ](https://proj.org/usage/quickstart.html) definition." + } + ], + "default": 4326 + } + } + }, + "name": "spatial_extent", + "description": "Bounding box for the area of interest" + }, + { + "schema": { + "type": "array", + "subtype": "temporal-interval", + "title": "Single temporal interval", + "description": "Left-closed temporal interval, represented as two-element array with the following elements:\n\n1. The first element is the start of the temporal interval. The specified instance in time is **included** in the interval.\n2. The second element is the end of the temporal interval. The specified instance in time is **excluded** from the interval.\n\nThe specified temporal strings follow [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.html). Although [RFC 3339 prohibits the hour to be '24'](https://www.rfc-editor.org/rfc/rfc3339.html#section-5.7), **this process allows the value '24' for the hour** of an end time in order to make it possible that left-closed time intervals can fully cover the day. `null` can be used to specify open intervals.", + "minItems": 2, + "maxItems": 2, + "items": { + "description": "Processes and implementations may choose to only implement a subset of the subtypes specified here. Clients must check what back-ends / processes actually support.", + "anyOf": [ + { + "type": "string", + "subtype": "date-time", + "format": "date-time", + "title": "Date with Time", + "description": "Date and time representation, as defined for `date-time` by [RFC 3339 in section 5.6](https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6)." + }, + { + "type": "string", + "subtype": "date", + "format": "date", + "title": "Date only", + "description": "Date only representation, as defined for `full-date` by [RFC 3339 in section 5.6](https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6). The time zone is UTC." + }, + { + "type": "string", + "subtype": "time", + "format": "time", + "title": "Time only", + "description": "Time only representation, as defined for `full-time` by [RFC 3339 in section 5.6](https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6). Although [RFC 3339 prohibits the hour to be '24'](https://www.rfc-editor.org/rfc/rfc3339.html#section-5.7), this definition allows the value '24' for the hour as end time in an interval in order to make it possible that left-closed time intervals can fully cover the day." + }, + { + "type": "string", + "subtype": "year", + "minLength": 4, + "maxLength": 4, + "pattern": "^\\d{4}$", + "title": "Year only", + "description": "Year representation, as defined for `date-fullyear` by [RFC 3339 in section 5.6](https://www.rfc-editor.org/rfc/rfc3339.html#section-5.6)." + }, + { + "type": "null" + } + ] + }, + "examples": [ + [ + "2015-01-01T00:00:00Z", + "2016-01-01T00:00:00Z" + ], + [ + "2015-01-01", + "2016-01-01" + ], + [ + "00:00:00Z", + "12:00:00Z" + ], + [ + "2015-01-01", + null + ] + ] + }, + "name": "temporal_extent", + "description": "Time span of interest" + }, + { + "schema": [ + { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "subtype": "band-name" + } + }, + { + "title": "No filter", + "description": "Don't filter bands. All bands are included in the data cube.", + "type": "null" + } + ], + "name": "bands", + "description": "Bands to load and process. Note: sen2like does not process `B09`, `B10`. ", + "optional": true + } + ], + "returns": { + "description": "A list of .zip files containing the Sentinel 2 - like .SAFE folders. ", + "schema": { + "type": "list" + } + }, + "process_graph": { + "load1": { + "process_id": "load_collection", + "arguments": { + "id": "SENTINEL2_L1C", + "spatial_extent": { + "from_parameter": "spatial_extent" + }, + "temporal_extent": { + "from_parameter": "temporal_extent" + }, + "bands": { + "from_parameter": "bands" + }, + "properties": {} + } + }, + "sen2": { + "process_id": "sen2like", + "arguments": { + "data": { + "from_node": "load1" + }, + "export_original_files": true + }, + "result": true + } + } + } diff --git a/tests/data/res_tests/unresolved/unresolved_sen2like.json b/tests/data/res_tests/unresolved/unresolved_sen2like.json new file mode 100644 index 0000000..f4fd4ef --- /dev/null +++ b/tests/data/res_tests/unresolved/unresolved_sen2like.json @@ -0,0 +1,23 @@ +{ + "sen2like_original_outputs": { + "process_id": "sen2like_original_outputs", + "arguments": { + "bands": [ + "bo1" + ], + "spatial_extent": { + "west": -4.919340483677538, + "east": 36.248628266322456, + "south": 41.43373541041478, + "north": 53.27118132212786 + }, + "temporal_extent": [ + "2024-04-01T00:00:00Z", + "2024-04-09T00:00:00Z" + ] + }, + "result": true, + "namespace": "user", + "description": "Create Sentinel 2 - like .SAFE outputs from Sentinel 2 L1C and Landsat 8 and 9 datasets. " + } +} diff --git a/tests/test_pg_resolving.py b/tests/test_pg_resolving.py index 42cea47..c17fc24 100644 --- a/tests/test_pg_resolving.py +++ b/tests/test_pg_resolving.py @@ -20,6 +20,7 @@ def get_predefined_process_registry(): predefined_process_registry = ProcessRegistry() predefined_processes_specs = [ + ('sen2like', {}), ('add', {}), ('apply', {}), ('load_collection', {}), @@ -67,6 +68,12 @@ def unresolved_gfm_pg() -> dict: return dict(json.loads(f.read())) +@pytest.fixture +def unresolved_sen2like_pg() -> dict: + with open('tests/data/res_tests/unresolved/unresolved_sen2like.json') as f: + return dict(json.loads(f.read())) + + @pytest.fixture def correctly_resolved_pg() -> dict: with open('tests/data/res_tests/resolved/resolved_complex.json') as f: @@ -79,6 +86,12 @@ def correctly_resolved_gfm_pg() -> dict: return dict(json.loads(f.read())) +@pytest.fixture +def correctly_resolved_sen2like_pg() -> dict: + with open('tests/data/res_tests/resolved/resolved_sen2like.json') as f: + return dict(json.loads(f.read())) + + def test_resolve_graph_with_predefined_process_registry( predefined_process_registry: ProcessRegistry, unresolved_pg: dict, @@ -160,6 +173,18 @@ def test_resolve_gfm_graph_with_predefined_process_registry( get_udp_spec=get_udp, ) - with open('resolved_gfm_graph.json', 'w') as f: - json.dump(resolved_pg, f) assert correctly_resolved_gfm_pg == resolved_pg + + +def test_resolve_sen2like_graph_with_predefined_process_registry( + predefined_process_registry: ProcessRegistry, + unresolved_sen2like_pg: dict, + correctly_resolved_sen2like_pg: dict, +): + resolved_pg = resolving_utils.resolve_process_graph( + process_graph=unresolved_sen2like_pg, + process_registry=predefined_process_registry, + get_udp_spec=get_udp, + ) + + assert correctly_resolved_sen2like_pg == resolved_pg From 8c1af9eca5093594d626c8eb33e7e7f5bc0cad9d Mon Sep 17 00:00:00 2001 From: Michele Claus <31700619+clausmichele@users.noreply.github.com> Date: Mon, 27 May 2024 15:48:43 +0200 Subject: [PATCH 4/9] pydantic>2 (#83) --- .pre-commit-config.yaml | 2 + openeo_pg_parser_networkx/graph.py | 5 +- openeo_pg_parser_networkx/pg_schema.py | 162 +++++++++++++------------ openeo_pg_parser_networkx/utils.py | 4 +- pyproject.toml | 4 +- tests/data/graphs/fit_rf_pg.json | 46 +++++++ tests/data/graphs/fit_rf_pg_0.json | 94 -------------- tests/test_pg_parser.py | 86 +++++++------ 8 files changed, 191 insertions(+), 212 deletions(-) create mode 100644 tests/data/graphs/fit_rf_pg.json delete mode 100644 tests/data/graphs/fit_rf_pg_0.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 353faa2..61bfce2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,7 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks +default_language_version: + python: python3 repos: - repo: https://github.com/asottile/pyupgrade rev: v3.10.1 diff --git a/openeo_pg_parser_networkx/graph.py b/openeo_pg_parser_networkx/graph.py index 2820a33..d054607 100644 --- a/openeo_pg_parser_networkx/graph.py +++ b/openeo_pg_parser_networkx/graph.py @@ -1,5 +1,8 @@ from __future__ import annotations +import sys + +sys.setrecursionlimit(16385) # Necessary when parsing really big graphs import functools import json import logging @@ -110,7 +113,7 @@ def _parse_datamodel(nested_graph: dict) -> ProcessGraph: Parses a nested process graph into the Pydantic datamodel for ProcessGraph. """ - return ProcessGraph.parse_obj(nested_graph) + return ProcessGraph.model_validate(nested_graph) def _parse_process_graph(self, process_graph: ProcessGraph, arg_name: str = None): """ diff --git a/openeo_pg_parser_networkx/pg_schema.py b/openeo_pg_parser_networkx/pg_schema.py index d68a73f..0269100 100644 --- a/openeo_pg_parser_networkx/pg_schema.py +++ b/openeo_pg_parser_networkx/pg_schema.py @@ -5,7 +5,7 @@ import logging from enum import Enum from re import match -from typing import Any, Optional, Union +from typing import Annotated, Any, List, Optional, Union from uuid import UUID, uuid4 import numpy as np @@ -22,9 +22,13 @@ BaseModel, Extra, Field, + RootModel, + StringConstraints, ValidationError, conlist, constr, + field_validator, + model_validator, validator, ) from shapely.geometry import Polygon @@ -65,13 +69,14 @@ class ParameterReference(BaseModel, extra=Extra.forbid): class ProcessNode(BaseModel, arbitrary_types_allowed=True): - process_id: constr(regex=r'^\w+$') + process_id: Annotated[str, StringConstraints(pattern=r'^\w+$')] + namespace: Optional[Optional[str]] = None result: Optional[bool] = False description: Optional[Optional[str]] = None arguments: dict[ str, - Optional[ + Annotated[ Union[ ResultReference, ParameterReference, @@ -87,11 +92,12 @@ class ProcessNode(BaseModel, arbitrary_types_allowed=True): # GeoJson, disable while https://github.com/developmentseed/geojson-pydantic/issues/92 is open Time, float, - str, bool, list, dict, - ] + str, + ], + Field(union_mode='left_to_right'), ], ] @@ -133,9 +139,9 @@ class BoundingBox(BaseModel, arbitrary_types_allowed=True): east: float north: float south: float - base: Optional[float] - height: Optional[float] - crs: Optional[Union[str, int]] + base: Optional[float] = None + height: Optional[float] = None + crs: Optional[Union[str, int]] = None # validators _parse_crs: classmethod = crs_validator('crs') @@ -153,10 +159,10 @@ def polygon(self) -> Polygon: ) -class Date(BaseModel): - __root__: datetime.datetime +class Date(RootModel): + root: datetime.datetime - @validator("__root__", pre=True) + @field_validator("root", mode="before") def validate_time(cls, value: Any) -> Any: if ( isinstance(value, str) @@ -164,37 +170,43 @@ def validate_time(cls, value: Any) -> Any: and match(r"[0-9]{4}[-/][0-9]{2}[-/][0-9]{2}T?", value) ): return pendulum.parse(value) - raise ValidationError("Could not parse `Date` from input.") + raise ValueError("Could not parse `Date` from input.") def to_numpy(self): - return np.datetime64(self.__root__) + return np.datetime64(self.root) def __repr__(self): - return self.__root__.__repr__() + return self.root.__repr__() + + def __gt__(self, date1): + return self.root > date1.root -class DateTime(BaseModel): - __root__: datetime.datetime +class DateTime(RootModel): + root: datetime.datetime - @validator("__root__", pre=True) + @field_validator("root", mode="before") def validate_time(cls, value: Any) -> Any: if isinstance(value, str) and match( r"[0-9]{4}-[0-9]{2}-[0-9]{2}T?[0-9]{2}:[0-9]{2}:?([0-9]{2})?Z?", value ): return pendulum.parse(value) - raise ValidationError("Could not parse `DateTime` from input.") + raise ValueError("Could not parse `DateTime` from input.") def to_numpy(self): - return np.datetime64(self.__root__) + return np.datetime64(self.root) def __repr__(self): - return self.__root__.__repr__() + return self.root.__repr__() + + def __gt__(self, date1): + return self.root > date1.root -class Time(BaseModel): - __root__: pendulum.Time +class Time(RootModel): + root: datetime.time - @validator("__root__", pre=True) + @field_validator("root", mode="before") def validate_time(cls, value: Any) -> Any: if ( isinstance(value, str) @@ -203,133 +215,133 @@ def validate_time(cls, value: Any) -> Any: and match(r"[0-9]{2}:[0-9]{2}:?([0-9]{2})?Z?", value) ): return pendulum.parse(value).time() - raise ValidationError("Could not parse `Time` from input.") + raise ValueError("Could not parse `Time` from input.") def to_numpy(self): raise NotImplementedError def __repr__(self): - return self.__root__.__repr__() + return self.time.__repr__() -class Year(BaseModel): - __root__: datetime.datetime +class Year(RootModel): + root: datetime.datetime - @validator("__root__", pre=True) + @field_validator("root", mode="before") def validate_time(cls, value: Any) -> Any: if isinstance(value, str) and len(value) <= 4 and match(r"^\d{4}$", value): return pendulum.parse(value) - raise ValidationError("Could not parse `Year` from input.") + raise ValueError("Could not parse `Year` from input.") def to_numpy(self): - return np.datetime64(self.__root__) + return np.datetime64(self.root) def __repr__(self): - return self.__root__.__repr__() + return self.root.__repr__() -class Duration(BaseModel): - __root__: datetime.timedelta +class Duration(RootModel): + root: datetime.timedelta - @validator("__root__", pre=True) + @field_validator("root", mode="before") def validate_time(cls, value: Any) -> Any: if isinstance(value, str) and match( r"P[0-9]*Y?[0-9]*M?[0-9]*D?T?[0-9]*H?[0-9]*M?[0-9]*S?", value ): return pendulum.parse(value).as_timedelta() - raise ValidationError("Could not parse `Duration` from input.") + raise ValueError("Could not parse `Duration` from input.") def to_numpy(self): - return np.timedelta64(self.__root__) + return np.timedelta64(self.root) def __repr__(self): - return self.__root__.__repr__() + return self.root.__repr__() -class TemporalInterval(BaseModel): - __root__: conlist(Union[Year, Date, DateTime, Time, None], min_items=2, max_items=2) +class TemporalInterval(RootModel): + root: conlist(Union[Year, Date, DateTime, Time, None], min_length=2, max_length=2) - @validator("__root__") + @field_validator("root") def validate_temporal_interval(cls, value: Any) -> Any: start = value[0] end = value[1] if start is None and end is None: - raise ValidationError("Could not parse `TemporalInterval` from input.") + raise ValueError("Could not parse `TemporalInterval` from input.") # Disambiguate the Time subtype if isinstance(start, Time) or isinstance(end, Time): if isinstance(start, Time) and isinstance(end, Time): - raise ValidationError( + raise ValueError( "Ambiguous TemporalInterval, both start and end are of type `Time`" ) if isinstance(start, Time): if end is None: - raise ValidationError( + raise ValueError( "Cannot disambiguate TemporalInterval, start is `Time` and end is `None`" ) logger.warning( "Start time of temporal interval is of type `time`. Assuming same date as the end time." ) start = DateTime( - __root__=pendulum.datetime( - end.__root__.year, - end.__root__.month, - end.__root__.day, - start.__root__.hour, - start.__root__.minute, - start.__root__.second, - start.__root__.microsecond, + root=pendulum.datetime( + end.root.year, + end.root.month, + end.root.day, + start.root.hour, + start.root.minute, + start.root.second, + start.root.microsecond, ).to_rfc3339_string() ) elif isinstance(end, Time): if start is None: - raise ValidationError( + raise ValueError( "Cannot disambiguate TemporalInterval, start is `None` and end is `Time`" ) logger.warning( "End time of temporal interval is of type `time`. Assuming same date as the start time." ) end = DateTime( - __root__=pendulum.datetime( - start.__root__.year, - start.__root__.month, - start.__root__.day, - end.__root__.hour, - end.__root__.minute, - end.__root__.second, - end.__root__.microsecond, + root=pendulum.datetime( + start.root.year, + start.root.month, + start.root.day, + end.root.hour, + end.root.minute, + end.root.second, + end.root.microsecond, ).to_rfc3339_string() ) - if not (start is None or end is None) and start.__root__ > end.__root__: - raise ValidationError("Start time > end time") + if not (start is None or end is None) and start > end: + raise ValueError("Start time > end time") return [start, end] @property def start(self): - return self.__root__[0] + return self.root[0] @property def end(self): - return self.__root__[1] + return self.root[1] def __iter__(self): - return iter(self.__root__) + return iter(self.root) def __getitem__(self, item): - return self.__root__[item] + return self.root[item] -class TemporalIntervals(BaseModel): - __root__: list[TemporalInterval] +class TemporalIntervals(RootModel): + root: list[TemporalInterval] def __iter__(self): - return iter(self.__root__) + return iter(self.root) def __getitem__(self, item) -> TemporalInterval: - return self.__root__[item] + return self.root[item] GeoJson = Union[FeatureCollection, Feature, GeometryCollection, MultiPolygon, Polygon] @@ -337,11 +349,11 @@ def __getitem__(self, item) -> TemporalInterval: # have a crs field anymore and recommends assuming it to be EPSG:4326, so we do the same. -class JobId(BaseModel): - __root__: str = Field( - regex=r"(eodc-jb-|jb-)[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}" +class JobId(RootModel): + root: str = Field( + pattern=r"(eodc-jb-|jb-)[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}" ) -ResultReference.update_forward_refs() -ProcessNode.update_forward_refs() +ResultReference.model_rebuild() +ProcessNode.model_rebuild() diff --git a/openeo_pg_parser_networkx/utils.py b/openeo_pg_parser_networkx/utils.py index e175081..aad36dc 100644 --- a/openeo_pg_parser_networkx/utils.py +++ b/openeo_pg_parser_networkx/utils.py @@ -10,14 +10,14 @@ def parse_nested_parameter(parameter: Any): try: return ResultReference.parse_obj(parameter) - except pydantic.error_wrappers.ValidationError: + except pydantic.ValidationError: pass except TypeError: pass try: return ParameterReference.parse_obj(parameter) - except pydantic.error_wrappers.ValidationError: + except pydantic.ValidationError: pass except TypeError: pass diff --git a/pyproject.toml b/pyproject.toml index 6eac78e..676f159 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,11 +24,11 @@ packages = [ [tool.poetry.dependencies] python = ">=3.9,<3.12" -pydantic = "^1.9.1" +pydantic = "^2.4.0" pyproj = "^3.4.0" networkx = "^2.8.6" shapely = ">=1.8" -geojson-pydantic = "^0.5.0" +geojson-pydantic = "^1.0.0" numpy = "^1.20.3" pendulum = "^2.1.2" matplotlib = { version = "^3.7.1", optional = true } diff --git a/tests/data/graphs/fit_rf_pg.json b/tests/data/graphs/fit_rf_pg.json new file mode 100644 index 0000000..a81f54a --- /dev/null +++ b/tests/data/graphs/fit_rf_pg.json @@ -0,0 +1,46 @@ +{ + "process_graph": { + "loadstac1": { + "process_id": "load_stac", + "arguments": { + "url": "https://openeo.eodc.eu/openeo/1.1.0//jobs/d96e00f2-ccf7-4ef6-bca8-41ce2ec6e611/results" + } + }, + "loadvectorcube1": { + "process_id": "load_vector_cube", + "arguments": { + "URL": "https://raw.githubusercontent.com/openEOPlatform/SRR3_notebooks/main/notebooks/resources/UC8/vector_data/target_canopy_cover_60m_WGS84/target_canopy_cover_WGS84_60m.geojson" + } + }, + "fitregrrandomforest1": { + "process_id": "fit_regr_random_forest", + "arguments": { + "predictors": { + "from_node": "loadstac1" + }, + "predictors_vars": [ + "VV", + "VH", + "B02", + "B03", + "B04" + ], + "target": { + "from_node": "loadvectorcube1" + }, + "target_var": "target_canopy_cover" + } + }, + "saveresult1": { + "process_id": "save_result", + "arguments": { + "data": { + "from_node": "fitregrrandomforest1" + }, + "format": "GeoJSON", + "options": {} + }, + "result": true + } + } +} diff --git a/tests/data/graphs/fit_rf_pg_0.json b/tests/data/graphs/fit_rf_pg_0.json deleted file mode 100644 index 7a1282d..0000000 --- a/tests/data/graphs/fit_rf_pg_0.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "process_graph": { - "loadcollection1": { - "process_id": "load_collection", - "arguments": { - "bands": [ - "B02", - "B03", - "B04", - "B08" - ], - "id": "boa_sentinel_2", - "spatial_extent": { - "east": 9.992539570642537, - "north": 47.73255902176489, - "south": 46.82379186182021, - "west": 8.259091264204697 - }, - "temporal_extent": [ - "2018-05-01", - "2018-09-01" - ] - } - }, - "reducedimension1": { - "process_id": "reduce_dimension", - "arguments": { - "data": { - "from_node": "loadcollection1" - }, - "dimension": "t", - "reducer": { - "process_graph": { - "median1": { - "process_id": "median", - "arguments": { - "data": { - "from_parameter": "data" - } - }, - "result": true - } - } - } - } - }, - "loadvectorcube1": { - "process_id": "load_vector_cube", - "arguments": { - "URL": "https://raw.githubusercontent.com/LukeWeidenwalker/SRR3_clustered_geojsons/master/AOI_clustered_distance_2/cluster_3.geojson" - } - }, - "aggregatespatial1": { - "process_id": "aggregate_spatial", - "arguments": { - "data": { - "from_node": "reducedimension1" - }, - "geometries": { - "from_node": "loadvectorcube1" - }, - "reducer": "mean", - "target_dimension": "result" - } - }, - "fitregrrandomforest1": { - "process_id": "fit_regr_random_forest", - "arguments": { - "data": { - "from_node": "aggregatespatial1" - }, - "max_variables": null, - "num_trees": 100, - "predictors_vars": [ - "B02", - "B03", - "B04", - "B08" - ], - "seed": 0, - "target_var": "target_canopy_cover" - } - }, - "savemlmodel1": { - "process_id": "save_ml_model", - "arguments": { - "model": { - "from_node": "fitregrrandomforest1" - } - }, - "result": true - } - } -} diff --git a/tests/test_pg_parser.py b/tests/test_pg_parser.py index ccc45d3..15ba3b5 100644 --- a/tests/test_pg_parser.py +++ b/tests/test_pg_parser.py @@ -77,13 +77,13 @@ def test_aggregate_temporal_period_parse(): def test_from_json_constructor(): - flat_process_graph = json.load(open(TEST_DATA_DIR / "graphs" / "fit_rf_pg_0.json")) + flat_process_graph = json.load(open(TEST_DATA_DIR / "graphs" / "fit_rf_pg.json")) parsed_graph = OpenEOProcessGraph.from_json(json.dumps(flat_process_graph)) assert isinstance(parsed_graph, OpenEOProcessGraph) def test_data_types_explicitly(): - flat_process_graph = json.load(open(TEST_DATA_DIR / "graphs" / "fit_rf_pg_0.json")) + flat_process_graph = json.load(open(TEST_DATA_DIR / "graphs" / "fit_rf_pg.json")) nested_process_graph = OpenEOProcessGraph._unflatten_raw_process_graph( flat_process_graph ) @@ -91,10 +91,10 @@ def test_data_types_explicitly(): assert isinstance(parsed_process_graph, ProcessGraph) assert isinstance(parsed_process_graph.process_graph["root"], ProcessNode) assert isinstance( - parsed_process_graph.process_graph["root"].arguments["model"], ResultReference + parsed_process_graph.process_graph["root"].arguments["data"], ResultReference ) assert isinstance( - parsed_process_graph.process_graph["root"].arguments["model"].node, + parsed_process_graph.process_graph["root"].arguments["data"].node, ProcessNode, ) @@ -112,7 +112,7 @@ def test_bounding_box(get_process_graph_with_args): } ) parsed_arg = ( - ProcessGraph.parse_obj(pg) + ProcessGraph.model_validate(pg) .process_graph[TEST_NODE_KEY] .arguments["spatial_extent"] ) @@ -132,7 +132,7 @@ def test_bounding_box_no_crs(get_process_graph_with_args): {'spatial_extent': {'west': 0, 'east': 10, 'south': 0, 'north': 10}} ) parsed_arg = ( - ProcessGraph.parse_obj(pg) + ProcessGraph.model_validate(pg) .process_graph[TEST_NODE_KEY] .arguments["spatial_extent"] ) @@ -154,7 +154,7 @@ def test_bounding_box_with_faulty_crs(get_process_graph_with_args): } ) with pytest.raises(pyproj.exceptions.CRSError): - ProcessGraph.parse_obj(pg).process_graph[TEST_NODE_KEY].arguments[ + ProcessGraph.model_validate(pg).process_graph[TEST_NODE_KEY].arguments[ "spatial_extent" ] @@ -164,7 +164,7 @@ def test_bounding_box_int_crs(get_process_graph_with_args): {'spatial_extent': {'west': 0, 'east': 10, 'south': 0, 'north': 10, 'crs': 4326}} ) parsed_arg = ( - ProcessGraph.parse_obj(pg) + ProcessGraph.model_validate(pg) .process_graph[TEST_NODE_KEY] .arguments["spatial_extent"] ) @@ -195,7 +195,9 @@ def test_geojson(get_process_graph_with_args): } pg = get_process_graph_with_args(argument) parsed_arg = ( - ProcessGraph.parse_obj(pg).process_graph[TEST_NODE_KEY].arguments["geometries"] + ProcessGraph.model_validate(pg) + .process_graph[TEST_NODE_KEY] + .arguments["geometries"] ) assert isinstance(parsed_arg, get_args(GeoJson)) @@ -205,14 +207,14 @@ def test_geojson(get_process_graph_with_args): ) def test_geojson_parsing(): with pytest.raises(ValidationError): - should_not_parse = GeoJson.parse_obj(['vh', 'vv']) + should_not_parse = GeoJson.model_validate(['vh', 'vv']) def test_jobid(get_process_graph_with_args): argument = {'job_id': 'jb-4da83382-8f8e-4153-8961-e15614b04185'} pg = get_process_graph_with_args(argument) parsed_arg = ( - ProcessGraph.parse_obj(pg).process_graph[TEST_NODE_KEY].arguments["job_id"] + ProcessGraph.model_validate(pg).process_graph[TEST_NODE_KEY].arguments["job_id"] ) assert isinstance(parsed_arg, JobId) @@ -228,7 +230,7 @@ def test_temporal_intervals(get_process_graph_with_args): } pg = get_process_graph_with_args(argument1) parsed_intervals = ( - ProcessGraph.parse_obj(pg) + ProcessGraph.model_validate(pg) .process_graph[TEST_NODE_KEY] .arguments["temporal_intervals"] ) @@ -242,7 +244,7 @@ def test_temporal_intervals(get_process_graph_with_args): assert isinstance(first_interval, TemporalInterval) assert isinstance(first_interval.start, DateTime) assert isinstance(first_interval.end, DateTime) - assert first_interval.end.__root__ == first_interval.start.__root__.add(hours=8) + assert first_interval.end.root == first_interval.start.root.add(hours=8) assert isinstance(second_interval, TemporalInterval) assert isinstance(second_interval.start, Date) @@ -259,29 +261,29 @@ def test_temporal_intervals(get_process_graph_with_args): def test_invalid_temporal_intervals(): with pytest.raises(ValidationError): - TemporalInterval.parse_obj(['1990-01-01T12:00:00', '11:00:00']) + TemporalInterval.model_validate(['1990-01-01T12:00:00', '11:00:00']) with pytest.raises(ValidationError): - TemporalInterval.parse_obj([None, None]) + TemporalInterval.model_validate([None, None]) with pytest.raises(ValidationError): - TemporalInterval.parse_obj(['15:00:00', '1990-01-01T20:00:00', '11:00:00']) + TemporalInterval.model_validate(['15:00:00', '1990-01-01T20:00:00', '11:00:00']) with pytest.raises(ValidationError): - TemporalInterval.parse_obj(['1990-01-01T20:00:00']) + TemporalInterval.model_validate(['1990-01-01T20:00:00']) with pytest.raises(ValidationError): - TemporalInterval.parse_obj([None, '13:00:00']) + TemporalInterval.model_validate([None, '13:00:00']) with pytest.raises(ValidationError): - TemporalInterval.parse_obj(['13:00:00', None]) + TemporalInterval.model_validate(['13:00:00', None]) with pytest.raises(ValidationError): - TemporalInterval.parse_obj(['13:00:00', '14:00:00']) + TemporalInterval.model_validate(['13:00:00', '14:00:00']) def test_duration(get_process_graph_with_args): argument = {'duration': 'P1Y1M1DT2H'} pg = get_process_graph_with_args(argument) parsed_arg = ( - ProcessGraph.parse_obj(pg).process_graph[TEST_NODE_KEY].arguments["duration"] + ProcessGraph.model_validate(pg).process_graph[TEST_NODE_KEY].arguments["duration"] ) assert isinstance(parsed_arg, Duration) - assert isinstance(parsed_arg.__root__, datetime.timedelta) + assert isinstance(parsed_arg.root, datetime.timedelta) assert parsed_arg.to_numpy() == np.timedelta64( pendulum.parse(argument["duration"]).as_timedelta() @@ -292,53 +294,61 @@ def test_datetime(get_process_graph_with_args): argument_valid = {'datetime': '1975-05-21T22:00:00'} pg = get_process_graph_with_args(argument_valid) parsed_arg = ( - ProcessGraph.parse_obj(pg).process_graph[TEST_NODE_KEY].arguments["datetime"] + ProcessGraph.model_validate(pg).process_graph[TEST_NODE_KEY].arguments["datetime"] ) assert isinstance(parsed_arg, DateTime) - assert isinstance(parsed_arg.__root__, datetime.datetime) + assert isinstance(parsed_arg.root, datetime.datetime) assert parsed_arg.to_numpy() == np.datetime64(argument_valid["datetime"]) with pytest.raises(ValidationError): - DateTime.parse_obj('21-05-1975T22:00:00') + DateTime.model_validate('21-05-1975T22:00:00') def test_date(get_process_graph_with_args): argument_valid = {'date': '1975-05-21'} pg = get_process_graph_with_args(argument_valid) - parsed_arg = ProcessGraph.parse_obj(pg).process_graph[TEST_NODE_KEY].arguments["date"] + print(pg) + parsed_arg = ( + ProcessGraph.model_validate(pg).process_graph[TEST_NODE_KEY].arguments["date"] + ) + print(parsed_arg) assert isinstance(parsed_arg, Date) - assert isinstance(parsed_arg.__root__, datetime.datetime) + assert isinstance(parsed_arg.root, datetime.datetime) assert parsed_arg.to_numpy() == np.datetime64(argument_valid["date"]) with pytest.raises(ValidationError): - DateTime.parse_obj('21-05-1975') - DateTime.parse_obj('22:00:80') + DateTime.model_validate('21-05-1975') + DateTime.model_validate('22:00:80') def test_year(get_process_graph_with_args): argument_valid = {'year': '1975'} pg = get_process_graph_with_args(argument_valid) - parsed_arg = ProcessGraph.parse_obj(pg).process_graph[TEST_NODE_KEY].arguments["year"] + parsed_arg = ( + ProcessGraph.model_validate(pg).process_graph[TEST_NODE_KEY].arguments["year"] + ) assert isinstance(parsed_arg, Year) - assert isinstance(parsed_arg.__root__, datetime.datetime) + assert isinstance(parsed_arg.root, datetime.datetime) assert parsed_arg.to_numpy() == np.datetime64(argument_valid["year"]) with pytest.raises(ValidationError): - DateTime.parse_obj('75') - DateTime.parse_obj('0001') - DateTime.parse_obj('22:00:80') + DateTime.model_validate('75') + DateTime.model_validate('0001') + DateTime.model_validate('22:00:80') def test_time(get_process_graph_with_args): argument_valid = {'time': '22:00:00'} pg = get_process_graph_with_args(argument_valid) - parsed_arg = ProcessGraph.parse_obj(pg).process_graph[TEST_NODE_KEY].arguments["time"] + parsed_arg = ( + ProcessGraph.model_validate(pg).process_graph[TEST_NODE_KEY].arguments["time"] + ) assert isinstance(parsed_arg, Time) - assert isinstance(parsed_arg.__root__, pendulum.Time) + assert isinstance(parsed_arg.root, pendulum.Time) with pytest.raises(NotImplementedError): parsed_arg.to_numpy() with pytest.raises(ValidationError): - DateTime.parse_obj('22:00:80') - DateTime.parse_obj('0001') + DateTime.model_validate('22:00:80') + DateTime.model_validate('0001') From 2c17defad86bf8ad6a8a7fbe5375302a8daa2dd0 Mon Sep 17 00:00:00 2001 From: ValentinaHutter <85164505+ValentinaHutter@users.noreply.github.com> Date: Mon, 27 May 2024 15:52:46 +0200 Subject: [PATCH 5/9] v2024.5.0 update pyproject.toml (#86) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 676f159..40a6835 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "openeo-pg-parser-networkx" -version = "2024.4.0" +version = "2024.5.0" description = "Parse OpenEO process graphs from JSON to traversible Python objects." authors = ["Lukas Weidenholzer ", "Sean Hoyal ", "Valentina Hutter ", "Gerald Irsiegler "] From 65ac1c1a0211603a6102d78e198e50891194712e Mon Sep 17 00:00:00 2001 From: Gerald Walter Irsiegler Date: Tue, 23 Jul 2024 12:14:04 +0200 Subject: [PATCH 6/9] Fix: Model Validation not being strict. (#88) --- openeo_pg_parser_networkx/graph.py | 34 ++++++++++++------------ openeo_pg_parser_networkx/pg_schema.py | 5 ++-- pyproject.toml | 2 +- tests/test_pg_parser.py | 36 ++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 20 deletions(-) diff --git a/openeo_pg_parser_networkx/graph.py b/openeo_pg_parser_networkx/graph.py index d054607..fdabd83 100644 --- a/openeo_pg_parser_networkx/graph.py +++ b/openeo_pg_parser_networkx/graph.py @@ -113,7 +113,7 @@ def _parse_datamodel(nested_graph: dict) -> ProcessGraph: Parses a nested process graph into the Pydantic datamodel for ProcessGraph. """ - return ProcessGraph.model_validate(nested_graph) + return ProcessGraph.model_validate(nested_graph, strict=True) def _parse_process_graph(self, process_graph: ProcessGraph, arg_name: str = None): """ @@ -187,11 +187,11 @@ def _parse_argument(self, arg: any, arg_name: str, access_func: Callable): # This access func business is necessary to let the program "remember" how to access and thus update this reference later sub_access_func = partial( - lambda key, access_func, new_value=None, set_bool=False: access_func()[ - key - ] - if not set_bool - else access_func().__setitem__(key, new_value), + lambda key, access_func, new_value=None, set_bool=False: ( + access_func()[key] + if not set_bool + else access_func().__setitem__(key, new_value) + ), key=k, access_func=access_func, ) @@ -205,11 +205,11 @@ def _parse_argument(self, arg: any, arg_name: str, access_func: Callable): parsed_arg = parse_nested_parameter(element) sub_access_func = partial( - lambda key, access_func, new_value=None, set_bool=False: access_func()[ - key - ] - if not set_bool - else access_func().__setitem__(key, new_value), + lambda key, access_func, new_value=None, set_bool=False: ( + access_func()[key] + if not set_bool + else access_func().__setitem__(key, new_value) + ), key=i, access_func=access_func, ) @@ -246,12 +246,12 @@ def _walk_node(self): # This just points to the resolved_kwarg itself! access_func = partial( - lambda node_uid, arg_name, new_value=None, set_bool=False: self.G.nodes[ - node_uid - ]["resolved_kwargs"][arg_name] - if not set_bool - else self.G.nodes[node_uid]["resolved_kwargs"].__setitem__( - arg_name, new_value + lambda node_uid, arg_name, new_value=None, set_bool=False: ( + self.G.nodes[node_uid]["resolved_kwargs"][arg_name] + if not set_bool + else self.G.nodes[node_uid]["resolved_kwargs"].__setitem__( + arg_name, new_value + ) ), node_uid=self._EVAL_ENV.node_uid, arg_name=arg_name, diff --git a/openeo_pg_parser_networkx/pg_schema.py b/openeo_pg_parser_networkx/pg_schema.py index 0269100..fa48c43 100644 --- a/openeo_pg_parser_networkx/pg_schema.py +++ b/openeo_pg_parser_networkx/pg_schema.py @@ -25,7 +25,6 @@ RootModel, StringConstraints, ValidationError, - conlist, constr, field_validator, model_validator, @@ -259,7 +258,9 @@ def __repr__(self): class TemporalInterval(RootModel): - root: conlist(Union[Year, Date, DateTime, Time, None], min_length=2, max_length=2) + root: Annotated[ + list[Union[Year, Date, DateTime, Time, None]], Field(min_length=2, max_length=2) + ] @field_validator("root") def validate_temporal_interval(cls, value: Any) -> Any: diff --git a/pyproject.toml b/pyproject.toml index 40a6835..d20c959 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "openeo-pg-parser-networkx" -version = "2024.5.0" +version = "2024.7.0" description = "Parse OpenEO process graphs from JSON to traversible Python objects." authors = ["Lukas Weidenholzer ", "Sean Hoyal ", "Valentina Hutter ", "Gerald Irsiegler "] diff --git a/tests/test_pg_parser.py b/tests/test_pg_parser.py index 15ba3b5..7773d63 100644 --- a/tests/test_pg_parser.py +++ b/tests/test_pg_parser.py @@ -159,6 +159,42 @@ def test_bounding_box_with_faulty_crs(get_process_graph_with_args): ] +def test_string_validation(get_process_graph_with_args): + ''' + During the pydantic 2 update, we found that some special strings get parsed + to non-string values ('t' to True, 'f' to False, etc.) + + Check that every incoming string stays a string by default + ''' + + test_args = { + 'arg_t': 't', + 'arg_f': 'f', + 'arg_str': 'arg_123_str', + 'arg_int': '123', + 'arg_float': '123.4', + } + + pg = get_process_graph_with_args(test_args) + + # Parse indirectly to check if model validation is strict and does not type coerce + parsed_graph = OpenEOProcessGraph(pg_data=pg) + + # Parse directly to check if strict model validation works seperately + parsed_args = [ + ProcessGraph.model_validate(pg, strict=True) + .process_graph[TEST_NODE_KEY] + .arguments[arg_name] + for arg_name in test_args.keys() + ] + + resolved_kwargs = parsed_graph.nodes[0][1]['resolved_kwargs'].items() + + assert all([isinstance(resolved_kwarg, str) for _, resolved_kwarg in resolved_kwargs]) + + assert all([isinstance(parsed_arg, str) for parsed_arg in parsed_args]) + + def test_bounding_box_int_crs(get_process_graph_with_args): pg = get_process_graph_with_args( {'spatial_extent': {'west': 0, 'east': 10, 'south': 0, 'north': 10, 'crs': 4326}} From 766ad3a97471b70edda5115b2143aa6bcef966dd Mon Sep 17 00:00:00 2001 From: Michele Claus <31700619+clausmichele@users.noreply.github.com> Date: Wed, 28 Aug 2024 09:33:56 +0200 Subject: [PATCH 7/9] Improve demo notebook (#87) --- examples/01_minibackend_demo.ipynb | 644 ++++++++++++++++++++++------- 1 file changed, 486 insertions(+), 158 deletions(-) diff --git a/examples/01_minibackend_demo.ipynb b/examples/01_minibackend_demo.ipynb index fd469d9..b8b7886 100644 --- a/examples/01_minibackend_demo.ipynb +++ b/examples/01_minibackend_demo.ipynb @@ -17,7 +17,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -40,39 +39,10 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "Deserialised process graph into nested structure\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Walking node root-e298ce00-9131-48e3-94a2-559580f96fe9\n", - "Walking node mintime-e298ce00-9131-48e3-94a2-559580f96fe9\n", - "Walking node min-b130a201-85a8-4792-be65-688af048cbf1\n", - "Walking node evi-e298ce00-9131-48e3-94a2-559580f96fe9\n", - "Walking node m3-06d7a847-5e5b-43e3-b162-96523efef2c1\n", - "Walking node div-06d7a847-5e5b-43e3-b162-96523efef2c1\n", - "Walking node sub-06d7a847-5e5b-43e3-b162-96523efef2c1\n", - "Walking node nir-06d7a847-5e5b-43e3-b162-96523efef2c1\n", - "Walking node red-06d7a847-5e5b-43e3-b162-96523efef2c1\n", - "Walking node add_one-06d7a847-5e5b-43e3-b162-96523efef2c1\n", - "Walking node sum-06d7a847-5e5b-43e3-b162-96523efef2c1\n", - "Walking node nir-06d7a847-5e5b-43e3-b162-96523efef2c1\n", - "Walking node m1-06d7a847-5e5b-43e3-b162-96523efef2c1\n", - "Walking node red-06d7a847-5e5b-43e3-b162-96523efef2c1\n", - "Walking node m2-06d7a847-5e5b-43e3-b162-96523efef2c1\n", - "Walking node blue-06d7a847-5e5b-43e3-b162-96523efef2c1\n", - "Walking node load_collection-e298ce00-9131-48e3-94a2-559580f96fe9\n" - ] - } - ], + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "from openeo_pg_parser_networkx import OpenEOProcessGraph\n", "\n", @@ -84,12 +54,14 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 2, @@ -111,11 +83,13 @@ { "cell_type": "code", "execution_count": 3, - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] @@ -129,7 +103,6 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ @@ -162,95 +135,9 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mWARNING: The directory '/home/gerald/.cache/pip' or its parent directory is not owned or is not writable by the current user. The cache has been disabled. Check the permissions and owner of that directory. If executing pip with sudo, you should use sudo's -H flag.\u001b[0m\u001b[33m\n", - "\u001b[0mRequirement already satisfied: openeo_processes_dask[implementations] in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (2023.7.1)\n", - "Requirement already satisfied: dask-geopandas<1,>=0.2.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo_processes_dask[implementations]) (0.3.1)\n", - "Requirement already satisfied: dask[array]>=2022.11.1 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo_processes_dask[implementations]) (2023.7.0)\n", - "Requirement already satisfied: geopandas<1,>=0.11.1 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo_processes_dask[implementations]) (0.13.2)\n", - "Requirement already satisfied: odc-geo<0.4.0,>=0.3.2 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo_processes_dask[implementations]) (0.3.3)\n", - "Requirement already satisfied: openeo-pg-parser-networkx>=2023.5.1 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo_processes_dask[implementations]) (2023.5.1)\n", - "Requirement already satisfied: planetary_computer>=0.5.1 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo_processes_dask[implementations]) (1.0.0)\n", - "Requirement already satisfied: pystac_client>=0.6.1 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo_processes_dask[implementations]) (0.7.2)\n", - "Requirement already satisfied: rasterio<2.0.0,>=1.3.4 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo_processes_dask[implementations]) (1.3.8)\n", - "Requirement already satisfied: rioxarray<1,>=0.12.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo_processes_dask[implementations]) (0.14.1)\n", - "Requirement already satisfied: stac_validator>=3.3.1 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo_processes_dask[implementations]) (3.3.1)\n", - "Requirement already satisfied: stackstac>=0.4.3 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo_processes_dask[implementations]) (0.4.4)\n", - "Requirement already satisfied: xarray>=2022.11.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo_processes_dask[implementations]) (2023.6.0)\n", - "Requirement already satisfied: distributed>=2021.06.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from dask-geopandas<1,>=0.2.0->openeo_processes_dask[implementations]) (2023.7.0)\n", - "Requirement already satisfied: packaging in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from dask-geopandas<1,>=0.2.0->openeo_processes_dask[implementations]) (23.1)\n", - "Requirement already satisfied: click>=8.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from dask[array]>=2022.11.1->openeo_processes_dask[implementations]) (8.1.4)\n", - "Requirement already satisfied: cloudpickle>=1.5.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from dask[array]>=2022.11.1->openeo_processes_dask[implementations]) (2.2.1)\n", - "Requirement already satisfied: fsspec>=2021.09.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from dask[array]>=2022.11.1->openeo_processes_dask[implementations]) (2023.6.0)\n", - "Requirement already satisfied: partd>=1.2.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from dask[array]>=2022.11.1->openeo_processes_dask[implementations]) (1.4.0)\n", - "Requirement already satisfied: pyyaml>=5.3.1 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from dask[array]>=2022.11.1->openeo_processes_dask[implementations]) (6.0)\n", - "Requirement already satisfied: toolz>=0.10.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from dask[array]>=2022.11.1->openeo_processes_dask[implementations]) (0.12.0)\n", - "Requirement already satisfied: importlib-metadata>=4.13.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from dask[array]>=2022.11.1->openeo_processes_dask[implementations]) (6.8.0)\n", - "Requirement already satisfied: numpy>=1.21 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from dask[array]>=2022.11.1->openeo_processes_dask[implementations]) (1.25.1)\n", - "Requirement already satisfied: fiona>=1.8.19 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from geopandas<1,>=0.11.1->openeo_processes_dask[implementations]) (1.9.4.post1)\n", - "Requirement already satisfied: pandas>=1.1.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from geopandas<1,>=0.11.1->openeo_processes_dask[implementations]) (2.0.3)\n", - "Requirement already satisfied: pyproj>=3.0.1 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from geopandas<1,>=0.11.1->openeo_processes_dask[implementations]) (3.6.0)\n", - "Requirement already satisfied: shapely>=1.7.1 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from geopandas<1,>=0.11.1->openeo_processes_dask[implementations]) (2.0.1)\n", - "Requirement already satisfied: affine in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from odc-geo<0.4.0,>=0.3.2->openeo_processes_dask[implementations]) (2.4.0)\n", - "Requirement already satisfied: cachetools in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from odc-geo<0.4.0,>=0.3.2->openeo_processes_dask[implementations]) (5.3.1)\n", - "Requirement already satisfied: geojson-pydantic<0.6.0,>=0.5.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo-pg-parser-networkx>=2023.5.1->openeo_processes_dask[implementations]) (0.5.0)\n", - "Requirement already satisfied: networkx<3.0.0,>=2.8.6 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo-pg-parser-networkx>=2023.5.1->openeo_processes_dask[implementations]) (2.8.8)\n", - "Requirement already satisfied: pendulum<3.0.0,>=2.1.2 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo-pg-parser-networkx>=2023.5.1->openeo_processes_dask[implementations]) (2.1.2)\n", - "Requirement already satisfied: pydantic<2.0.0,>=1.9.1 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from openeo-pg-parser-networkx>=2023.5.1->openeo_processes_dask[implementations]) (1.10.11)\n", - "Requirement already satisfied: pystac>=1.0.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from planetary_computer>=0.5.1->openeo_processes_dask[implementations]) (1.8.2)\n", - "Requirement already satisfied: pytz>=2020.5 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from planetary_computer>=0.5.1->openeo_processes_dask[implementations]) (2023.3)\n", - "Requirement already satisfied: requests>=2.25.1 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from planetary_computer>=0.5.1->openeo_processes_dask[implementations]) (2.31.0)\n", - "Requirement already satisfied: python-dotenv in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from planetary_computer>=0.5.1->openeo_processes_dask[implementations]) (1.0.0)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from pystac_client>=0.6.1->openeo_processes_dask[implementations]) (2.8.2)\n", - "Requirement already satisfied: attrs in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from rasterio<2.0.0,>=1.3.4->openeo_processes_dask[implementations]) (23.1.0)\n", - "Requirement already satisfied: certifi in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from rasterio<2.0.0,>=1.3.4->openeo_processes_dask[implementations]) (2023.5.7)\n", - "Requirement already satisfied: cligj>=0.5 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from rasterio<2.0.0,>=1.3.4->openeo_processes_dask[implementations]) (0.7.2)\n", - "Requirement already satisfied: snuggs>=1.4.1 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from rasterio<2.0.0,>=1.3.4->openeo_processes_dask[implementations]) (1.4.7)\n", - "Requirement already satisfied: click-plugins in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from rasterio<2.0.0,>=1.3.4->openeo_processes_dask[implementations]) (1.1.1)\n", - "Requirement already satisfied: setuptools in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from rasterio<2.0.0,>=1.3.4->openeo_processes_dask[implementations]) (68.0.0)\n", - "Requirement already satisfied: jsonschema>=3.2.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from stac_validator>=3.3.1->openeo_processes_dask[implementations]) (4.17.3)\n", - "Requirement already satisfied: types-setuptools in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from stac_validator>=3.3.1->openeo_processes_dask[implementations]) (68.0.0.1)\n", - "Requirement already satisfied: jinja2>=2.10.3 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from distributed>=2021.06.0->dask-geopandas<1,>=0.2.0->openeo_processes_dask[implementations]) (3.1.2)\n", - "Requirement already satisfied: locket>=1.0.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from distributed>=2021.06.0->dask-geopandas<1,>=0.2.0->openeo_processes_dask[implementations]) (1.0.0)\n", - "Requirement already satisfied: msgpack>=1.0.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from distributed>=2021.06.0->dask-geopandas<1,>=0.2.0->openeo_processes_dask[implementations]) (1.0.5)\n", - "Requirement already satisfied: psutil>=5.7.2 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from distributed>=2021.06.0->dask-geopandas<1,>=0.2.0->openeo_processes_dask[implementations]) (5.9.5)\n", - "Requirement already satisfied: sortedcontainers>=2.0.5 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from distributed>=2021.06.0->dask-geopandas<1,>=0.2.0->openeo_processes_dask[implementations]) (2.4.0)\n", - "Requirement already satisfied: tblib>=1.6.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from distributed>=2021.06.0->dask-geopandas<1,>=0.2.0->openeo_processes_dask[implementations]) (2.0.0)\n", - "Requirement already satisfied: tornado>=6.0.4 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from distributed>=2021.06.0->dask-geopandas<1,>=0.2.0->openeo_processes_dask[implementations]) (6.3.2)\n", - "Requirement already satisfied: urllib3>=1.24.3 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from distributed>=2021.06.0->dask-geopandas<1,>=0.2.0->openeo_processes_dask[implementations]) (2.0.3)\n", - "Requirement already satisfied: zict>=2.2.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from distributed>=2021.06.0->dask-geopandas<1,>=0.2.0->openeo_processes_dask[implementations]) (3.0.0)\n", - "Requirement already satisfied: six in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from fiona>=1.8.19->geopandas<1,>=0.11.1->openeo_processes_dask[implementations]) (1.16.0)\n", - "Requirement already satisfied: zipp>=0.5 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from importlib-metadata>=4.13.0->dask[array]>=2022.11.1->openeo_processes_dask[implementations]) (3.16.1)\n", - "Requirement already satisfied: pyrsistent!=0.17.0,!=0.17.1,!=0.17.2,>=0.14.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from jsonschema>=3.2.0->stac_validator>=3.3.1->openeo_processes_dask[implementations]) (0.19.3)\n", - "Requirement already satisfied: tzdata>=2022.1 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from pandas>=1.1.0->geopandas<1,>=0.11.1->openeo_processes_dask[implementations]) (2023.3)\n", - "Requirement already satisfied: pytzdata>=2020.1 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from pendulum<3.0.0,>=2.1.2->openeo-pg-parser-networkx>=2023.5.1->openeo_processes_dask[implementations]) (2020.1)\n", - "Requirement already satisfied: typing-extensions>=4.2.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from pydantic<2.0.0,>=1.9.1->openeo-pg-parser-networkx>=2023.5.1->openeo_processes_dask[implementations]) (4.7.1)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from requests>=2.25.1->planetary_computer>=0.5.1->openeo_processes_dask[implementations]) (3.2.0)\n", - "Requirement already satisfied: idna<4,>=2.5 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from requests>=2.25.1->planetary_computer>=0.5.1->openeo_processes_dask[implementations]) (3.4)\n", - "Requirement already satisfied: pyparsing>=2.1.6 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from snuggs>=1.4.1->rasterio<2.0.0,>=1.3.4->openeo_processes_dask[implementations]) (3.1.0)\n", - "Requirement already satisfied: MarkupSafe>=2.0 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from jinja2>=2.10.3->distributed>=2021.06.0->dask-geopandas<1,>=0.2.0->openeo_processes_dask[implementations]) (2.1.3)\n", - "\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m23.1.2\u001b[0m\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", - "Note: you may need to restart the kernel to use updated packages.\n", - "\u001b[33mWARNING: The directory '/home/gerald/.cache/pip' or its parent directory is not owned or is not writable by the current user. The cache has been disabled. Check the permissions and owner of that directory. If executing pip with sudo, you should use sudo's -H flag.\u001b[0m\u001b[33m\n", - "\u001b[0mRequirement already satisfied: netCDF4 in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (1.6.4)\n", - "Requirement already satisfied: cftime in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from netCDF4) (1.6.2)\n", - "Requirement already satisfied: certifi in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from netCDF4) (2023.5.7)\n", - "Requirement already satisfied: numpy in /home/gerald/.cache/pypoetry/virtualenvs/openeo-pg-parser-networkx-tLltH1mo-py3.10/lib/python3.10/site-packages (from netCDF4) (1.25.1)\n", - "\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m23.1.2\u001b[0m\n", - "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", - "Note: you may need to restart the kernel to use updated packages.\n" - ] - } - ], + "outputs": [], "source": [ "%pip install \"openeo_processes_dask[implementations]\"\n", "%pip install \"netCDF4\"" @@ -258,21 +145,11 @@ }, { "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [ - { - "ename": "ImportError", - "evalue": "attempted relative import with no known parent package", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mImportError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[19], line 4\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[39mimport\u001b[39;00m \u001b[39minspect\u001b[39;00m\n\u001b[1;32m 3\u001b[0m \u001b[39m#from openeo_pg_parser_networkx import ProcessRegistry\u001b[39;00m\n\u001b[0;32m----> 4\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39m.\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mopeneo_pg_parser_networkx\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mprocess_registry\u001b[39;00m \u001b[39mimport\u001b[39;00m ProcessRegistry\n\u001b[1;32m 5\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39mopeneo_processes_dask\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mprocess_implementations\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mcore\u001b[39;00m \u001b[39mimport\u001b[39;00m process\n\u001b[1;32m 6\u001b[0m \u001b[39mfrom\u001b[39;00m \u001b[39mopeneo_pg_parser_networkx\u001b[39;00m\u001b[39m.\u001b[39;00m\u001b[39mprocess_registry\u001b[39;00m \u001b[39mimport\u001b[39;00m Process\n", - "\u001b[0;31mImportError\u001b[0m: attempted relative import with no known parent package" - ] - } - ], + "execution_count": 20, + "metadata": { + "tags": [] + }, + "outputs": [], "source": [ "import importlib\n", "import inspect\n", @@ -311,45 +188,497 @@ "\n", "def save_result(data, format = 'netcdf', options = None):\n", " # No generic implementation available, so need to implement locally!\n", - " pass\n", + " data.attrs = {}\n", + " data.to_netcdf(\"./data/result.nc\")\n", + " return True\n", "\n", "from openeo_processes_dask.specs import load_collection as load_collection_spec\n", "from openeo_processes_dask.specs import save_result as save_result_spec\n", "\n", "process_registry[\"load_collection\"] = Process(spec=load_collection_spec, implementation=load_collection)\n", - "process_registry[\"save_result\"] = Process(spec=save_result_spec, implementation=save_result)\n", - "\n" + "process_registry[\"save_result\"] = Process(spec=save_result_spec, implementation=save_result)" ] }, { "cell_type": "code", - "execution_count": 11, - "metadata": {}, + "execution_count": 21, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ "pg_callable = parsed_graph.to_callable(process_registry=process_registry)" ] }, { - "cell_type": "code", - "execution_count": 14, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "pg_callable()" + "Run the workflow" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/mclaus@eurac.edu/openeo-processes-dask/openeo_processes_dask/process_implementations/math.py:90: RuntimeWarning: divide by zero encountered in divide\n", + " result = x / y\n" + ] + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pg_callable()" + ] + }, + { + "cell_type": "markdown", "metadata": {}, - "outputs": [], - "source": [] + "source": [ + "Open the EVI result stored as a netCDF" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.DataArray (y: 27, x: 40)> Size: 9kB\n",
+       "[1080 values with dtype=float64]\n",
+       "Coordinates:\n",
+       "  * y            (y) float64 216B 1.608e+06 1.608e+06 ... 1.608e+06 1.608e+06\n",
+       "  * x            (x) float64 320B 5.249e+06 5.249e+06 ... 5.249e+06 5.249e+06\n",
+       "    spatial_ref  int32 4B ...
" + ], + "text/plain": [ + " Size: 9kB\n", + "[1080 values with dtype=float64]\n", + "Coordinates:\n", + " * y (y) float64 216B 1.608e+06 1.608e+06 ... 1.608e+06 1.608e+06\n", + " * x (x) float64 320B 5.249e+06 5.249e+06 ... 5.249e+06 5.249e+06\n", + " spatial_ref int32 4B ..." + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import xarray as xr\n", + "\n", + "ds_out = xr.open_dataarray(\"./data/result.nc\")\n", + "ds_out" + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3.9.5 ('.venv': poetry)", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -363,9 +692,8 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.6" + "version": "3.10.12" }, - "orig_nbformat": 4, "vscode": { "interpreter": { "hash": "43bfd548961cb44d0ba5c288dd3238b5cc2de91951eb0a07084fe475948c38b4" @@ -373,5 +701,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } From 444ed5f6ef5b63c34148a29a60e9a24d3a8c0779 Mon Sep 17 00:00:00 2001 From: ValentinaHutter <85164505+ValentinaHutter@users.noreply.github.com> Date: Wed, 9 Oct 2024 08:55:40 +0200 Subject: [PATCH 8/9] allow None in PG (#91) --- openeo_pg_parser_networkx/pg_schema.py | 1 + pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openeo_pg_parser_networkx/pg_schema.py b/openeo_pg_parser_networkx/pg_schema.py index fa48c43..d79813d 100644 --- a/openeo_pg_parser_networkx/pg_schema.py +++ b/openeo_pg_parser_networkx/pg_schema.py @@ -95,6 +95,7 @@ class ProcessNode(BaseModel, arbitrary_types_allowed=True): list, dict, str, + None, ], Field(union_mode='left_to_right'), ], diff --git a/pyproject.toml b/pyproject.toml index d20c959..6f5f1d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "openeo-pg-parser-networkx" -version = "2024.7.0" +version = "2024.10.0" description = "Parse OpenEO process graphs from JSON to traversible Python objects." authors = ["Lukas Weidenholzer ", "Sean Hoyal ", "Valentina Hutter ", "Gerald Irsiegler "] From 9f72a9d284bf2fa7799962d3fe60f52e96ebfada Mon Sep 17 00:00:00 2001 From: Gerald Walter Irsiegler Date: Thu, 10 Oct 2024 11:50:05 +0200 Subject: [PATCH 9/9] Update: Extra none tests (#92) --- pyproject.toml | 2 +- tests/conftest.py | 5 +++ tests/data/graphs/none_1.json | 58 ++++++++++++++++++++++++++++++++++ tests/data/graphs/none_2.json | 58 ++++++++++++++++++++++++++++++++++ tests/data/graphs/none_3.json | 59 +++++++++++++++++++++++++++++++++++ tests/test_pg_parser.py | 12 +++++++ 6 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 tests/data/graphs/none_1.json create mode 100644 tests/data/graphs/none_2.json create mode 100644 tests/data/graphs/none_3.json diff --git a/pyproject.toml b/pyproject.toml index 6f5f1d1..025d7f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "openeo-pg-parser-networkx" -version = "2024.10.0" +version = "2024.10.1" description = "Parse OpenEO process graphs from JSON to traversible Python objects." authors = ["Lukas Weidenholzer ", "Sean Hoyal ", "Valentina Hutter ", "Gerald Irsiegler "] diff --git a/tests/conftest.py b/tests/conftest.py index 91b5ff7..64388d0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,6 +37,11 @@ def process_graph_path(request) -> Path: return request.param +@pytest.fixture +def specific_graph(path) -> dict: + return json.load(path) + + @pytest.fixture def process_registry() -> ProcessRegistry: registry = ProcessRegistry(wrap_funcs=[]) diff --git a/tests/data/graphs/none_1.json b/tests/data/graphs/none_1.json new file mode 100644 index 0000000..2399d0b --- /dev/null +++ b/tests/data/graphs/none_1.json @@ -0,0 +1,58 @@ +{ + "process_graph": { + "load1": { + "process_id": "load_collection", + "arguments": { + "id": "boa_sentinel_2", + "spatial_extent": { + "west": 5, + "east": 5.1, + "south": 51.2, + "north": 51.3 + }, + "temporal_extent": [ + "2019-07-04T12:00:00+00:00", + "2021-06-22T12:00:00+00:00" + ], + "bands": null + } + }, + "filtertemporal1": { + "process_id": "filter_temporal", + "arguments": { + "data": { + "from_node": "load1" + }, + "extent": [ + "2020-08-01", + "2020-08-10" + ] + } + }, + "filterbbox1": { + "process_id": "filter_bbox", + "arguments": { + "data": { + "from_node": "filtertemporal1" + }, + "extent": { + "west": 5.07, + "east": 5.1, + "north": 51.23, + "south": 51.21 + } + } + }, + "saveresult1": { + "process_id": "save_result", + "arguments": { + "data": { + "from_node": "filterbbox1" + }, + "format": "NetCDF", + "options": null + }, + "result": true + } + } +} diff --git a/tests/data/graphs/none_2.json b/tests/data/graphs/none_2.json new file mode 100644 index 0000000..a1c8595 --- /dev/null +++ b/tests/data/graphs/none_2.json @@ -0,0 +1,58 @@ +{ + "process_graph": { + "load1": { + "process_id": "load_collection", + "arguments": { + "id": "boa_sentinel_2", + "spatial_extent": { + "west": 5, + "east": 5.1, + "south": 51.2, + "north": 51.3 + }, + "temporal_extent": [ + "2019-07-04T12:00:00+00:00", + "2021-06-22T12:00:00+00:00" + ], + "bands": null + } + }, + "filtertemporal1": { + "process_id": "filter_temporal", + "arguments": { + "data": { + "from_node": "load1" + }, + "extent": [ + "2020-08-01", + "2020-08-10" + ] + } + }, + "filterbbox1": { + "process_id": "filter_bbox", + "arguments": { + "data": { + "from_node": "filtertemporal1" + }, + "extent": { + "west": 5.07, + "east": 5.1, + "north": 51.23, + "south": 51.21 + } + } + }, + "saveresult1": { + "process_id": "save_result", + "arguments": { + "data": { + "from_node": "filterbbox1" + }, + "format": "NetCDF", + "options": null + }, + "result": true + } + } +} diff --git a/tests/data/graphs/none_3.json b/tests/data/graphs/none_3.json new file mode 100644 index 0000000..89554e2 --- /dev/null +++ b/tests/data/graphs/none_3.json @@ -0,0 +1,59 @@ +{ + "process_graph": { + "load1": { + "process_id": "load_collection", + "arguments": { + "id": "boa_sentinel_2", + "spatial_extent": { + "west": 16.354249035707454, + "east": 16.397538190976075, + "south": 48.19099103894396, + "north": 48.22215063861063 + }, + "temporal_extent": null, + "bands": [ + "B04" + ], + "properties": {} + } + }, + "load2": { + "process_id": "load_collection", + "arguments": { + "id": "boa_sentinel_2", + "spatial_extent": { + "west": 16.354249035707454, + "east": 16.397538190976075, + "south": 48.19099103894396, + "north": 48.22215063861063 + }, + "temporal_extent": [ + "2019-01-01T00:00:00Z", + "2019-06-01T00:00:00Z" + ], + "bands": null + } + }, + "save1": { + "process_id": "save_result", + "arguments": { + "data": { + "from_node": "resample1" + }, + "format": "NETCDF" + }, + "result": true + }, + "resample1": { + "process_id": "resample_cube_spatial", + "arguments": { + "data": { + "from_node": "load1" + }, + "target": { + "from_node": "load2" + } + } + } + } +} diff --git a/tests/test_pg_parser.py b/tests/test_pg_parser.py index 7773d63..5dd3db5 100644 --- a/tests/test_pg_parser.py +++ b/tests/test_pg_parser.py @@ -195,6 +195,18 @@ def test_string_validation(get_process_graph_with_args): assert all([isinstance(parsed_arg, str) for parsed_arg in parsed_args]) +@pytest.mark.parametrize( + "specific_graph,expected_nodes", + [path for path in zip((TEST_DATA_DIR / "graphs").glob('none_*.json'), [4, 4, 4])], +) +def test_none_parameter(specific_graph, expected_nodes): + with open(specific_graph) as fp: + pg_data = json.load(fp=fp) + + parsed_graph = OpenEOProcessGraph(pg_data=pg_data) + assert len(parsed_graph.nodes) == expected_nodes + + def test_bounding_box_int_crs(get_process_graph_with_args): pg = get_process_graph_with_args( {'spatial_extent': {'west': 0, 'east': 10, 'south': 0, 'north': 10, 'crs': 4326}}