dasf-exb-reference 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dasf_exb_reference-0.1.0/dasf_exb_reference.egg-info → dasf_exb_reference-0.2.0}/PKG-INFO +9 -5
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb/_version.py +3 -3
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb/backend.py +34 -5
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb/download_geodatasets.py +1 -1
- dasf_exb_reference-0.2.0/dasf_exb/input_module.py +177 -0
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb/return_models.py +3 -3
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0/dasf_exb_reference.egg-info}/PKG-INFO +9 -5
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb_reference.egg-info/SOURCES.txt +4 -1
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb_reference.egg-info/requires.txt +10 -4
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/pyproject.toml +14 -5
- dasf_exb_reference-0.2.0/tests/test_backend.py +123 -0
- dasf_exb_reference-0.2.0/tests/test_connected_module.py +41 -0
- dasf_exb_reference-0.2.0/tests/test_input_module.py +226 -0
- dasf_exb_reference-0.1.0/tests/test_backend.py +0 -58
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/LICENSES/Apache-2.0.txt +0 -0
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/LICENSES/CC-BY-4.0.txt +0 -0
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/LICENSES/CC0-1.0.txt +0 -0
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/MANIFEST.in +0 -0
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/README.md +0 -0
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb/__init__.py +0 -0
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb/input_models.py +0 -0
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb/py.typed +0 -0
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb_reference.egg-info/dependency_links.txt +0 -0
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb_reference.egg-info/not-zip-safe +0 -0
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb_reference.egg-info/top_level.txt +0 -0
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/setup.cfg +0 -0
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/setup.py +0 -0
- {dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/tests/test_imports.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dasf-exb-reference
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Reference implementation and Demonstration of a DASF Backend Module visualized by the dasf-experiencebuilder-plugin
|
|
5
5
|
Author-email: "Philipp S. Sommer" <philipp.sommer@hereon.de>, Markus Benninghoff <markus.benninghoff@hereon.de>
|
|
6
6
|
Maintainer-email: "Philipp S. Sommer" <philipp.sommer@hereon.de>, Markus Benninghoff <markus.benninghoff@hereon.de>
|
|
@@ -26,13 +26,17 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
License-File: LICENSES/Apache-2.0.txt
|
|
27
27
|
License-File: LICENSES/CC-BY-4.0.txt
|
|
28
28
|
License-File: LICENSES/CC0-1.0.txt
|
|
29
|
-
Requires-Dist:
|
|
29
|
+
Requires-Dist: dasf>=1.0.1
|
|
30
30
|
Provides-Extra: backend
|
|
31
|
-
Requires-Dist:
|
|
31
|
+
Requires-Dist: dasf[backend]; extra == "backend"
|
|
32
32
|
Requires-Dist: geodatasets; extra == "backend"
|
|
33
33
|
Requires-Dist: geopandas; extra == "backend"
|
|
34
|
+
Provides-Extra: openai
|
|
35
|
+
Requires-Dist: dasf[openai]; extra == "openai"
|
|
36
|
+
Provides-Extra: broker
|
|
37
|
+
Requires-Dist: dasf[broker]; extra == "broker"
|
|
34
38
|
Provides-Extra: testsite
|
|
35
|
-
Requires-Dist: dasf-exb-reference[backend]; extra == "testsite"
|
|
39
|
+
Requires-Dist: dasf-exb-reference[backend,broker,openai]; extra == "testsite"
|
|
36
40
|
Requires-Dist: tox; extra == "testsite"
|
|
37
41
|
Requires-Dist: isort==7.0.0; extra == "testsite"
|
|
38
42
|
Requires-Dist: black==25.9.0; extra == "testsite"
|
|
@@ -40,7 +44,7 @@ Requires-Dist: blackdoc==0.4.6; extra == "testsite"
|
|
|
40
44
|
Requires-Dist: flake8==7.3.0; extra == "testsite"
|
|
41
45
|
Requires-Dist: pre-commit; extra == "testsite"
|
|
42
46
|
Requires-Dist: mypy==1.18.2; extra == "testsite"
|
|
43
|
-
Requires-Dist: dasf-broker-django>=0.0
|
|
47
|
+
Requires-Dist: dasf-broker-django[pytest]>=1.0.0; extra == "testsite"
|
|
44
48
|
Requires-Dist: djangorestframework; extra == "testsite"
|
|
45
49
|
Requires-Dist: pytest-django; extra == "testsite"
|
|
46
50
|
Requires-Dist: pytest-cov; extra == "testsite"
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2026-
|
|
11
|
+
"date": "2026-11-02T10:44:05+0100",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "0.
|
|
14
|
+
"full-revisionid": "28b10ed9b9c275ca3d4b2a024ed41fa1d2ddd6ec",
|
|
15
|
+
"version": "0.2.0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -11,14 +11,16 @@ This backend module provides some demo functions for demonstrating the
|
|
|
11
11
|
|
|
12
12
|
import datetime as dt
|
|
13
13
|
import os
|
|
14
|
-
from typing import TYPE_CHECKING, Dict, List, Literal
|
|
14
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Literal
|
|
15
15
|
|
|
16
16
|
import geodatasets
|
|
17
17
|
import geopandas as gpd
|
|
18
18
|
import pandas as pd
|
|
19
|
-
from
|
|
19
|
+
from dasf import configure, main, registry # noqa: F401
|
|
20
|
+
from dasf.reports import InputReport
|
|
20
21
|
|
|
21
22
|
from .input_models import GeoArea, GeoLocation
|
|
23
|
+
from .input_module import GeoAreaProperty, GeoLocationProperty
|
|
22
24
|
from .return_models import (
|
|
23
25
|
BinaryFile,
|
|
24
26
|
DataSource,
|
|
@@ -37,6 +39,7 @@ __all__ = [
|
|
|
37
39
|
"get_timeseries",
|
|
38
40
|
"show_buttons",
|
|
39
41
|
"get_multiple_responses",
|
|
42
|
+
"InputReporterDemo",
|
|
40
43
|
]
|
|
41
44
|
|
|
42
45
|
|
|
@@ -99,7 +102,7 @@ def get_geodataset(what: T_WHAT) -> GeoJson:
|
|
|
99
102
|
"""
|
|
100
103
|
path = geodatasets.get_path(what)
|
|
101
104
|
df = gpd.read_file(path)
|
|
102
|
-
return GeoJson(data=df.to_crs("
|
|
105
|
+
return GeoJson(data=df.to_crs("epsg:4326").to_geo_dict(), title=what)
|
|
103
106
|
|
|
104
107
|
|
|
105
108
|
def get_point_info(
|
|
@@ -222,12 +225,38 @@ def get_multiple_responses() -> MultipleResponses:
|
|
|
222
225
|
)
|
|
223
226
|
|
|
224
227
|
|
|
228
|
+
class InputReporterDemo:
|
|
229
|
+
"""Demo for asking the user for a location and an area on a map."""
|
|
230
|
+
|
|
231
|
+
def input_report_geo_demo(self) -> Dict[str, Any]:
|
|
232
|
+
"""Ask the user for a location and an area on the map
|
|
233
|
+
|
|
234
|
+
Returns
|
|
235
|
+
-------
|
|
236
|
+
Dict[str, Any]
|
|
237
|
+
The response of the user, with the ``location`` (see
|
|
238
|
+
:class:`~dasf_exb.input_models.GeoLocation`) and the ``area`` (see
|
|
239
|
+
:class:`~dasf_exb.input_models.GeoArea`) that the user selected.
|
|
240
|
+
"""
|
|
241
|
+
properties = {
|
|
242
|
+
"location": GeoLocationProperty(
|
|
243
|
+
description="Select a location on the map"
|
|
244
|
+
),
|
|
245
|
+
"area": GeoAreaProperty(description="Select an area on the map"),
|
|
246
|
+
}
|
|
247
|
+
with InputReport() as reporter:
|
|
248
|
+
answer = reporter.ask_for_properties(
|
|
249
|
+
properties, "Select a location and an area"
|
|
250
|
+
)
|
|
251
|
+
return answer
|
|
252
|
+
|
|
253
|
+
|
|
225
254
|
if __name__ == "__main__":
|
|
226
255
|
if os.getenv("DE_MESSAGING_ENV_FILE"):
|
|
227
|
-
from
|
|
256
|
+
from dasf.config import MessagingConfig, ModuleConfig
|
|
228
257
|
|
|
229
258
|
config = ModuleConfig(
|
|
230
|
-
messaging_config=
|
|
259
|
+
messaging_config=MessagingConfig( # type: ignore[arg-type]
|
|
231
260
|
topic="experience-builder-demo",
|
|
232
261
|
_env_file=os.environ["DE_MESSAGING_ENV_FILE"],
|
|
233
262
|
)
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Helmholtz-Zentrum hereon GmbH
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
# This module is a backend module for the LLM tools of a chat, that extends
|
|
6
|
+
# :mod:`dasf.utility_modules.input_module` with the
|
|
7
|
+
# :class:`GeoLocationProperty` and the :class:`GeoAreaProperty`, to let the
|
|
8
|
+
# user select a point or an area on a map. See there for how to use it. Pass
|
|
9
|
+
# ``"dasf_exb.input_module"`` instead of
|
|
10
|
+
# ``"dasf.utility_modules.input_module"`` to
|
|
11
|
+
# :meth:`dasf.BackendModule.create_model`. The docstrings in this module are
|
|
12
|
+
# the descriptions that the LLM gets for the tools.
|
|
13
|
+
|
|
14
|
+
import inspect
|
|
15
|
+
import textwrap
|
|
16
|
+
import threading
|
|
17
|
+
from typing import (
|
|
18
|
+
Any,
|
|
19
|
+
ClassVar,
|
|
20
|
+
Dict,
|
|
21
|
+
Literal,
|
|
22
|
+
Mapping,
|
|
23
|
+
Optional,
|
|
24
|
+
Type,
|
|
25
|
+
Union,
|
|
26
|
+
cast,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from dasf import configure
|
|
30
|
+
from dasf.reports.input import BaseProperty
|
|
31
|
+
from dasf.utility_modules import input_module as dasf_input_module
|
|
32
|
+
from pydantic import BaseModel, Field
|
|
33
|
+
|
|
34
|
+
from .input_models import GeoArea, GeoLocation
|
|
35
|
+
|
|
36
|
+
__doc__ = dasf_input_module.__doc__
|
|
37
|
+
|
|
38
|
+
__all__ = ["ask_for_input"]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _model_schema(model: Type[BaseModel]) -> Dict[str, Any]:
|
|
42
|
+
"""Get the JSON-Schema of `model` without any ``$ref``.
|
|
43
|
+
|
|
44
|
+
The schema of a property is embedded into the schema of the question, so
|
|
45
|
+
the ``$defs`` of the model would not be found at the root of the document.
|
|
46
|
+
"""
|
|
47
|
+
schema = model.model_json_schema()
|
|
48
|
+
defs = schema.pop("$defs", {})
|
|
49
|
+
|
|
50
|
+
def resolve(node: Any) -> Any:
|
|
51
|
+
if isinstance(node, list):
|
|
52
|
+
return [resolve(item) for item in node]
|
|
53
|
+
if not isinstance(node, dict):
|
|
54
|
+
return node
|
|
55
|
+
resolved = {
|
|
56
|
+
key: resolve(value) for key, value in node.items() if key != "$ref"
|
|
57
|
+
}
|
|
58
|
+
if "$ref" in node:
|
|
59
|
+
resolved = {
|
|
60
|
+
**resolve(defs[node["$ref"].rsplit("/", 1)[-1]]),
|
|
61
|
+
**resolved,
|
|
62
|
+
}
|
|
63
|
+
return resolved
|
|
64
|
+
|
|
65
|
+
return resolve(schema)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class _GeoProperty(BaseProperty):
|
|
69
|
+
"""Base class for the properties that the user selects on a map.
|
|
70
|
+
|
|
71
|
+
The :attr:`~BaseProperty.type` of these properties is not a JSON-Schema
|
|
72
|
+
type, but tells them apart from each other. The user cannot choose from a
|
|
73
|
+
list of values here, but selects the value on a map.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
#: The model that the answer of the user must match
|
|
77
|
+
_geo_model: ClassVar[Type[BaseModel]]
|
|
78
|
+
|
|
79
|
+
choices: Literal[None] = Field( # type: ignore
|
|
80
|
+
None, description="This is disabled for properties on a map."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
accepts_other: Literal[False] = Field( # type: ignore
|
|
84
|
+
False, description="This is disabled for properties on a map."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def to_json_schema(self) -> Dict[str, Any]:
|
|
88
|
+
"""Get the JSON-Schema of this property.
|
|
89
|
+
|
|
90
|
+
This is the schema of the model that the property is selected for. It
|
|
91
|
+
has the ``uiSchema`` of this model, that makes the frontend show a
|
|
92
|
+
map to the user.
|
|
93
|
+
"""
|
|
94
|
+
schema = _model_schema(self._geo_model)
|
|
95
|
+
if self.description:
|
|
96
|
+
schema["description"] = self.description
|
|
97
|
+
if self.default is not None:
|
|
98
|
+
schema["default"] = cast(BaseModel, self.default).model_dump()
|
|
99
|
+
return schema
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class GeoLocationProperty(_GeoProperty):
|
|
103
|
+
"""A location on a map that the user is asked for.
|
|
104
|
+
|
|
105
|
+
The answer is an object with the longitude (``lon``) and the latitude
|
|
106
|
+
(``lat``) of the point that the user selected.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
_geo_model: ClassVar[Type[BaseModel]] = GeoLocation
|
|
110
|
+
|
|
111
|
+
type: Literal["geolocation"] = Field( # type: ignore
|
|
112
|
+
"geolocation", description="The type of the property."
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
default: Optional[GeoLocation] = None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class GeoAreaProperty(_GeoProperty):
|
|
119
|
+
"""An area on a map that the user is asked for.
|
|
120
|
+
|
|
121
|
+
The answer is an object with the ``points`` (each of them with a
|
|
122
|
+
longitude ``lon`` and a latitude ``lat``) of the polygon that the user
|
|
123
|
+
selected.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
_geo_model: ClassVar[Type[BaseModel]] = GeoArea
|
|
127
|
+
|
|
128
|
+
type: Literal["geoarea"] = Field( # type: ignore
|
|
129
|
+
"geoarea", description="The type of the property."
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
default: Optional[GeoArea] = None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
#: The properties that the user can be asked for
|
|
136
|
+
Property = Union[
|
|
137
|
+
dasf_input_module.Property, GeoLocationProperty, GeoAreaProperty
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@configure(cancelable=True)
|
|
142
|
+
def ask_for_input(
|
|
143
|
+
cancel_event: Optional[threading.Event],
|
|
144
|
+
prompt: str,
|
|
145
|
+
properties: Mapping[str, Property],
|
|
146
|
+
) -> Dict[str, Any]:
|
|
147
|
+
# the docstring is set below
|
|
148
|
+
return dasf_input_module.ask_for_input(
|
|
149
|
+
cancel_event, prompt, properties # type: ignore[arg-type]
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# The docstring is what the LLM gets as the description of the tool. It is the
|
|
154
|
+
# one of the dasf function, extended by the description of the geo properties
|
|
155
|
+
# at the end of the description of the `properties`, i.e. before the section
|
|
156
|
+
# with the return value.
|
|
157
|
+
_head, _sep, _tail = inspect.cleandoc(
|
|
158
|
+
dasf_input_module.ask_for_input.__doc__ or ""
|
|
159
|
+
).partition("\nReturns\n")
|
|
160
|
+
ask_for_input.__doc__ = (
|
|
161
|
+
_head.rstrip()
|
|
162
|
+
+ "\n"
|
|
163
|
+
+ textwrap.indent(
|
|
164
|
+
textwrap.fill(
|
|
165
|
+
"In addition to these types, the ``type`` of a property can be "
|
|
166
|
+
'``"geolocation"`` or ``"geoarea"``. The user then selects a '
|
|
167
|
+
"point or an area on a map. The answer is an object with ``lon`` "
|
|
168
|
+
"and ``lat`` for the point, and an object with a list of such "
|
|
169
|
+
"``points`` for the area. These have neither ``choices`` nor "
|
|
170
|
+
"``accepts_other``.",
|
|
171
|
+
width=75,
|
|
172
|
+
),
|
|
173
|
+
" ",
|
|
174
|
+
)
|
|
175
|
+
+ _sep
|
|
176
|
+
+ _tail
|
|
177
|
+
)
|
|
@@ -135,9 +135,9 @@ class DataSource(BaseModel):
|
|
|
135
135
|
|
|
136
136
|
response_type: Literal["datasource"] = "datasource"
|
|
137
137
|
|
|
138
|
-
records: List[
|
|
139
|
-
|
|
140
|
-
)
|
|
138
|
+
records: List[
|
|
139
|
+
Dict[str, Union[None, str, dt.datetime, dt.date, float, int]]
|
|
140
|
+
] = Field(description="The individual record information.")
|
|
141
141
|
|
|
142
142
|
name: str = Field(
|
|
143
143
|
description="Name of the datasource in the frontend application",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dasf-exb-reference
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Reference implementation and Demonstration of a DASF Backend Module visualized by the dasf-experiencebuilder-plugin
|
|
5
5
|
Author-email: "Philipp S. Sommer" <philipp.sommer@hereon.de>, Markus Benninghoff <markus.benninghoff@hereon.de>
|
|
6
6
|
Maintainer-email: "Philipp S. Sommer" <philipp.sommer@hereon.de>, Markus Benninghoff <markus.benninghoff@hereon.de>
|
|
@@ -26,13 +26,17 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
License-File: LICENSES/Apache-2.0.txt
|
|
27
27
|
License-File: LICENSES/CC-BY-4.0.txt
|
|
28
28
|
License-File: LICENSES/CC0-1.0.txt
|
|
29
|
-
Requires-Dist:
|
|
29
|
+
Requires-Dist: dasf>=1.0.1
|
|
30
30
|
Provides-Extra: backend
|
|
31
|
-
Requires-Dist:
|
|
31
|
+
Requires-Dist: dasf[backend]; extra == "backend"
|
|
32
32
|
Requires-Dist: geodatasets; extra == "backend"
|
|
33
33
|
Requires-Dist: geopandas; extra == "backend"
|
|
34
|
+
Provides-Extra: openai
|
|
35
|
+
Requires-Dist: dasf[openai]; extra == "openai"
|
|
36
|
+
Provides-Extra: broker
|
|
37
|
+
Requires-Dist: dasf[broker]; extra == "broker"
|
|
34
38
|
Provides-Extra: testsite
|
|
35
|
-
Requires-Dist: dasf-exb-reference[backend]; extra == "testsite"
|
|
39
|
+
Requires-Dist: dasf-exb-reference[backend,broker,openai]; extra == "testsite"
|
|
36
40
|
Requires-Dist: tox; extra == "testsite"
|
|
37
41
|
Requires-Dist: isort==7.0.0; extra == "testsite"
|
|
38
42
|
Requires-Dist: black==25.9.0; extra == "testsite"
|
|
@@ -40,7 +44,7 @@ Requires-Dist: blackdoc==0.4.6; extra == "testsite"
|
|
|
40
44
|
Requires-Dist: flake8==7.3.0; extra == "testsite"
|
|
41
45
|
Requires-Dist: pre-commit; extra == "testsite"
|
|
42
46
|
Requires-Dist: mypy==1.18.2; extra == "testsite"
|
|
43
|
-
Requires-Dist: dasf-broker-django>=0.0
|
|
47
|
+
Requires-Dist: dasf-broker-django[pytest]>=1.0.0; extra == "testsite"
|
|
44
48
|
Requires-Dist: djangorestframework; extra == "testsite"
|
|
45
49
|
Requires-Dist: pytest-django; extra == "testsite"
|
|
46
50
|
Requires-Dist: pytest-cov; extra == "testsite"
|
{dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb_reference.egg-info/SOURCES.txt
RENAMED
|
@@ -10,6 +10,7 @@ dasf_exb/_version.py
|
|
|
10
10
|
dasf_exb/backend.py
|
|
11
11
|
dasf_exb/download_geodatasets.py
|
|
12
12
|
dasf_exb/input_models.py
|
|
13
|
+
dasf_exb/input_module.py
|
|
13
14
|
dasf_exb/py.typed
|
|
14
15
|
dasf_exb/return_models.py
|
|
15
16
|
dasf_exb_reference.egg-info/PKG-INFO
|
|
@@ -19,4 +20,6 @@ dasf_exb_reference.egg-info/not-zip-safe
|
|
|
19
20
|
dasf_exb_reference.egg-info/requires.txt
|
|
20
21
|
dasf_exb_reference.egg-info/top_level.txt
|
|
21
22
|
tests/test_backend.py
|
|
22
|
-
tests/
|
|
23
|
+
tests/test_connected_module.py
|
|
24
|
+
tests/test_imports.py
|
|
25
|
+
tests/test_input_module.py
|
{dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb_reference.egg-info/requires.txt
RENAMED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
dasf>=1.0.1
|
|
2
2
|
|
|
3
3
|
[backend]
|
|
4
|
-
|
|
4
|
+
dasf[backend]
|
|
5
5
|
geodatasets
|
|
6
6
|
geopandas
|
|
7
7
|
|
|
8
|
+
[broker]
|
|
9
|
+
dasf[broker]
|
|
10
|
+
|
|
8
11
|
[dev]
|
|
9
12
|
dasf-exb-reference[testsite]
|
|
10
13
|
dasf-exb-reference[docs]
|
|
@@ -18,8 +21,11 @@ hereon-netcdf-sphinxext
|
|
|
18
21
|
sphinx-design
|
|
19
22
|
myst_parser
|
|
20
23
|
|
|
24
|
+
[openai]
|
|
25
|
+
dasf[openai]
|
|
26
|
+
|
|
21
27
|
[testsite]
|
|
22
|
-
dasf-exb-reference[backend]
|
|
28
|
+
dasf-exb-reference[backend,broker,openai]
|
|
23
29
|
tox
|
|
24
30
|
isort==7.0.0
|
|
25
31
|
black==25.9.0
|
|
@@ -27,7 +33,7 @@ blackdoc==0.4.6
|
|
|
27
33
|
flake8==7.3.0
|
|
28
34
|
pre-commit
|
|
29
35
|
mypy==1.18.2
|
|
30
|
-
dasf-broker-django>=0.0
|
|
36
|
+
dasf-broker-django[pytest]>=1.0.0
|
|
31
37
|
djangorestframework
|
|
32
38
|
pytest-django
|
|
33
39
|
pytest-cov
|
|
@@ -47,7 +47,7 @@ classifiers = [
|
|
|
47
47
|
|
|
48
48
|
requires-python = '>= 3.9'
|
|
49
49
|
dependencies = [
|
|
50
|
-
"
|
|
50
|
+
"dasf>=1.0.1",
|
|
51
51
|
# add your dependencies here
|
|
52
52
|
]
|
|
53
53
|
|
|
@@ -60,12 +60,21 @@ Tracker = "https://codebase.helmholtz.cloud/dasf/demos/dasf-exb-reference/issues
|
|
|
60
60
|
|
|
61
61
|
[project.optional-dependencies]
|
|
62
62
|
backend = [
|
|
63
|
-
"
|
|
63
|
+
"dasf[backend]",
|
|
64
64
|
"geodatasets",
|
|
65
65
|
"geopandas",
|
|
66
66
|
]
|
|
67
|
+
|
|
68
|
+
openai = [
|
|
69
|
+
"dasf[openai]",
|
|
70
|
+
]
|
|
71
|
+
# everything that the development message broker (the runserver command of the
|
|
72
|
+
# backend module) needs
|
|
73
|
+
broker = [
|
|
74
|
+
"dasf[broker]",
|
|
75
|
+
]
|
|
67
76
|
testsite = [
|
|
68
|
-
"dasf-exb-reference[backend]",
|
|
77
|
+
"dasf-exb-reference[backend, broker, openai]",
|
|
69
78
|
"tox",
|
|
70
79
|
"isort==7.0.0",
|
|
71
80
|
"black==25.9.0",
|
|
@@ -73,7 +82,7 @@ testsite = [
|
|
|
73
82
|
"flake8==7.3.0",
|
|
74
83
|
"pre-commit",
|
|
75
84
|
"mypy==1.18.2",
|
|
76
|
-
"dasf-broker-django>=0.0
|
|
85
|
+
"dasf-broker-django[pytest]>=1.0.0",
|
|
77
86
|
"djangorestframework",
|
|
78
87
|
"pytest-django",
|
|
79
88
|
"pytest-cov",
|
|
@@ -119,7 +128,7 @@ exclude = [
|
|
|
119
128
|
|
|
120
129
|
[tool.pytest.ini_options]
|
|
121
130
|
addopts = '-v'
|
|
122
|
-
DJANGO_SETTINGS_MODULE = "
|
|
131
|
+
DJANGO_SETTINGS_MODULE = "dasf.dev_broker.settings"
|
|
123
132
|
|
|
124
133
|
[tool.versioneer]
|
|
125
134
|
VCS = 'git'
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Helmholtz-Zentrum hereon GmbH
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""Test module for :mod:`dasf_exb.backend`."""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_show_version():
|
|
11
|
+
from dasf_exb import __version__ as ref_version
|
|
12
|
+
from dasf_exb import backend
|
|
13
|
+
|
|
14
|
+
version_info = backend.version_info()
|
|
15
|
+
assert "dasf-exb-reference" in version_info
|
|
16
|
+
assert version_info["dasf-exb-reference"] == ref_version
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_get_geodataset():
|
|
20
|
+
from dasf_exb import backend
|
|
21
|
+
|
|
22
|
+
geojson = backend.get_geodataset("naturalearth.land")
|
|
23
|
+
assert geojson is not None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_get_point_info():
|
|
27
|
+
from dasf_exb import backend
|
|
28
|
+
from dasf_exb.input_models import GeoLocation
|
|
29
|
+
|
|
30
|
+
text = backend.get_point_info(
|
|
31
|
+
GeoLocation(lat=40, lon=40), [GeoLocation(lat=30, lon=30)]
|
|
32
|
+
)
|
|
33
|
+
assert "lon: 40" in text
|
|
34
|
+
assert "lat: 40" in text
|
|
35
|
+
assert "lon: 30" in text
|
|
36
|
+
assert "lat: 30" in text
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_get_timeseries():
|
|
40
|
+
import pandas as pd
|
|
41
|
+
|
|
42
|
+
from dasf_exb import backend
|
|
43
|
+
|
|
44
|
+
timeseries = backend.get_timeseries()
|
|
45
|
+
|
|
46
|
+
df = pd.DataFrame.from_dict(timeseries.records)
|
|
47
|
+
assert sorted(df.columns) == ["date", "value", "value2"]
|
|
48
|
+
assert len(df) == 10
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_get_multiple_responses():
|
|
52
|
+
import pandas as pd
|
|
53
|
+
|
|
54
|
+
from dasf_exb import backend
|
|
55
|
+
|
|
56
|
+
responses = backend.get_multiple_responses()
|
|
57
|
+
|
|
58
|
+
df = pd.DataFrame.from_dict(responses.data[0].records)
|
|
59
|
+
assert sorted(df.columns) == ["date", "value", "value2"]
|
|
60
|
+
assert len(df) == 10
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_create_backend_module():
|
|
64
|
+
"""Test that dasf accepts the module.
|
|
65
|
+
|
|
66
|
+
dasf creates the model for the requests when the backend module is
|
|
67
|
+
created. It rejects, for instance, functions with a report as parameter.
|
|
68
|
+
"""
|
|
69
|
+
from dasf import BackendModule
|
|
70
|
+
|
|
71
|
+
Model = BackendModule.create_model(
|
|
72
|
+
"dasf_exb.backend", messaging_config=dict(topic="test")
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
schema = json.dumps(Model.model_json_schema())
|
|
76
|
+
assert "get_point_info" in schema
|
|
77
|
+
assert "InputReporterDemo" in schema
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_process_point_request():
|
|
81
|
+
"""Test a request for a function with the models of this package."""
|
|
82
|
+
from dasf import BackendModule
|
|
83
|
+
|
|
84
|
+
Model = BackendModule.create_model(
|
|
85
|
+
"dasf_exb.backend", messaging_config=dict(topic="test")
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
result = Model.process_request(
|
|
89
|
+
{
|
|
90
|
+
"func_name": "get_point_info",
|
|
91
|
+
"point": {"lon": 40, "lat": 41},
|
|
92
|
+
"points": [{"lon": 30, "lat": 31}],
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
assert "lon: 40" in result.root
|
|
97
|
+
assert "lat: 41" in result.root
|
|
98
|
+
assert "lon: 30" in result.root
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_input_report_geo_demo(asked_questions):
|
|
102
|
+
"""Test that the demo asks for a location and an area on the map."""
|
|
103
|
+
from dasf import BackendModule
|
|
104
|
+
|
|
105
|
+
from dasf_exb.input_module import GeoAreaProperty, GeoLocationProperty
|
|
106
|
+
|
|
107
|
+
Model = BackendModule.create_model(
|
|
108
|
+
"dasf_exb.backend", messaging_config=dict(topic="test")
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
result = Model.process_request(
|
|
112
|
+
{
|
|
113
|
+
"class_name": "InputReporterDemo",
|
|
114
|
+
"function": {"func_name": "input_report_geo_demo"},
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
assert result.root == {"location": "location", "area": "area"}
|
|
119
|
+
assert len(asked_questions) == 1
|
|
120
|
+
properties, prompt = asked_questions[0]
|
|
121
|
+
assert prompt == "Select a location and an area"
|
|
122
|
+
assert isinstance(properties["location"], GeoLocationProperty)
|
|
123
|
+
assert isinstance(properties["area"], GeoAreaProperty)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Helmholtz-Zentrum hereon GmbH
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""Tests for the backend module that is connected to a message broker."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
from typing import TYPE_CHECKING, Callable, Dict, Union
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
import subprocess as spr
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_request_via_broker(
|
|
17
|
+
connected_module: spr.Popen,
|
|
18
|
+
random_topic: str,
|
|
19
|
+
test_dasf_request: Callable[[str, str, Union[Dict, str]], str],
|
|
20
|
+
):
|
|
21
|
+
"""Send a request to the module via a message broker.
|
|
22
|
+
|
|
23
|
+
The module runs in a process of its own, that is connected to a live
|
|
24
|
+
development broker (see the ``connected_module`` fixture).
|
|
25
|
+
"""
|
|
26
|
+
assert connected_module.poll() is None
|
|
27
|
+
|
|
28
|
+
output = test_dasf_request(
|
|
29
|
+
random_topic,
|
|
30
|
+
"dasf_exb.backend",
|
|
31
|
+
{
|
|
32
|
+
"func_name": "get_point_info",
|
|
33
|
+
"point": {"lon": 40, "lat": 41},
|
|
34
|
+
"points": [{"lon": 30, "lat": 31}],
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
text = json.loads(output)
|
|
39
|
+
assert "lon: 40" in text
|
|
40
|
+
assert "lat: 41" in text
|
|
41
|
+
assert "lon: 30" in text
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 Helmholtz-Zentrum hereon GmbH
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""Test module for :mod:`dasf_exb.input_module`."""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import threading
|
|
9
|
+
from typing import Any, Dict, Type
|
|
10
|
+
|
|
11
|
+
import docstring_parser
|
|
12
|
+
import jsonschema
|
|
13
|
+
import pytest
|
|
14
|
+
from dasf import BackendModule
|
|
15
|
+
from dasf.reports.input import (
|
|
16
|
+
BaseProperty,
|
|
17
|
+
BooleanProperty,
|
|
18
|
+
FloatProperty,
|
|
19
|
+
IntegerProperty,
|
|
20
|
+
StringProperty,
|
|
21
|
+
properties_to_schema,
|
|
22
|
+
)
|
|
23
|
+
from pydantic import TypeAdapter, ValidationError
|
|
24
|
+
|
|
25
|
+
from dasf_exb import input_module
|
|
26
|
+
from dasf_exb.input_models import GeoLocation
|
|
27
|
+
from dasf_exb.input_module import (
|
|
28
|
+
GeoAreaProperty,
|
|
29
|
+
GeoLocationProperty,
|
|
30
|
+
Property,
|
|
31
|
+
ask_for_input,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
TOPIC = "user_input"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def module() -> Type[BackendModule]:
|
|
39
|
+
return BackendModule.create_model(
|
|
40
|
+
"dasf_exb.input_module", messaging_config=dict(topic=TOPIC)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_single_function():
|
|
45
|
+
assert input_module.__all__ == ["ask_for_input"]
|
|
46
|
+
assert callable(ask_for_input)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestGeoProperties:
|
|
50
|
+
def test_location_schema(self):
|
|
51
|
+
schema = GeoLocationProperty(description="Pick").to_json_schema()
|
|
52
|
+
|
|
53
|
+
# the schema is embedded into the one of the question, so there must
|
|
54
|
+
# not be a reference to the definitions
|
|
55
|
+
assert "$ref" not in json.dumps(schema)
|
|
56
|
+
assert "$defs" not in schema
|
|
57
|
+
assert schema["description"] == "Pick"
|
|
58
|
+
assert schema["uiSchema"]["ui:field"] == "GeoLocation"
|
|
59
|
+
assert sorted(schema["properties"]) == ["lat", "lon"]
|
|
60
|
+
assert sorted(schema["required"]) == ["lat", "lon"]
|
|
61
|
+
|
|
62
|
+
def test_location_default(self):
|
|
63
|
+
prop = GeoLocationProperty(default=GeoLocation(lon=10, lat=20))
|
|
64
|
+
assert prop.to_json_schema()["default"] == {"lon": 10.0, "lat": 20.0}
|
|
65
|
+
|
|
66
|
+
def test_area_schema(self):
|
|
67
|
+
schema = GeoAreaProperty(description="Pick an area").to_json_schema()
|
|
68
|
+
|
|
69
|
+
assert "$ref" not in json.dumps(schema)
|
|
70
|
+
assert schema["description"] == "Pick an area"
|
|
71
|
+
assert schema["uiSchema"]["ui:field"] == "GeoArea"
|
|
72
|
+
# the points are resolved, too
|
|
73
|
+
points = schema["properties"]["points"]["items"]
|
|
74
|
+
assert sorted(points["properties"]) == ["lat", "lon"]
|
|
75
|
+
|
|
76
|
+
@pytest.mark.parametrize(
|
|
77
|
+
"kws", [{"choices": ["a"]}, {"accepts_other": True}]
|
|
78
|
+
)
|
|
79
|
+
@pytest.mark.parametrize("cls", [GeoLocationProperty, GeoAreaProperty])
|
|
80
|
+
def test_no_choices(self, cls: Type[GeoLocationProperty], kws):
|
|
81
|
+
"""The user selects on a map, there is nothing to choose from."""
|
|
82
|
+
with pytest.raises(ValidationError):
|
|
83
|
+
cls(**kws)
|
|
84
|
+
|
|
85
|
+
def test_answers_are_validated(self):
|
|
86
|
+
"""The answers are validated by dasf against the schema we created."""
|
|
87
|
+
properties = {
|
|
88
|
+
"name": StringProperty(description="Your name"),
|
|
89
|
+
"location": GeoLocationProperty(description="Pick"),
|
|
90
|
+
"area": GeoAreaProperty(description="Pick an area"),
|
|
91
|
+
}
|
|
92
|
+
schema = properties_to_schema(properties)
|
|
93
|
+
point = {"lon": 10, "lat": 20}
|
|
94
|
+
|
|
95
|
+
jsonschema.validate(
|
|
96
|
+
{"name": "Peter", "location": point, "area": {"points": [point]}},
|
|
97
|
+
schema,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
with pytest.raises(jsonschema.ValidationError):
|
|
101
|
+
jsonschema.validate(
|
|
102
|
+
{
|
|
103
|
+
"name": "Peter",
|
|
104
|
+
"location": {"lon": 10, "lat": 91}, # not a latitude
|
|
105
|
+
"area": {"points": [point]},
|
|
106
|
+
},
|
|
107
|
+
schema,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class TestProperty:
|
|
112
|
+
@pytest.mark.parametrize(
|
|
113
|
+
"type_, cls",
|
|
114
|
+
[
|
|
115
|
+
("integer", IntegerProperty),
|
|
116
|
+
("number", FloatProperty),
|
|
117
|
+
("string", StringProperty),
|
|
118
|
+
("boolean", BooleanProperty),
|
|
119
|
+
("geolocation", GeoLocationProperty),
|
|
120
|
+
("geoarea", GeoAreaProperty),
|
|
121
|
+
],
|
|
122
|
+
)
|
|
123
|
+
def test_union(self, type_: str, cls: type):
|
|
124
|
+
"""The ``type`` of a property tells which one it is."""
|
|
125
|
+
prop: BaseProperty = TypeAdapter(Property).validate_python(
|
|
126
|
+
{"type": type_, "description": "something"}
|
|
127
|
+
)
|
|
128
|
+
assert type(prop) is cls
|
|
129
|
+
|
|
130
|
+
def test_unknown_type(self):
|
|
131
|
+
with pytest.raises(ValidationError):
|
|
132
|
+
TypeAdapter(Property).validate_python({"type": "other"})
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class TestAskForInput:
|
|
136
|
+
def test_docstring(self):
|
|
137
|
+
"""The LLM gets the description of the geo properties."""
|
|
138
|
+
doc = docstring_parser.parse(ask_for_input.__doc__ or "")
|
|
139
|
+
|
|
140
|
+
params = {param.arg_name: param for param in doc.params}
|
|
141
|
+
assert sorted(params) == ["prompt", "properties"]
|
|
142
|
+
description = params["properties"].description or ""
|
|
143
|
+
# the description of dasf is still there ...
|
|
144
|
+
assert "accepts_other" in description
|
|
145
|
+
# ... and we added ours
|
|
146
|
+
assert '"geolocation"' in description
|
|
147
|
+
assert '"geoarea"' in description
|
|
148
|
+
# and it ends up in the description of the parameter, not of the
|
|
149
|
+
# return value
|
|
150
|
+
assert doc.returns is not None
|
|
151
|
+
assert "geo" not in (doc.returns.description or "")
|
|
152
|
+
|
|
153
|
+
def test_schema(self, module: Type[BackendModule]):
|
|
154
|
+
"""The LLM can choose the geo properties and has no reporter."""
|
|
155
|
+
defs: Dict[str, Any] = module.model_json_schema()["$defs"]
|
|
156
|
+
|
|
157
|
+
assert "GeoLocationProperty" in defs
|
|
158
|
+
assert "GeoAreaProperty" in defs
|
|
159
|
+
(func,) = [
|
|
160
|
+
func
|
|
161
|
+
for func in defs.values()
|
|
162
|
+
if func.get("properties", {}).get("func_name", {}).get("const")
|
|
163
|
+
== "ask_for_input"
|
|
164
|
+
]
|
|
165
|
+
assert sorted(func["properties"]) == [
|
|
166
|
+
"func_name",
|
|
167
|
+
"prompt",
|
|
168
|
+
"properties",
|
|
169
|
+
]
|
|
170
|
+
variants = func["properties"]["properties"]["additionalProperties"][
|
|
171
|
+
"anyOf"
|
|
172
|
+
]
|
|
173
|
+
assert {"$ref": "#/$defs/GeoLocationProperty"} in variants
|
|
174
|
+
assert {"$ref": "#/$defs/GeoAreaProperty"} in variants
|
|
175
|
+
|
|
176
|
+
def test_request(self, module: Type[BackendModule], asked_questions):
|
|
177
|
+
"""Test the request of the LLM with properties on a map."""
|
|
178
|
+
result = module.process_request(
|
|
179
|
+
{
|
|
180
|
+
"func_name": "ask_for_input",
|
|
181
|
+
"prompt": "Where are you?",
|
|
182
|
+
"properties": {
|
|
183
|
+
"name": {"type": "string", "description": "Your name"},
|
|
184
|
+
"location": {
|
|
185
|
+
"type": "geolocation",
|
|
186
|
+
"description": "Your location",
|
|
187
|
+
},
|
|
188
|
+
"area": {"type": "geoarea", "description": "Your area"},
|
|
189
|
+
},
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
assert result.root == {
|
|
194
|
+
"name": "name",
|
|
195
|
+
"location": "location",
|
|
196
|
+
"area": "area",
|
|
197
|
+
}
|
|
198
|
+
assert len(asked_questions) == 1
|
|
199
|
+
properties, prompt = asked_questions[0]
|
|
200
|
+
assert prompt == "Where are you?"
|
|
201
|
+
assert isinstance(properties["name"], StringProperty)
|
|
202
|
+
assert isinstance(properties["location"], GeoLocationProperty)
|
|
203
|
+
assert properties["location"].description == "Your location"
|
|
204
|
+
assert isinstance(properties["area"], GeoAreaProperty)
|
|
205
|
+
|
|
206
|
+
def test_cancel(self, monkeypatch: pytest.MonkeyPatch, asked_questions):
|
|
207
|
+
"""The event to cancel the question gets to the question."""
|
|
208
|
+
from dasf.reports import InputReport
|
|
209
|
+
|
|
210
|
+
stop_events = []
|
|
211
|
+
ask_for_properties = InputReport.ask_for_properties
|
|
212
|
+
|
|
213
|
+
def spy(self, properties, prompt=None, stop_event=None, *args):
|
|
214
|
+
stop_events.append(stop_event)
|
|
215
|
+
return ask_for_properties(self, properties, prompt, stop_event)
|
|
216
|
+
|
|
217
|
+
monkeypatch.setattr(InputReport, "ask_for_properties", spy)
|
|
218
|
+
cancel_event = threading.Event()
|
|
219
|
+
|
|
220
|
+
ask_for_input(
|
|
221
|
+
cancel_event,
|
|
222
|
+
"Where are you?",
|
|
223
|
+
{"location": GeoLocationProperty(description="Your location")},
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
assert stop_events == [cancel_event]
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2025 Helmholtz-Zentrum hereon GmbH
|
|
2
|
-
#
|
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
|
|
5
|
-
"""Test module for :mod:`dasf_exb.backend`."""
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def test_show_version():
|
|
9
|
-
from dasf_exb import __version__ as ref_version
|
|
10
|
-
from dasf_exb import backend
|
|
11
|
-
|
|
12
|
-
version_info = backend.version_info()
|
|
13
|
-
assert "dasf-exb-reference" in version_info
|
|
14
|
-
assert version_info["dasf-exb-reference"] == ref_version
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def test_get_geodataset():
|
|
18
|
-
from dasf_exb import backend
|
|
19
|
-
|
|
20
|
-
geojson = backend.get_geodataset("naturalearth.land")
|
|
21
|
-
assert geojson is not None
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def test_get_point_info():
|
|
25
|
-
from dasf_exb import backend
|
|
26
|
-
from dasf_exb.input_models import GeoLocation
|
|
27
|
-
|
|
28
|
-
text = backend.get_point_info(
|
|
29
|
-
GeoLocation(lat=40, lon=40), [GeoLocation(lat=30, lon=30)]
|
|
30
|
-
)
|
|
31
|
-
assert "lon: 40" in text
|
|
32
|
-
assert "lat: 40" in text
|
|
33
|
-
assert "lon: 30" in text
|
|
34
|
-
assert "lat: 30" in text
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def test_get_timeseries():
|
|
38
|
-
import pandas as pd
|
|
39
|
-
|
|
40
|
-
from dasf_exb import backend
|
|
41
|
-
|
|
42
|
-
timeseries = backend.get_timeseries()
|
|
43
|
-
|
|
44
|
-
df = pd.DataFrame.from_dict(timeseries.records)
|
|
45
|
-
assert sorted(df.columns) == ["date", "value", "value2"]
|
|
46
|
-
assert len(df) == 10
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def test_get_multiple_responses():
|
|
50
|
-
import pandas as pd
|
|
51
|
-
|
|
52
|
-
from dasf_exb import backend
|
|
53
|
-
|
|
54
|
-
responses = backend.get_multiple_responses()
|
|
55
|
-
|
|
56
|
-
df = pd.DataFrame.from_dict(responses.data[0].records)
|
|
57
|
-
assert sorted(df.columns) == ["date", "value", "value2"]
|
|
58
|
-
assert len(df) == 10
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb_reference.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{dasf_exb_reference-0.1.0 → dasf_exb_reference-0.2.0}/dasf_exb_reference.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|