Skip to content

Commit bbb36c6

Browse files
authored
support additional from_parameter in property filter
* seems functional, needs work #327 * clean up and add tests #327 * retain backwards compatibility wrt/ openeo-geopyspark-driver #327 * update CHANGELOG #327 * improve error message #327 * update version and CHANGELOG #327
1 parent 4e181db commit bbb36c6

9 files changed

+132
-29
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ and start a new "In Progress" section above it.
2121

2222
## In progress
2323

24+
## 0.124.0
25+
2426
- Better argument validation in `resample_spatial`/`resample_cube_spatial` (related to [Open-EO/openeo-python-client#690](https://github.com/Open-EO/openeo-python-client/issues/690))
2527
- Improve `resample_spatial`/`resample_cube_spatial` metadata tracking in dry-run ([#348](https://github.com/Open-EO/openeo-python-driver/issues/348))
26-
28+
- `load_collection`/`load_stac`: support parameters in `properties` ([#327](https://github.com/Open-EO/openeo-python-driver/issues/327))
2729

2830
## 0.123.0
2931

openeo_driver/ProcessGraphDeserializer.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -759,15 +759,18 @@ def load_collection(args: dict, env: EvalEnv) -> DriverDataCube:
759759

760760
dry_run_tracer: DryRunDataTracer = env.get(ENV_DRY_RUN_TRACER)
761761
if dry_run_tracer:
762-
return dry_run_tracer.load_collection(collection_id=collection_id, arguments=arguments, metadata=metadata)
762+
return dry_run_tracer.load_collection(
763+
collection_id=collection_id, arguments=arguments, metadata=metadata, env=env
764+
)
763765
else:
764766
# Extract basic source constraints.
765767
# TODO #275: eliminate this VITO specific handling?
766768
properties = {**CollectionMetadata(metadata).get("_vito", "properties", default={}),
767769
**arguments.get("properties", {})}
768770

769-
source_id = dry_run.DataSource.load_collection(collection_id=collection_id,
770-
properties=properties,bands = arguments.get("bands",[])).get_source_id()
771+
source_id = dry_run.DataSource.load_collection(
772+
collection_id=collection_id, properties=properties, bands=arguments.get("bands", []), env=env
773+
).get_source_id()
771774
load_params = _extract_load_parameters(env, source_id=source_id)
772775
# Override with explicit arguments
773776
load_params.update(arguments)
@@ -2416,9 +2419,11 @@ def load_stac(args: Dict, env: EvalEnv) -> DriverDataCube:
24162419

24172420
dry_run_tracer: DryRunDataTracer = env.get(ENV_DRY_RUN_TRACER)
24182421
if dry_run_tracer:
2419-
return dry_run_tracer.load_stac(url, arguments)
2422+
return dry_run_tracer.load_stac(url, arguments, env)
24202423
else:
2421-
source_id = dry_run.DataSource.load_stac(url, properties=arguments.get("properties", {}), bands=arguments.get("bands",[])).get_source_id()
2424+
source_id = dry_run.DataSource.load_stac(
2425+
url, properties=arguments.get("properties", {}), bands=arguments.get("bands", []), env=env
2426+
).get_source_id()
24222427
load_params = _extract_load_parameters(env, source_id=source_id)
24232428
load_params.update(arguments)
24242429

openeo_driver/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.123.0a1"
1+
__version__ = "0.124.0a1"

openeo_driver/dry_run.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,10 @@ def describe(self) -> str:
167167
return self._process
168168

169169
@classmethod
170-
def load_collection(cls, collection_id, properties={}, bands=[]) -> "DataSource":
170+
def load_collection(cls, collection_id, properties={}, bands=[], env=EvalEnv()) -> "DataSource":
171171
"""Factory for a `load_collection` DataSource."""
172172
exact_property_matches = {
173-
property_name: filter_properties.extract_literal_match(condition)
173+
property_name: filter_properties.extract_literal_match(condition, env)
174174
for property_name, condition in properties.items()
175175
}
176176

@@ -192,10 +192,10 @@ def load_result(cls, job_id: str) -> "DataSource":
192192
return cls(process="load_result", arguments=(job_id,))
193193

194194
@classmethod
195-
def load_stac(cls, url: str, properties={}, bands=[]) -> "DataSource":
195+
def load_stac(cls, url: str, properties={}, bands=[], env=EvalEnv()) -> "DataSource":
196196
"""Factory for a `load_stac` DataSource."""
197197
exact_property_matches = {
198-
property_name: filter_properties.extract_literal_match(condition)
198+
property_name: filter_properties.extract_literal_match(condition, env)
199199
for property_name, condition in properties.items()
200200
}
201201

@@ -273,7 +273,9 @@ def process_traces(self, traces: List[DataTraceBase], operation: str, arguments:
273273
"""Process given traces with an operation (and keep track of the results)."""
274274
return [self.add_trace(DataTrace(parent=t, operation=operation, arguments=arguments)) for t in traces]
275275

276-
def load_collection(self, collection_id: str, arguments: dict, metadata: dict = None) -> "DryRunDataCube":
276+
def load_collection(
277+
self, collection_id: str, arguments: dict, metadata: dict = None, env: EvalEnv = EvalEnv()
278+
) -> "DryRunDataCube":
277279
"""Create a DryRunDataCube from a `load_collection` process."""
278280
# TODO #275 avoid VITO/Terrascope specific handling here?
279281
properties = {
@@ -282,7 +284,7 @@ def load_collection(self, collection_id: str, arguments: dict, metadata: dict =
282284
}
283285

284286
trace = DataSource.load_collection(
285-
collection_id=collection_id, properties=properties, bands=arguments.get("bands", [])
287+
collection_id=collection_id, properties=properties, bands=arguments.get("bands", []), env=env
286288
)
287289
self.add_trace(trace)
288290

@@ -329,10 +331,10 @@ def load_result(self, job_id: str, arguments: dict) -> "DryRunDataCube":
329331

330332
return cube
331333

332-
def load_stac(self, url: str, arguments: dict) -> "DryRunDataCube":
334+
def load_stac(self, url: str, arguments: dict, env: EvalEnv = EvalEnv()) -> "DryRunDataCube":
333335
properties = arguments.get("properties", {})
334336

335-
trace = DataSource.load_stac(url=url, properties=properties, bands=arguments.get("bands", []))
337+
trace = DataSource.load_stac(url=url, properties=properties, bands=arguments.get("bands", []), env=env)
336338
self.add_trace(trace)
337339

338340
metadata = CollectionMetadata(

openeo_driver/filter_properties.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from openeo.internal.process_graph_visitor import ProcessGraphVisitor
44
from openeo_driver.errors import OpenEOApiException
5+
from openeo_driver.utils import EvalEnv
56

67

78
class PropertyConditionException(OpenEOApiException):
@@ -10,11 +11,12 @@ class PropertyConditionException(OpenEOApiException):
1011
message = "Property condition is invalid."
1112

1213

13-
def extract_literal_match(condition: dict, parameter_name="value") -> Dict[str, Any]:
14+
def extract_literal_match(condition: dict, env=EvalEnv()) -> Dict[str, Any]:
1415
"""
1516
Turns a condition as defined by the load_collection process into a set of criteria ((operator, value) pairs).
1617
Conditions are currently limited to processes "eq", "lte" and "gte" so they will be turned into a single criterion.
1718
"""
19+
callback_parameter_name = "value" # as in: {"from_parameter": "value"}
1820

1921
class LiteralMatchExtractingGraphVisitor(ProcessGraphVisitor):
2022

@@ -32,11 +34,20 @@ def enterProcess(self, process_id: str, arguments: dict, namespace: Union[str, N
3234

3335
self.result["operator"] = "in" if process_id == "array_contains" else process_id
3436

35-
def enterArgument(self, argument_id: str, value):
36-
self.result["parameter"] = value.get("from_parameter")
37+
def enterArgument(self, argument_id: str, value: dict):
38+
parameter_name = value.get("from_parameter")
39+
40+
if parameter_name == callback_parameter_name:
41+
self.result["parameter"] = parameter_name
42+
else:
43+
env_parameters = env.collect_parameters()
44+
if parameter_name not in env_parameters:
45+
raise PropertyConditionException(f"Unknown parameter {parameter_name}")
46+
47+
self.result["value"] = env_parameters[parameter_name]
3748

3849
def constantArgument(self, argument_id: str, value):
39-
self.result["constant"] = value
50+
self.result["value"] = value
4051

4152
if argument_id == 'x':
4253
if self.result["operator"] == "lte":
@@ -45,21 +56,21 @@ def constantArgument(self, argument_id: str, value):
4556
self.result["operator"] = "lte"
4657

4758
def constantArrayElement(self, value):
48-
if "constant" not in self.result:
49-
self.result["constant"] = []
59+
if "value" not in self.result:
60+
self.result["value"] = []
5061

51-
self.result["constant"].append(value)
62+
self.result["value"].append(value)
5263

5364
visitor = LiteralMatchExtractingGraphVisitor()
5465
visitor.accept_process_graph(condition['process_graph'])
5566

5667
if "parameter" not in visitor.result:
57-
raise PropertyConditionException(f"No parameter {parameter_name!r} found")
58-
if visitor.result["parameter"] != parameter_name:
68+
raise PropertyConditionException(f"No parameter {callback_parameter_name!r} found")
69+
if visitor.result["parameter"] != callback_parameter_name:
5970
raise PropertyConditionException(
60-
f"Expected parameter {parameter_name!r} but got {visitor.result['parameter']!r}"
71+
f"Expected parameter {callback_parameter_name!r} but got {visitor.result['parameter']!r}"
6172
)
62-
if "constant" not in visitor.result:
63-
raise PropertyConditionException(f"No comparison with constant")
73+
if "value" not in visitor.result:
74+
raise PropertyConditionException(f"No comparison with constant/value from parameter")
6475

65-
return {visitor.result["operator"]: visitor.result["constant"]}
76+
return {visitor.result["operator"]: visitor.result["value"]}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"id": "load_collection_property_from_parameter",
3+
"parameters": [
4+
{
5+
"name": "orbit_state",
6+
"schema": {
7+
"type": "string"
8+
}
9+
}
10+
],
11+
"process_graph": {
12+
"loadcollection1": {
13+
"process_id": "load_collection",
14+
"arguments": {
15+
"id": "SENTINEL1_GRD",
16+
"properties": {
17+
"sat:orbit_state": {
18+
"process_graph": {
19+
"eq1": {
20+
"arguments": {
21+
"x": {
22+
"from_parameter": "value"
23+
},
24+
"y": {
25+
"from_parameter": "orbit_state"
26+
}
27+
},
28+
"process_id": "eq",
29+
"result": true
30+
}
31+
}
32+
}
33+
}
34+
},
35+
"result": true
36+
}
37+
}
38+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"udp1": {
3+
"process_id": "load_collection_property_from_parameter",
4+
"arguments": {
5+
"orbit_state": "ASCENDING"
6+
},
7+
"result": true
8+
}
9+
}

tests/test_filter_properties.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

33
from openeo_driver.filter_properties import extract_literal_match, PropertyConditionException
4+
from openeo_driver.utils import EvalEnv
45

56

67
def test_extract_literal_match_basic():
@@ -47,9 +48,10 @@ def test_extract_literal_match_gte_reverse():
4748
])
4849
def test_extract_literal_match_failures(node):
4950
node["result"] = True
51+
env = EvalEnv().push_parameters({"data": "some parameter"})
5052
pg = {"process_graph": {"p": node}}
5153
with pytest.raises(PropertyConditionException):
52-
extract_literal_match(pg)
54+
extract_literal_match(pg, env)
5355

5456

5557
def test_array_contains():
@@ -67,3 +69,22 @@ def test_array_contains():
6769
}
6870

6971
assert extract_literal_match(pg) == {"in": ["31UES", "31UFS"]}
72+
73+
74+
def test_parameter():
75+
env = EvalEnv().push_parameters({"orbit_state": "ASCENDING"})
76+
77+
pg = {
78+
"process_graph": {
79+
"eq1": {
80+
"process_id": "eq",
81+
"arguments": {
82+
"x": {"from_parameter": "value"},
83+
"y": {"from_parameter": "orbit_state"}
84+
},
85+
"result": True
86+
}
87+
}
88+
}
89+
90+
assert extract_literal_match(pg, env) == {"eq": "ASCENDING"}

tests/test_views_execute.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4672,3 +4672,18 @@ def i_spy_with_my_little_eye(args: ProcessArgs, env: EvalEnv):
46724672
api.ensure_auth_header()
46734673
res = api.post(path="/result", json=post_data)
46744674
assert res.assert_status_code(200).json == 123
4675+
4676+
4677+
def test_load_collection_property_from_parameter(api, udp_registry):
4678+
# You can't execute a top-level process graph with parameters; instead, you execute a top-level process graph
4679+
# that calls a UDP with parameters.
4680+
4681+
api.set_auth_bearer_token(TEST_USER_BEARER_TOKEN)
4682+
udp_spec = api.load_json("udp/load_collection_property_from_parameter.json")
4683+
udp_registry.save(user_id=TEST_USER, process_id="load_collection_property_from_parameter", spec=udp_spec)
4684+
pg = api.load_json("udp_load_collection_property_from_parameter.json")
4685+
api.check_result(pg)
4686+
4687+
params = dummy_backend.last_load_collection_call("SENTINEL1_GRD")
4688+
4689+
assert "sat:orbit_state" in params.properties

0 commit comments

Comments
 (0)