drb 2.7.2__tar.gz → 2.8.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.
- {drb-2.7.2/drb.egg-info → drb-2.8.0}/PKG-INFO +1 -3
- {drb-2.7.2 → drb-2.8.0}/drb/core/_version.py +3 -3
- {drb-2.7.2 → drb-2.8.0}/drb/core/impls.py +11 -8
- {drb-2.7.2 → drb-2.8.0}/drb/nodes/abstract_node.py +22 -15
- {drb-2.7.2 → drb-2.8.0}/drb/nodes/logical_node.py +1 -0
- {drb-2.7.2 → drb-2.8.0}/drb/nodes/url_node.py +10 -7
- {drb-2.7.2 → drb-2.8.0}/drb/topics/dao/manager_dao.py +14 -8
- {drb-2.7.2 → drb-2.8.0}/drb/topics/dao/rdf_dao.py +66 -10
- {drb-2.7.2 → drb-2.8.0}/drb/topics/resolver.py +32 -4
- drb-2.8.0/drb/utils/plugins.py +9 -0
- {drb-2.7.2 → drb-2.8.0/drb.egg-info}/PKG-INFO +1 -3
- {drb-2.7.2 → drb-2.8.0}/drb.egg-info/SOURCES.txt +3 -3
- {drb-2.7.2 → drb-2.8.0}/drb.egg-info/requires.txt +0 -2
- {drb-2.7.2 → drb-2.8.0}/requirements.txt +0 -2
- {drb-2.7.2 → drb-2.8.0}/setup.cfg +0 -3
- {drb-2.7.2 → drb-2.8.0}/tests/test_abstract_node.py +29 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_implementation_manager.py +10 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_logical_node.py +9 -1
- {drb-2.7.2 → drb-2.8.0}/tests/test_manager_dao.py +74 -0
- drb-2.8.0/tests/test_named_child_no_sibling_resolution.py +60 -0
- drb-2.8.0/tests/test_rdf_dao_robustness.py +105 -0
- drb-2.8.0/tests/test_resolver_factoryless_refinement.py +75 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_url_node.py +45 -0
- drb-2.7.2/drb/topics/dao/topic_schema.yml +0 -95
- drb-2.7.2/drb/topics/dao/xml_dao.py +0 -184
- drb-2.7.2/drb/utils/plugins.py +0 -21
- drb-2.7.2/tests/test_xml_dao.py +0 -71
- {drb-2.7.2 → drb-2.8.0}/LICENCE.txt +0 -0
- {drb-2.7.2 → drb-2.8.0}/MANIFEST.in +0 -0
- {drb-2.7.2 → drb-2.8.0}/README.md +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/addons/addon.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/core/__init__.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/core/auth.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/core/context.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/core/cortex_validator.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/core/events.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/core/factory.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/core/item.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/core/node.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/core/path.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/core/predicate.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/core/signature.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/drivers/file/__init__.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/drivers/file/cortex.ttl +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/drivers/file/file.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/drivers/xml/__init__.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/drivers/xml/cortex.ttl +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/drivers/xml/factory.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/drivers/xml/node.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/exceptions/core.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/exceptions/file.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/keyring/bitwarden/__init__.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/keyring/bitwarden/bitwarden.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/nodes/mutable_node.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/signatures/core.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/signatures/hierarchy.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/signatures/xquery.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/topics/dao/__init__.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/topics/dao/topic_dao.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/topics/file/__init__.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/topics/file/cortex.ttl +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/topics/topic.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/topics/xml/__init__.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/topics/xml/cortex.ttl +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/utils/__init__.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/utils/drb_python_script.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/utils/io.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/utils/url_security.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/utils/utils.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/xquery/XQueryLexer.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/xquery/XQueryParser.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/xquery/XQueryParserListener.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/xquery/XQueryParserVisitor.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/xquery/__init__.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/xquery/drb_xquery.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/xquery/drb_xquery_cmd.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/xquery/drb_xquery_context.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/xquery/drb_xquery_func.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/xquery/drb_xquery_item.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/xquery/drb_xquery_res_to_string.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/xquery/drb_xquery_utils.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/xquery/drb_xquery_variable.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/xquery/drb_xquery_visitor.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb/xquery/exceptions.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb.egg-info/dependency_links.txt +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb.egg-info/entry_points.txt +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb.egg-info/not-zip-safe +0 -0
- {drb-2.7.2 → drb-2.8.0}/drb.egg-info/top_level.txt +0 -0
- {drb-2.7.2 → drb-2.8.0}/pyproject.toml +0 -0
- {drb-2.7.2 → drb-2.8.0}/setup.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_addon.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_auth.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_children_unresolved.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_core_topic_ttl.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_cortex_validator.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_drb_node_list.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_drb_python_script.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_event.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_factory.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_factory_resolver.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_finest_multiparent.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_implementation_coupling.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_keyring_bitwarden.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_manager_get_dao.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_node_context.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_node_context_manager.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_node_descendant.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_path.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_protocol_dispatch.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_rdf_dao.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_rdf_dao_multi_url.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_rdf_dao_scheme.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_rdf_dao_signatures.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_resolver_auth.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_resolver_format_alias.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_resolver_implicit_file.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_resolver_recursion_guard.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_scheme_force_impl.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_scheme_force_url.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_signature_duplicate_warning.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_signature_exact.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_signature_hierarchy.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_signature_inline_flags.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_signatures.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_topic.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_topic_descriptor_discovery.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_topic_merge_crosspackage.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_url_security.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/tests/test_xml_xquery_import_order.py +0 -0
- {drb-2.7.2 → drb-2.8.0}/versioneer.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drb
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.8.0
|
|
4
4
|
Summary: Data Request Broker
|
|
5
5
|
Author: GAEL Systems
|
|
6
6
|
Author-email: drb-python@gael.fr
|
|
@@ -12,8 +12,6 @@ Requires-Python: <3.14,>=3.11
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENCE.txt
|
|
14
14
|
Requires-Dist: importlib-metadata>=4.11.4
|
|
15
|
-
Requires-Dist: jsonschema==4.17.0
|
|
16
|
-
Requires-Dist: PyYaml~=6.0.2
|
|
17
15
|
Requires-Dist: click==8.1.3
|
|
18
16
|
Requires-Dist: keyring<26,>=25.7.0
|
|
19
17
|
Requires-Dist: requests<3,>=2.33.0
|
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2026-07-
|
|
11
|
+
"date": "2026-07-10T13:20:21+0000",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "2.
|
|
14
|
+
"full-revisionid": "dad4a706c920e704103cb0a1c83b0bfbdf97a204",
|
|
15
|
+
"version": "2.8.0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -26,15 +26,15 @@ class ImplementationManager:
|
|
|
26
26
|
|
|
27
27
|
def __find_impl(self, impl: type, identifier: Optional[str]) \
|
|
28
28
|
-> List[Tuple[type, Optional[str]]]:
|
|
29
|
+
# keys are iterated in registration order so that a multi-match
|
|
30
|
+
# lookup stays deterministic (first registered wins in get_impl)
|
|
29
31
|
if identifier is None:
|
|
30
32
|
if (impl, None) in self.__impls.keys():
|
|
31
33
|
return [(impl, None)]
|
|
32
|
-
keys =
|
|
34
|
+
keys = self.__impls.keys()
|
|
33
35
|
else:
|
|
34
|
-
keys =
|
|
35
|
-
|
|
36
|
-
)
|
|
37
|
-
return list(filter(lambda x: issubclass(x[0], impl), keys))
|
|
36
|
+
keys = filter(lambda x: x[1] == identifier, self.__impls.keys())
|
|
37
|
+
return [key for key in keys if issubclass(key[0], impl)]
|
|
38
38
|
|
|
39
39
|
def has_impl(self, impl: type, identifier: str = None) -> bool:
|
|
40
40
|
"""
|
|
@@ -62,14 +62,17 @@ class ImplementationManager:
|
|
|
62
62
|
specify from which add-on retrieve the
|
|
63
63
|
implementation (default: ``None``)
|
|
64
64
|
Return:
|
|
65
|
-
Callable: the expected implementation.
|
|
65
|
+
Callable: the expected implementation. When several registered
|
|
66
|
+
implementations match, the first registered one is returned,
|
|
67
|
+
keeping ``get_impl`` consistent with ``has_impl``.
|
|
66
68
|
Raises:
|
|
67
69
|
KeyError: if the given interface is not found
|
|
68
70
|
"""
|
|
69
71
|
keys = self.__find_impl(impl, identifier)
|
|
70
|
-
if
|
|
72
|
+
if keys:
|
|
71
73
|
return self.__impls[keys[0]]
|
|
72
|
-
raise KeyError
|
|
74
|
+
raise KeyError(f'No implementation ({impl.__name__}, {identifier}) '
|
|
75
|
+
'found')
|
|
73
76
|
|
|
74
77
|
def get_capabilities(self) -> List[Tuple[type, str]]:
|
|
75
78
|
"""
|
|
@@ -112,8 +112,10 @@ class AbstractNode(DrbNode, abc.ABC):
|
|
|
112
112
|
def __setitem__(self, key, value):
|
|
113
113
|
if key is None:
|
|
114
114
|
self._browse_children().append(value)
|
|
115
|
+
return
|
|
115
116
|
if isinstance(key, int):
|
|
116
117
|
self._browse_children()[key] = value
|
|
118
|
+
return
|
|
117
119
|
try:
|
|
118
120
|
if isinstance(key, str):
|
|
119
121
|
self._browse_children()[
|
|
@@ -122,23 +124,24 @@ class AbstractNode(DrbNode, abc.ABC):
|
|
|
122
124
|
key = get_name_namespace_index(key)
|
|
123
125
|
self._browse_children()[self._get_named_child_index(
|
|
124
126
|
*key)] = value
|
|
127
|
+
else:
|
|
128
|
+
raise KeyError(f'Invalid key {key}')
|
|
125
129
|
except (DrbException, IndexError, TypeError) as ex:
|
|
126
130
|
raise KeyError(f'Invalid key {key}') from ex
|
|
127
131
|
|
|
128
132
|
def __delitem__(self, key):
|
|
129
133
|
if isinstance(key, int):
|
|
130
134
|
del self._browse_children()[key]
|
|
135
|
+
return
|
|
131
136
|
try:
|
|
132
137
|
if isinstance(key, str):
|
|
133
138
|
del self._browse_children()[self._get_named_child_index(key)]
|
|
134
|
-
elif isinstance(key, tuple):
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
else:
|
|
141
|
-
raise KeyError(f'Invalid key {key}')
|
|
139
|
+
elif isinstance(key, tuple) and len(key) == 2:
|
|
140
|
+
key = get_name_namespace_index(key)
|
|
141
|
+
del self._browse_children()[self._get_named_child_index(
|
|
142
|
+
*key)]
|
|
143
|
+
else:
|
|
144
|
+
raise KeyError(f'Invalid key {key}')
|
|
142
145
|
except (DrbException, IndexError, TypeError) as ex:
|
|
143
146
|
raise KeyError(f'Invalid key {key}') from ex
|
|
144
147
|
|
|
@@ -162,14 +165,14 @@ class AbstractNode(DrbNode, abc.ABC):
|
|
|
162
165
|
occ = occurrence
|
|
163
166
|
cpt = 0
|
|
164
167
|
if self.namespace_aware or namespace_uri is not None:
|
|
165
|
-
for x in self.
|
|
168
|
+
for x in self.children_unresolved:
|
|
166
169
|
if x.name == name and x.namespace_uri == namespace_uri:
|
|
167
170
|
if occ <= 0:
|
|
168
171
|
return cpt
|
|
169
172
|
occ -= 1
|
|
170
173
|
cpt += 1
|
|
171
174
|
else:
|
|
172
|
-
for x in self.
|
|
175
|
+
for x in self.children_unresolved:
|
|
173
176
|
if x.name == name:
|
|
174
177
|
return cpt
|
|
175
178
|
cpt += 1
|
|
@@ -199,12 +202,16 @@ class AbstractNode(DrbNode, abc.ABC):
|
|
|
199
202
|
IndexError: if item is out of range of found children
|
|
200
203
|
DrbException: if no child following given criteria is found
|
|
201
204
|
"""
|
|
205
|
+
# Match unresolved children and let the caller resolve only the
|
|
206
|
+
# selected node: a targeted named lookup must not resolve unrelated
|
|
207
|
+
# siblings (contract: the driver owns optimized access; the core
|
|
208
|
+
# resolves only when necessary). drb-testbench#21.
|
|
202
209
|
if self.namespace_aware or namespace_uri is not None:
|
|
203
|
-
named_children = [x for x in self.
|
|
210
|
+
named_children = [x for x in self.children_unresolved
|
|
204
211
|
if x.name == name
|
|
205
212
|
and x.namespace_uri == namespace_uri]
|
|
206
213
|
else:
|
|
207
|
-
named_children = [x for x in self.
|
|
214
|
+
named_children = [x for x in self.children_unresolved
|
|
208
215
|
if x.name == name]
|
|
209
216
|
if len(named_children) <= 0:
|
|
210
217
|
raise DrbException(f'No child found having name: {name} and'
|
|
@@ -225,13 +232,13 @@ class AbstractNode(DrbNode, abc.ABC):
|
|
|
225
232
|
|
|
226
233
|
result = []
|
|
227
234
|
if name is not None and namespace is not None:
|
|
228
|
-
result = [x for x in self.
|
|
235
|
+
result = [x for x in self.children_unresolved
|
|
229
236
|
if x.name == name and x.namespace_uri == namespace]
|
|
230
237
|
if name is None:
|
|
231
|
-
result = [x for x in self.
|
|
238
|
+
result = [x for x in self.children_unresolved
|
|
232
239
|
if x.namespace_uri == namespace]
|
|
233
240
|
if namespace is None:
|
|
234
|
-
result = [x for x in self.
|
|
241
|
+
result = [x for x in self.children_unresolved if x.name == name]
|
|
235
242
|
|
|
236
243
|
return len(result) > 0
|
|
237
244
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
import io
|
|
3
|
-
import keyring
|
|
4
3
|
import requests
|
|
5
|
-
from requests.auth import AuthBase
|
|
4
|
+
from requests.auth import AuthBase
|
|
6
5
|
from typing import Any, Optional, List, Dict, Tuple, Union
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
7
|
|
|
8
|
+
from drb.core.auth import credential_to_authbase, resolve_credential
|
|
8
9
|
from drb.core.node import DrbNode
|
|
9
10
|
from drb.core.path import ParsedPath, Path, parse_path
|
|
10
11
|
from drb.exceptions.core import DrbException
|
|
@@ -107,11 +108,13 @@ class UrlNode(DrbNode):
|
|
|
107
108
|
raise DrbException('UrlNode has no child')
|
|
108
109
|
|
|
109
110
|
def _find_credential(self, url: str):
|
|
111
|
+
# explicit auth wins; otherwise resolve through the drb.core.auth
|
|
112
|
+
# provider chain (injected > keyring > env > anonymous),
|
|
113
|
+
# host-scoped like every transport driver
|
|
110
114
|
if self._auth is not None:
|
|
111
115
|
return self._auth
|
|
112
116
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return None
|
|
117
|
+
parsed = urlparse(url)
|
|
118
|
+
credential = resolve_credential(self._context, parsed.scheme,
|
|
119
|
+
parsed.hostname or '')
|
|
120
|
+
return credential_to_authbase(credential)
|
|
@@ -8,7 +8,6 @@ import os
|
|
|
8
8
|
import logging
|
|
9
9
|
import importlib
|
|
10
10
|
import drb.utils.plugins
|
|
11
|
-
from jsonschema.exceptions import ValidationError
|
|
12
11
|
from drb.utils.url_security import validate_url
|
|
13
12
|
|
|
14
13
|
from .topic_dao import DrbTopicDao
|
|
@@ -86,8 +85,10 @@ def _load_all_drb_topic_dao() -> Dict[uuid.UUID, DrbTopicDao]:
|
|
|
86
85
|
return result
|
|
87
86
|
|
|
88
87
|
# One merged graph across all descriptors (cross-package resolution).
|
|
88
|
+
# skip_invalid: one broken descriptor is logged and skipped instead of
|
|
89
|
+
# aborting topic loading for every installed package.
|
|
89
90
|
try:
|
|
90
|
-
dao = RDFDao(paths)
|
|
91
|
+
dao = RDFDao(paths, skip_invalid=True)
|
|
91
92
|
for topic in dao.read_all():
|
|
92
93
|
if not _implementation_available(topic):
|
|
93
94
|
logger.warning(
|
|
@@ -97,8 +98,8 @@ def _load_all_drb_topic_dao() -> Dict[uuid.UUID, DrbTopicDao]:
|
|
|
97
98
|
topic.label, topic.uri, topic.factory)
|
|
98
99
|
continue
|
|
99
100
|
result[topic.id] = dao
|
|
100
|
-
except
|
|
101
|
-
logger.
|
|
101
|
+
except Exception as ex:
|
|
102
|
+
logger.error(f'Topic loading failed: {ex}')
|
|
102
103
|
|
|
103
104
|
return result
|
|
104
105
|
|
|
@@ -245,9 +246,14 @@ class ManagerDao:
|
|
|
245
246
|
return False
|
|
246
247
|
|
|
247
248
|
for parent_id in actual.subClassOf:
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
249
|
+
try:
|
|
250
|
+
parent = self.get_drb_topic(parent_id)
|
|
251
|
+
except DrbException:
|
|
252
|
+
# dangling parent reference: cannot match, try the next one
|
|
253
|
+
continue
|
|
254
|
+
if self.is_subclass(parent, expected):
|
|
255
|
+
return True
|
|
256
|
+
return False
|
|
251
257
|
|
|
252
258
|
def get_parents(self, topic: DrbTopic) -> List[DrbTopic]:
|
|
253
259
|
|
|
@@ -259,7 +265,7 @@ class ManagerDao:
|
|
|
259
265
|
for parent in topic.subClassOf:
|
|
260
266
|
|
|
261
267
|
if parent in self.__topic_dao.keys():
|
|
262
|
-
dao = self.__topic_dao[
|
|
268
|
+
dao = self.__topic_dao[parent]
|
|
263
269
|
parents.append(dao.read(parent))
|
|
264
270
|
|
|
265
271
|
return parents
|
|
@@ -22,6 +22,8 @@ _DRB_ITEM_URI_LEGACY = "http://www.gael.fr/drb/item"
|
|
|
22
22
|
# Default (connect, read) timeout in seconds for outbound HTTP requests.
|
|
23
23
|
_HTTP_TIMEOUT = (10, 60)
|
|
24
24
|
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
25
27
|
|
|
26
28
|
def _rdf_bool(literal) -> bool:
|
|
27
29
|
"""Coerce an RDF literal to bool.
|
|
@@ -52,21 +54,33 @@ class RDFDao(DrbTopicDao):
|
|
|
52
54
|
self,
|
|
53
55
|
path: Union[str, List[str]],
|
|
54
56
|
auth: Optional[tuple] = None,
|
|
57
|
+
skip_invalid: bool = False,
|
|
55
58
|
):
|
|
56
59
|
"""
|
|
57
60
|
Load RDF from a single path/URL or from a list of URLs (e.g. Graph
|
|
58
61
|
Store), merging into one graph. Optional auth is (user, password)
|
|
59
|
-
for HTTP Basic when loading from http/https URLs.
|
|
62
|
+
for HTTP Basic when loading from http/https URLs. With
|
|
63
|
+
``skip_invalid`` an unreadable/unparsable source is logged and
|
|
64
|
+
skipped instead of aborting the whole load (used by the merged
|
|
65
|
+
entry-point loading, so one broken descriptor cannot take every
|
|
66
|
+
other package down).
|
|
60
67
|
"""
|
|
61
68
|
self.graph = rdflib.Graph()
|
|
62
|
-
if isinstance(path, list)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
sources = path if isinstance(path, list) else [path]
|
|
70
|
+
for source in sources:
|
|
71
|
+
src = source.strip() if isinstance(source, str) else source
|
|
72
|
+
if not src:
|
|
73
|
+
continue
|
|
74
|
+
if skip_invalid:
|
|
75
|
+
try:
|
|
76
|
+
self._parse_one(src, auth)
|
|
77
|
+
except Exception as ex:
|
|
78
|
+
logger.error(
|
|
79
|
+
'Skipping invalid topic descriptor %s: %s', src, ex)
|
|
80
|
+
else:
|
|
81
|
+
self._parse_one(src, auth)
|
|
69
82
|
self.__result = self.__query_rdf_file()
|
|
83
|
+
self._warn_undiscoverable_topics()
|
|
70
84
|
|
|
71
85
|
def _parse_one(self, path_or_url: str, auth: Optional[tuple]) -> None:
|
|
72
86
|
"""Parse one path or URL into self.graph."""
|
|
@@ -138,6 +152,43 @@ class RDFDao(DrbTopicDao):
|
|
|
138
152
|
|
|
139
153
|
return result
|
|
140
154
|
|
|
155
|
+
def _warn_undiscoverable_topics(self) -> None:
|
|
156
|
+
"""Warn about topic-like subjects discovery will silently drop.
|
|
157
|
+
|
|
158
|
+
Discovery only selects classes reaching ``drb:item`` through
|
|
159
|
+
``rdfs:subClassOf+``; a topic whose parent package is not loaded
|
|
160
|
+
(or whose parent URI is mistyped) simply vanishes. Surface those
|
|
161
|
+
here instead of leaving the user to wonder why a topic does not
|
|
162
|
+
resolve.
|
|
163
|
+
"""
|
|
164
|
+
result = self.graph.query(f"""
|
|
165
|
+
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
166
|
+
PREFIX drb: <http://www.gael.fr/drb#>
|
|
167
|
+
SELECT DISTINCT ?Class
|
|
168
|
+
(GROUP_CONCAT(DISTINCT STR(?parent); separator=", ")
|
|
169
|
+
AS ?parents)
|
|
170
|
+
WHERE {{
|
|
171
|
+
{{ ?Class drb:id ?mark . }}
|
|
172
|
+
UNION {{ ?Class drb:signature ?mark . }}
|
|
173
|
+
UNION {{ ?Class drb:implementationIdentifier ?mark . }}
|
|
174
|
+
OPTIONAL {{ ?Class rdfs:subClassOf ?parent . }}
|
|
175
|
+
FILTER NOT EXISTS {{
|
|
176
|
+
?Class rdfs:subClassOf+ <{DRB_ITEM_URI}> . }}
|
|
177
|
+
FILTER NOT EXISTS {{
|
|
178
|
+
?Class rdfs:subClassOf+ <{_DRB_ITEM_URI_LEGACY}> . }}
|
|
179
|
+
FILTER (?Class != <{DRB_ITEM_URI}>
|
|
180
|
+
&& ?Class != <{_DRB_ITEM_URI_LEGACY}>)
|
|
181
|
+
}}
|
|
182
|
+
GROUP BY ?Class
|
|
183
|
+
""")
|
|
184
|
+
for row in result:
|
|
185
|
+
parents = str(row.parents) if row.parents else None
|
|
186
|
+
logger.warning(
|
|
187
|
+
"Topic '%s' is not discoverable: its rdfs:subClassOf chain "
|
|
188
|
+
"does not reach drb:item (direct parents: %s). Check the "
|
|
189
|
+
"parent URI and that the parent package is installed.",
|
|
190
|
+
row.Class.toPython(), parents or 'none')
|
|
191
|
+
|
|
141
192
|
def get_topic_signature(self, topic: DrbTopic):
|
|
142
193
|
|
|
143
194
|
result = self.graph.query(f'''
|
|
@@ -281,7 +332,7 @@ class RDFDao(DrbTopicDao):
|
|
|
281
332
|
|
|
282
333
|
continue
|
|
283
334
|
|
|
284
|
-
raise DrbException
|
|
335
|
+
raise DrbException(f'No topic found for identifier: {identifier}')
|
|
285
336
|
|
|
286
337
|
def find(self, search: str) -> List[DrbTopic]:
|
|
287
338
|
"""
|
|
@@ -312,7 +363,12 @@ class RDFDao(DrbTopicDao):
|
|
|
312
363
|
topic = self.__generate_topic_from_rdf(r)
|
|
313
364
|
topics.append(topic)
|
|
314
365
|
|
|
315
|
-
except TypeError:
|
|
366
|
+
except (TypeError, ValueError) as ex:
|
|
367
|
+
# e.g. a non-UUID drb:id or an unknown drb:category: skip
|
|
368
|
+
# the row but say so, a silent drop is undebuggable
|
|
369
|
+
logger.warning(
|
|
370
|
+
"Skipping malformed topic row '%s': %s",
|
|
371
|
+
r.Class.toPython(), ex)
|
|
316
372
|
continue
|
|
317
373
|
|
|
318
374
|
return topics
|
|
@@ -94,6 +94,31 @@ def _topic_by_factory(name: str):
|
|
|
94
94
|
return None
|
|
95
95
|
|
|
96
96
|
|
|
97
|
+
def _driver_topic(topic: DrbTopic) -> Optional[DrbTopic]:
|
|
98
|
+
"""Nearest topic in the ``subClassOf`` chain (``topic`` first) that
|
|
99
|
+
declares a driver ``factory``.
|
|
100
|
+
|
|
101
|
+
A semantic refinement (e.g. a mission-specific container/formatting topic
|
|
102
|
+
such as ``Sentinel-2 …: Zip``) may declare no ``factory`` of its own; the
|
|
103
|
+
node must still be built by the driver of the nearest ancestor that has
|
|
104
|
+
one -- the refinement only relabels it, it does not rebuild it. Returns
|
|
105
|
+
``None`` when no topic in the chain declares a factory.
|
|
106
|
+
"""
|
|
107
|
+
by_id = {t.id: t for t in ManagerDao().get_all_drb_topics()}
|
|
108
|
+
seen = set()
|
|
109
|
+
queue = [topic]
|
|
110
|
+
while queue:
|
|
111
|
+
current = queue.pop(0)
|
|
112
|
+
if current is None or current.id in seen:
|
|
113
|
+
continue
|
|
114
|
+
seen.add(current.id)
|
|
115
|
+
if current.factory is not None:
|
|
116
|
+
return current
|
|
117
|
+
for parent_id in (current.subClassOf or []):
|
|
118
|
+
queue.append(by_id.get(parent_id))
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
|
|
97
122
|
def _topic_by_scheme_any(scheme: str):
|
|
98
123
|
"""The topic (any category) declaring ``scheme`` in its ``drb:scheme``.
|
|
99
124
|
|
|
@@ -477,13 +502,16 @@ class _DrbFactoryResolver(DrbFactory):
|
|
|
477
502
|
node = _create_via_factory(protocol, node)
|
|
478
503
|
|
|
479
504
|
container, node = self.__retrieve_container(node)
|
|
480
|
-
if container is not None
|
|
481
|
-
|
|
505
|
+
if container is not None:
|
|
506
|
+
driver = _driver_topic(container)
|
|
507
|
+
if driver is not None:
|
|
508
|
+
node = _create_via_factory(driver, node)
|
|
482
509
|
|
|
483
510
|
formatting = self.__retrieve_formatting(node)
|
|
484
511
|
if formatting is not None:
|
|
485
|
-
|
|
486
|
-
|
|
512
|
+
driver = _driver_topic(formatting)
|
|
513
|
+
if driver is not None:
|
|
514
|
+
node = _create_via_factory(driver, node)
|
|
487
515
|
t, n = self.__finest_drb_topic(node, formatting)
|
|
488
516
|
elif container is not None:
|
|
489
517
|
t, n = container, node
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: drb
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.8.0
|
|
4
4
|
Summary: Data Request Broker
|
|
5
5
|
Author: GAEL Systems
|
|
6
6
|
Author-email: drb-python@gael.fr
|
|
@@ -12,8 +12,6 @@ Requires-Python: <3.14,>=3.11
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENCE.txt
|
|
14
14
|
Requires-Dist: importlib-metadata>=4.11.4
|
|
15
|
-
Requires-Dist: jsonschema==4.17.0
|
|
16
|
-
Requires-Dist: PyYaml~=6.0.2
|
|
17
15
|
Requires-Dist: click==8.1.3
|
|
18
16
|
Requires-Dist: keyring<26,>=25.7.0
|
|
19
17
|
Requires-Dist: requests<3,>=2.33.0
|
|
@@ -51,8 +51,6 @@ drb/topics/dao/__init__.py
|
|
|
51
51
|
drb/topics/dao/manager_dao.py
|
|
52
52
|
drb/topics/dao/rdf_dao.py
|
|
53
53
|
drb/topics/dao/topic_dao.py
|
|
54
|
-
drb/topics/dao/topic_schema.yml
|
|
55
|
-
drb/topics/dao/xml_dao.py
|
|
56
54
|
drb/topics/file/__init__.py
|
|
57
55
|
drb/topics/file/cortex.ttl
|
|
58
56
|
drb/topics/xml/__init__.py
|
|
@@ -96,6 +94,7 @@ tests/test_keyring_bitwarden.py
|
|
|
96
94
|
tests/test_logical_node.py
|
|
97
95
|
tests/test_manager_dao.py
|
|
98
96
|
tests/test_manager_get_dao.py
|
|
97
|
+
tests/test_named_child_no_sibling_resolution.py
|
|
99
98
|
tests/test_node_context.py
|
|
100
99
|
tests/test_node_context_manager.py
|
|
101
100
|
tests/test_node_descendant.py
|
|
@@ -103,9 +102,11 @@ tests/test_path.py
|
|
|
103
102
|
tests/test_protocol_dispatch.py
|
|
104
103
|
tests/test_rdf_dao.py
|
|
105
104
|
tests/test_rdf_dao_multi_url.py
|
|
105
|
+
tests/test_rdf_dao_robustness.py
|
|
106
106
|
tests/test_rdf_dao_scheme.py
|
|
107
107
|
tests/test_rdf_dao_signatures.py
|
|
108
108
|
tests/test_resolver_auth.py
|
|
109
|
+
tests/test_resolver_factoryless_refinement.py
|
|
109
110
|
tests/test_resolver_format_alias.py
|
|
110
111
|
tests/test_resolver_implicit_file.py
|
|
111
112
|
tests/test_resolver_recursion_guard.py
|
|
@@ -121,5 +122,4 @@ tests/test_topic_descriptor_discovery.py
|
|
|
121
122
|
tests/test_topic_merge_crosspackage.py
|
|
122
123
|
tests/test_url_node.py
|
|
123
124
|
tests/test_url_security.py
|
|
124
|
-
tests/test_xml_dao.py
|
|
125
125
|
tests/test_xml_xquery_import_order.py
|
|
@@ -42,11 +42,8 @@ drb.signature =
|
|
|
42
42
|
parentIsSubClassTest = drb.signatures.hierarchy:ParentIsSubClassTestSignature
|
|
43
43
|
parentClassMatch = drb.signatures.hierarchy:ParentClassMatchSignature
|
|
44
44
|
parentIsSubClassMatch = drb.signatures.hierarchy:ParentIsSubClassMatchSignature
|
|
45
|
-
drb.auth =
|
|
46
45
|
|
|
47
46
|
[options.package_data]
|
|
48
|
-
drb.topics.dao =
|
|
49
|
-
topic_schema.yml
|
|
50
47
|
drb.topics.xml =
|
|
51
48
|
cortex.ttl
|
|
52
49
|
drb.topics.file =
|
|
@@ -178,6 +178,35 @@ class TestDrbNode(unittest.TestCase):
|
|
|
178
178
|
del node['Test_0']
|
|
179
179
|
self.assertEqual(len(node), 0)
|
|
180
180
|
|
|
181
|
+
def test_delitem_name_namespace_tuple(self):
|
|
182
|
+
# drb-testbench#23: deleting by (name, namespace) raised KeyError
|
|
183
|
+
# AFTER the child was actually removed
|
|
184
|
+
node = DrbLogicalNode('container')
|
|
185
|
+
node.append_child(DrbLogicalNode('child', namespace_uri='ns'))
|
|
186
|
+
node.append_child(DrbLogicalNode('other'))
|
|
187
|
+
|
|
188
|
+
del node[('child', 'ns')]
|
|
189
|
+
|
|
190
|
+
self.assertEqual(1, len(node))
|
|
191
|
+
self.assertEqual('other', node[0].name)
|
|
192
|
+
|
|
193
|
+
def test_delitem_invalid_key_type(self):
|
|
194
|
+
# drb-testbench#23: an unsupported key type was silently ignored
|
|
195
|
+
node = DrbLogicalNode('container')
|
|
196
|
+
node.append_child(DrbLogicalNode('child'))
|
|
197
|
+
|
|
198
|
+
with self.assertRaises(KeyError):
|
|
199
|
+
del node[1.5]
|
|
200
|
+
self.assertEqual(1, len(node))
|
|
201
|
+
|
|
202
|
+
def test_setitem_invalid_key_type(self):
|
|
203
|
+
# drb-testbench#23: an unsupported key type was silently ignored
|
|
204
|
+
node = DrbLogicalNode('container')
|
|
205
|
+
|
|
206
|
+
with self.assertRaises(KeyError):
|
|
207
|
+
node[1.5] = DrbLogicalNode('x')
|
|
208
|
+
self.assertEqual(0, len(node))
|
|
209
|
+
|
|
181
210
|
def test_namespace_11(self):
|
|
182
211
|
# Case 1.1: node with ns / user access with ns / aware = True
|
|
183
212
|
root = DrbLogicalNode("root")
|
|
@@ -66,6 +66,16 @@ class TestImplManager(unittest.TestCase):
|
|
|
66
66
|
with self.assertRaises(KeyError):
|
|
67
67
|
mng.get_impl(dict)
|
|
68
68
|
|
|
69
|
+
def test_get_impl_ambiguous_returns_first_registered(self):
|
|
70
|
+
# drb-testbench#24: has_impl answered True but get_impl raised
|
|
71
|
+
# KeyError when several impls matched; first registered now wins
|
|
72
|
+
mng = ImplementationManager()
|
|
73
|
+
mng.add_impl(str, 'a', function_1)
|
|
74
|
+
mng.add_impl(str, 'b', function_2)
|
|
75
|
+
|
|
76
|
+
self.assertTrue(mng.has_impl(str))
|
|
77
|
+
self.assertEqual(function_1, mng.get_impl(str))
|
|
78
|
+
|
|
69
79
|
def test_get_capabilities(self):
|
|
70
80
|
mng = ImplementationManager()
|
|
71
81
|
self.assertEqual([], mng.get_capabilities())
|
|
@@ -6,7 +6,7 @@ import operator
|
|
|
6
6
|
from functools import partial
|
|
7
7
|
from drb.core import DrbNode, Predicate, ParsedPath
|
|
8
8
|
from drb.core.path import parse_path
|
|
9
|
-
from drb.nodes.logical_node import DrbLogicalNode
|
|
9
|
+
from drb.nodes.logical_node import DrbLogicalNode, WrappedNode
|
|
10
10
|
from drb.exceptions.core import DrbException
|
|
11
11
|
|
|
12
12
|
|
|
@@ -295,6 +295,14 @@ class TestLogicalNode(unittest.TestCase):
|
|
|
295
295
|
node = DrbLogicalNode(source)
|
|
296
296
|
node.close()
|
|
297
297
|
|
|
298
|
+
def test_wrapped_node_imatmul_returns_node(self):
|
|
299
|
+
# drb-testbench#23: WrappedNode.__imatmul__ returned None, so
|
|
300
|
+
# `wrapped @= ...` rebound the variable to None
|
|
301
|
+
wrapped = WrappedNode(DrbLogicalNode('base'))
|
|
302
|
+
wrapped @= ('attr', 'value')
|
|
303
|
+
self.assertIsNotNone(wrapped)
|
|
304
|
+
self.assertEqual('value', wrapped @ 'attr')
|
|
305
|
+
|
|
298
306
|
def test_impl(self):
|
|
299
307
|
source = "/path/to/data/node"
|
|
300
308
|
node = DrbLogicalNode(source)
|