drb 2.4.2__tar.gz → 2.4.4__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.
Files changed (116) hide show
  1. {drb-2.4.2/drb.egg-info → drb-2.4.4}/PKG-INFO +1 -3
  2. {drb-2.4.2 → drb-2.4.4}/README.md +0 -2
  3. {drb-2.4.2 → drb-2.4.4}/drb/core/_version.py +3 -3
  4. {drb-2.4.2 → drb-2.4.4}/drb/signatures/core.py +42 -5
  5. {drb-2.4.2 → drb-2.4.4}/drb/topics/resolver.py +72 -32
  6. {drb-2.4.2 → drb-2.4.4/drb.egg-info}/PKG-INFO +1 -3
  7. {drb-2.4.2 → drb-2.4.4}/drb.egg-info/SOURCES.txt +4 -0
  8. drb-2.4.4/tests/test_finest_multiparent.py +71 -0
  9. drb-2.4.4/tests/test_resolver_recursion_guard.py +65 -0
  10. drb-2.4.4/tests/test_scheme_force_url.py +81 -0
  11. drb-2.4.4/tests/test_signature_inline_flags.py +18 -0
  12. {drb-2.4.2 → drb-2.4.4}/LICENCE.txt +0 -0
  13. {drb-2.4.2 → drb-2.4.4}/MANIFEST.in +0 -0
  14. {drb-2.4.2 → drb-2.4.4}/drb/addons/addon.py +0 -0
  15. {drb-2.4.2 → drb-2.4.4}/drb/core/__init__.py +0 -0
  16. {drb-2.4.2 → drb-2.4.4}/drb/core/context.py +0 -0
  17. {drb-2.4.2 → drb-2.4.4}/drb/core/cortex_validator.py +0 -0
  18. {drb-2.4.2 → drb-2.4.4}/drb/core/events.py +0 -0
  19. {drb-2.4.2 → drb-2.4.4}/drb/core/factory.py +0 -0
  20. {drb-2.4.2 → drb-2.4.4}/drb/core/impls.py +0 -0
  21. {drb-2.4.2 → drb-2.4.4}/drb/core/item.py +0 -0
  22. {drb-2.4.2 → drb-2.4.4}/drb/core/node.py +0 -0
  23. {drb-2.4.2 → drb-2.4.4}/drb/core/path.py +0 -0
  24. {drb-2.4.2 → drb-2.4.4}/drb/core/predicate.py +0 -0
  25. {drb-2.4.2 → drb-2.4.4}/drb/core/signature.py +0 -0
  26. {drb-2.4.2 → drb-2.4.4}/drb/drivers/file/__init__.py +0 -0
  27. {drb-2.4.2 → drb-2.4.4}/drb/drivers/file/cortex.ttl +0 -0
  28. {drb-2.4.2 → drb-2.4.4}/drb/drivers/file/file.py +0 -0
  29. {drb-2.4.2 → drb-2.4.4}/drb/drivers/xml/__init__.py +0 -0
  30. {drb-2.4.2 → drb-2.4.4}/drb/drivers/xml/cortex.ttl +0 -0
  31. {drb-2.4.2 → drb-2.4.4}/drb/drivers/xml/factory.py +0 -0
  32. {drb-2.4.2 → drb-2.4.4}/drb/drivers/xml/node.py +0 -0
  33. {drb-2.4.2 → drb-2.4.4}/drb/exceptions/core.py +0 -0
  34. {drb-2.4.2 → drb-2.4.4}/drb/exceptions/file.py +0 -0
  35. {drb-2.4.2 → drb-2.4.4}/drb/keyring/bitwarden/__init__.py +0 -0
  36. {drb-2.4.2 → drb-2.4.4}/drb/keyring/bitwarden/bitwarden.py +0 -0
  37. {drb-2.4.2 → drb-2.4.4}/drb/nodes/abstract_node.py +0 -0
  38. {drb-2.4.2 → drb-2.4.4}/drb/nodes/logical_node.py +0 -0
  39. {drb-2.4.2 → drb-2.4.4}/drb/nodes/mutable_node.py +0 -0
  40. {drb-2.4.2 → drb-2.4.4}/drb/nodes/url_node.py +0 -0
  41. {drb-2.4.2 → drb-2.4.4}/drb/signatures/hierarchy.py +0 -0
  42. {drb-2.4.2 → drb-2.4.4}/drb/signatures/xquery.py +0 -0
  43. {drb-2.4.2 → drb-2.4.4}/drb/topics/dao/__init__.py +0 -0
  44. {drb-2.4.2 → drb-2.4.4}/drb/topics/dao/manager_dao.py +0 -0
  45. {drb-2.4.2 → drb-2.4.4}/drb/topics/dao/rdf_dao.py +0 -0
  46. {drb-2.4.2 → drb-2.4.4}/drb/topics/dao/topic_dao.py +0 -0
  47. {drb-2.4.2 → drb-2.4.4}/drb/topics/dao/topic_schema.yml +0 -0
  48. {drb-2.4.2 → drb-2.4.4}/drb/topics/dao/xml_dao.py +0 -0
  49. {drb-2.4.2 → drb-2.4.4}/drb/topics/file/__init__.py +0 -0
  50. {drb-2.4.2 → drb-2.4.4}/drb/topics/file/cortex.ttl +0 -0
  51. {drb-2.4.2 → drb-2.4.4}/drb/topics/topic.py +0 -0
  52. {drb-2.4.2 → drb-2.4.4}/drb/topics/xml/__init__.py +0 -0
  53. {drb-2.4.2 → drb-2.4.4}/drb/topics/xml/cortex.ttl +0 -0
  54. {drb-2.4.2 → drb-2.4.4}/drb/utils/__init__.py +0 -0
  55. {drb-2.4.2 → drb-2.4.4}/drb/utils/drb_python_script.py +0 -0
  56. {drb-2.4.2 → drb-2.4.4}/drb/utils/plugins.py +0 -0
  57. {drb-2.4.2 → drb-2.4.4}/drb/utils/url_security.py +0 -0
  58. {drb-2.4.2 → drb-2.4.4}/drb/utils/utils.py +0 -0
  59. {drb-2.4.2 → drb-2.4.4}/drb/xquery/XQueryLexer.py +0 -0
  60. {drb-2.4.2 → drb-2.4.4}/drb/xquery/XQueryParser.py +0 -0
  61. {drb-2.4.2 → drb-2.4.4}/drb/xquery/XQueryParserListener.py +0 -0
  62. {drb-2.4.2 → drb-2.4.4}/drb/xquery/XQueryParserVisitor.py +0 -0
  63. {drb-2.4.2 → drb-2.4.4}/drb/xquery/__init__.py +0 -0
  64. {drb-2.4.2 → drb-2.4.4}/drb/xquery/drb_xquery.py +0 -0
  65. {drb-2.4.2 → drb-2.4.4}/drb/xquery/drb_xquery_cmd.py +0 -0
  66. {drb-2.4.2 → drb-2.4.4}/drb/xquery/drb_xquery_context.py +0 -0
  67. {drb-2.4.2 → drb-2.4.4}/drb/xquery/drb_xquery_func.py +0 -0
  68. {drb-2.4.2 → drb-2.4.4}/drb/xquery/drb_xquery_item.py +0 -0
  69. {drb-2.4.2 → drb-2.4.4}/drb/xquery/drb_xquery_res_to_string.py +0 -0
  70. {drb-2.4.2 → drb-2.4.4}/drb/xquery/drb_xquery_utils.py +0 -0
  71. {drb-2.4.2 → drb-2.4.4}/drb/xquery/drb_xquery_variable.py +0 -0
  72. {drb-2.4.2 → drb-2.4.4}/drb/xquery/drb_xquery_visitor.py +0 -0
  73. {drb-2.4.2 → drb-2.4.4}/drb/xquery/exceptions.py +0 -0
  74. {drb-2.4.2 → drb-2.4.4}/drb.egg-info/dependency_links.txt +0 -0
  75. {drb-2.4.2 → drb-2.4.4}/drb.egg-info/entry_points.txt +0 -0
  76. {drb-2.4.2 → drb-2.4.4}/drb.egg-info/not-zip-safe +0 -0
  77. {drb-2.4.2 → drb-2.4.4}/drb.egg-info/requires.txt +0 -0
  78. {drb-2.4.2 → drb-2.4.4}/drb.egg-info/top_level.txt +0 -0
  79. {drb-2.4.2 → drb-2.4.4}/pyproject.toml +0 -0
  80. {drb-2.4.2 → drb-2.4.4}/requirements.txt +0 -0
  81. {drb-2.4.2 → drb-2.4.4}/setup.cfg +0 -0
  82. {drb-2.4.2 → drb-2.4.4}/setup.py +0 -0
  83. {drb-2.4.2 → drb-2.4.4}/tests/test_abstract_node.py +0 -0
  84. {drb-2.4.2 → drb-2.4.4}/tests/test_addon.py +0 -0
  85. {drb-2.4.2 → drb-2.4.4}/tests/test_children_unresolved.py +0 -0
  86. {drb-2.4.2 → drb-2.4.4}/tests/test_core_topic_ttl.py +0 -0
  87. {drb-2.4.2 → drb-2.4.4}/tests/test_cortex_validator.py +0 -0
  88. {drb-2.4.2 → drb-2.4.4}/tests/test_drb_node_list.py +0 -0
  89. {drb-2.4.2 → drb-2.4.4}/tests/test_drb_python_script.py +0 -0
  90. {drb-2.4.2 → drb-2.4.4}/tests/test_event.py +0 -0
  91. {drb-2.4.2 → drb-2.4.4}/tests/test_factory.py +0 -0
  92. {drb-2.4.2 → drb-2.4.4}/tests/test_factory_resolver.py +0 -0
  93. {drb-2.4.2 → drb-2.4.4}/tests/test_implementation_coupling.py +0 -0
  94. {drb-2.4.2 → drb-2.4.4}/tests/test_implementation_manager.py +0 -0
  95. {drb-2.4.2 → drb-2.4.4}/tests/test_keyring_bitwarden.py +0 -0
  96. {drb-2.4.2 → drb-2.4.4}/tests/test_logical_node.py +0 -0
  97. {drb-2.4.2 → drb-2.4.4}/tests/test_manager_dao.py +0 -0
  98. {drb-2.4.2 → drb-2.4.4}/tests/test_manager_get_dao.py +0 -0
  99. {drb-2.4.2 → drb-2.4.4}/tests/test_node_context.py +0 -0
  100. {drb-2.4.2 → drb-2.4.4}/tests/test_node_descendant.py +0 -0
  101. {drb-2.4.2 → drb-2.4.4}/tests/test_path.py +0 -0
  102. {drb-2.4.2 → drb-2.4.4}/tests/test_protocol_dispatch.py +0 -0
  103. {drb-2.4.2 → drb-2.4.4}/tests/test_rdf_dao.py +0 -0
  104. {drb-2.4.2 → drb-2.4.4}/tests/test_rdf_dao_multi_url.py +0 -0
  105. {drb-2.4.2 → drb-2.4.4}/tests/test_rdf_dao_scheme.py +0 -0
  106. {drb-2.4.2 → drb-2.4.4}/tests/test_rdf_dao_signatures.py +0 -0
  107. {drb-2.4.2 → drb-2.4.4}/tests/test_scheme_force_impl.py +0 -0
  108. {drb-2.4.2 → drb-2.4.4}/tests/test_signature_exact.py +0 -0
  109. {drb-2.4.2 → drb-2.4.4}/tests/test_signature_hierarchy.py +0 -0
  110. {drb-2.4.2 → drb-2.4.4}/tests/test_signatures.py +0 -0
  111. {drb-2.4.2 → drb-2.4.4}/tests/test_topic.py +0 -0
  112. {drb-2.4.2 → drb-2.4.4}/tests/test_topic_descriptor_discovery.py +0 -0
  113. {drb-2.4.2 → drb-2.4.4}/tests/test_url_node.py +0 -0
  114. {drb-2.4.2 → drb-2.4.4}/tests/test_url_security.py +0 -0
  115. {drb-2.4.2 → drb-2.4.4}/tests/test_xml_dao.py +0 -0
  116. {drb-2.4.2 → drb-2.4.4}/versioneer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drb
3
- Version: 2.4.2
3
+ Version: 2.4.4
4
4
  Summary: Data Request Broker
5
5
  Author: GAEL Systems
6
6
  Author-email: drb-python@gael.fr
@@ -65,8 +65,6 @@ You can also join the moderators team. Please contact me.
65
65
 
66
66
  Contribution process is based on gitlab best practice and processes the following schema:
67
67
 
68
- ![](docs/modification_process.png)
69
-
70
68
  ### How to contribute
71
69
  Contributor shall work in its own fork of the project and provides its contributions via the merge request process.
72
70
  The merge request shall be approved and merged by a maintainers. So the step-by-step procedure to contribute is:
@@ -36,8 +36,6 @@ You can also join the moderators team. Please contact me.
36
36
 
37
37
  Contribution process is based on gitlab best practice and processes the following schema:
38
38
 
39
- ![](docs/modification_process.png)
40
-
41
39
  ### How to contribute
42
40
  Contributor shall work in its own fork of the project and provides its contributions via the merge request process.
43
41
  The merge request shall be approved and merged by a maintainers. So the step-by-step procedure to contribute is:
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2026-06-26T17:44:46+0000",
11
+ "date": "2026-06-30T02:14:50+0000",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "d6abeea9ba11991cf33d5cc12fc33405c6032658",
15
- "version": "2.4.2"
14
+ "full-revisionid": "6de9fd1d1e819008ecdd4ddbd56a71bd2e99c4ab",
15
+ "version": "2.4.4"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -1,10 +1,44 @@
1
+ import re
2
+ import logging
3
+
1
4
  from drb.core import DrbNode
2
5
  from drb.core.predicate import Predicate
3
6
  from drb.utils.drb_python_script import exec_with_return
4
7
  from drb.exceptions.core import DrbException
5
8
  from drb.core.signature import Signature, SignatureAggregator
6
- import re
7
- import logging
9
+
10
+
11
+ _INLINE_FLAG_GROUP = re.compile(r'\(\?([aiLmsux]+)\)')
12
+ _FLAG_CHARS = {'a': re.A, 'i': re.I, 'L': re.L, 'm': re.M,
13
+ 's': re.S, 'u': re.U, 'x': re.X}
14
+
15
+
16
+ def _compile_regex(pattern: str):
17
+ """Compile a regex, tolerating global inline flags (e.g. ``(?i)``)
18
+ placed anywhere in the pattern. Python 3.11+ rejects a global inline
19
+ flag that is not at position 0, but drb-java / legacy descriptors
20
+ author them after an anchor (``^(?i)...``). A bare ``(?i)`` group is
21
+ already global, so its flags are moved to the compile-time ``flags``
22
+ argument and the group is stripped from the pattern. Scoped groups
23
+ like ``(?i:...)`` (with a colon) are untouched.
24
+
25
+ Args:
26
+ pattern: The raw regex pattern string, possibly containing
27
+ global inline flag groups.
28
+
29
+ Returns:
30
+ A compiled :class:`re.Pattern` with any extracted flags applied.
31
+ """
32
+ chars = set()
33
+ for m in _INLINE_FLAG_GROUP.finditer(pattern):
34
+ chars.update(m.group(1))
35
+ if not chars:
36
+ return re.compile(pattern)
37
+ cleaned = _INLINE_FLAG_GROUP.sub('', pattern)
38
+ flags = 0
39
+ for ch in chars:
40
+ flags |= _FLAG_CHARS[ch]
41
+ return re.compile(cleaned, flags)
8
42
 
9
43
 
10
44
  class NameSignature(Signature):
@@ -16,9 +50,10 @@ class NameSignature(Signature):
16
50
 
17
51
  def __init__(self, regex: str):
18
52
  self.__regex = regex
53
+ self.__pattern = _compile_regex(regex)
19
54
 
20
55
  def matches(self, node: DrbNode) -> bool:
21
- return re.match(self.__regex, node.name) is not None
56
+ return self.__pattern.match(node.name) is not None
22
57
 
23
58
  def to_dict(self) -> dict:
24
59
  return {self.get_name(): self.__regex}
@@ -37,9 +72,10 @@ class NamespaceSignature(Signature):
37
72
 
38
73
  def __init__(self, regex: str):
39
74
  self.__regex = regex
75
+ self.__pattern = _compile_regex(regex)
40
76
 
41
77
  def matches(self, node: DrbNode) -> bool:
42
- return re.match(self.__regex, node.namespace_uri) is not None
78
+ return self.__pattern.match(node.namespace_uri) is not None
43
79
 
44
80
  def to_dict(self) -> dict:
45
81
  return {self.get_name(): self.__regex}
@@ -92,9 +128,10 @@ class PathSignature(Signature):
92
128
 
93
129
  def __init__(self, regex: str):
94
130
  self.__regex = regex
131
+ self.__pattern = _compile_regex(regex)
95
132
 
96
133
  def matches(self, node: DrbNode) -> bool:
97
- return re.match(self.__regex, node.path.name) is not None
134
+ return self.__pattern.match(node.path.name) is not None
98
135
 
99
136
  def to_dict(self) -> dict:
100
137
  return {self.get_name(): self.__regex}
@@ -4,6 +4,7 @@ import logging
4
4
  import pathlib
5
5
  import sys
6
6
  import re
7
+ import threading
7
8
 
8
9
  from urllib.parse import urlparse
9
10
  from typing import List, Optional, Tuple, Union
@@ -19,6 +20,8 @@ from drb.addons.addon import AddonManager
19
20
 
20
21
  logger = logging.getLogger('DrbResolver')
21
22
 
23
+ _resolution_guard = threading.local()
24
+
22
25
 
23
26
  def _is_remote_url(parsed_path):
24
27
  """
@@ -227,7 +230,7 @@ class _DrbFactoryResolver(DrbFactory):
227
230
  DrbTopic: the finest topic matching the given node
228
231
  """
229
232
  topics = [x for x in ManagerDao().get_all_drb_topics()
230
- if x.subClassOf is not None if x.subClassOf[0] == finest.id]
233
+ if x.subClassOf is not None if finest.id in x.subClassOf]
231
234
  for topic in topics:
232
235
  node = forced_factory(topic, node)
233
236
  if topic.matches(node):
@@ -283,6 +286,24 @@ class _DrbFactoryResolver(DrbFactory):
283
286
  pp = urlparse(url)
284
287
 
285
288
  if curl is None and path is None:
289
+ # Chained scheme "<base>+<forced>://": resolve the base URL
290
+ # normally -- walking the path through any container (e.g. a zip
291
+ # inside an S3 object) -- then apply each forced driver to the
292
+ # descended leaf. Without this, the forced token is crammed onto
293
+ # the whole-path base resource (e.g. an S3 object key that
294
+ # includes the in-archive path), which cannot be opened.
295
+ if pp.scheme and '+' in pp.scheme:
296
+ base, *tokens = pp.scheme.split('+')
297
+ if base == '' or any(t == '' for t in tokens):
298
+ raise DrbFactoryException(
299
+ f'Invalid scheme "{pp.scheme}": empty segment')
300
+ clean_url = base + url[len(pp.scheme):]
301
+ node = self.__create_from_url(clean_url)
302
+ for token in tokens:
303
+ _, factory = _resolve_token(token)
304
+ node = factory.create(node)
305
+ return node
306
+
286
307
  try:
287
308
  return self.create(UrlNode(url))
288
309
  except (DrbFactoryException, DrbException,
@@ -338,38 +359,57 @@ class _DrbFactoryResolver(DrbFactory):
338
359
  node = UrlNode(source)
339
360
  else:
340
361
  node = source
341
- protocol = None
342
- if node.parent is None:
343
- dispatched = self.__dispatch_scheme(node)
344
- if dispatched is not None:
345
- protocol, node = dispatched
362
+ active = getattr(_resolution_guard, 'active', None)
363
+ if active is None:
364
+ active = set()
365
+ _resolution_guard.active = active
366
+ try:
367
+ key = node.path.path
368
+ except Exception: # noqa: BLE001
369
+ key = None
370
+ if key is not None and key in active:
371
+ raise DrbFactoryException(
372
+ f'Cyclic resolution detected for node path: {key}')
373
+ if key is not None:
374
+ active.add(key)
375
+ try:
376
+ protocol = None
377
+ if node.parent is None:
378
+ dispatched = self.__dispatch_scheme(node)
379
+ if dispatched is not None:
380
+ protocol, node = dispatched
381
+ else:
382
+ protocol = self.__retrieve_protocol(node)
383
+ if protocol is None:
384
+ raise DrbFactoryException(
385
+ f'Cannot resolve: {source}')
386
+ node = _create_via_factory(protocol, node)
387
+
388
+ container, node = self.__retrieve_container(node)
389
+ if container is not None and container.factory is not None:
390
+ node = _create_via_factory(container, node)
391
+
392
+ formatting = self.__retrieve_formatting(node)
393
+ if formatting is not None:
394
+ if formatting.factory is not None:
395
+ node = _create_via_factory(formatting, node)
396
+ t, n = self.__finest_drb_topic(node, formatting)
397
+ elif container is not None:
398
+ t, n = container, node
399
+ elif protocol is not None:
400
+ t, n = protocol, node
346
401
  else:
347
- protocol = self.__retrieve_protocol(node)
348
- if protocol is None:
349
- raise DrbFactoryException(f'Cannot resolve: {source}')
350
- node = _create_via_factory(protocol, node)
351
-
352
- container, node = self.__retrieve_container(node)
353
- if container is not None and container.factory is not None:
354
- node = _create_via_factory(container, node)
355
-
356
- formatting = self.__retrieve_formatting(node)
357
- if formatting is not None:
358
- if formatting.factory is not None:
359
- node = _create_via_factory(formatting, node)
360
- t, n = self.__finest_drb_topic(node, formatting)
361
- elif container is not None:
362
- t, n = container, node
363
- elif protocol is not None:
364
- t, n = protocol, node
365
- else:
366
- raise DrbFactoryException(f'Cannot resolve: {source}')
367
-
368
- addons = filter(lambda x: x.can_apply(t),
369
- AddonManager().get_all_addons())
370
- for addon in addons:
371
- node.add_impl(addon.return_type(), addon.apply, addon.identifier())
372
- return t, n
402
+ raise DrbFactoryException(f'Cannot resolve: {source}')
403
+
404
+ addons = filter(lambda x: x.can_apply(t),
405
+ AddonManager().get_all_addons())
406
+ for addon in addons:
407
+ node.add_impl(
408
+ addon.return_type(), addon.apply, addon.identifier())
409
+ return t, n
410
+ finally:
411
+ if key is not None:
412
+ active.discard(key)
373
413
 
374
414
  def resolve(self, source: Union[str, DrbNode]) \
375
415
  -> Tuple[DrbTopic, Optional[DrbNode]]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drb
3
- Version: 2.4.2
3
+ Version: 2.4.4
4
4
  Summary: Data Request Broker
5
5
  Author: GAEL Systems
6
6
  Author-email: drb-python@gael.fr
@@ -65,8 +65,6 @@ You can also join the moderators team. Please contact me.
65
65
 
66
66
  Contribution process is based on gitlab best practice and processes the following schema:
67
67
 
68
- ![](docs/modification_process.png)
69
-
70
68
  ### How to contribute
71
69
  Contributor shall work in its own fork of the project and provides its contributions via the merge request process.
72
70
  The merge request shall be approved and merged by a maintainers. So the step-by-step procedure to contribute is:
@@ -86,6 +86,7 @@ tests/test_drb_python_script.py
86
86
  tests/test_event.py
87
87
  tests/test_factory.py
88
88
  tests/test_factory_resolver.py
89
+ tests/test_finest_multiparent.py
89
90
  tests/test_implementation_coupling.py
90
91
  tests/test_implementation_manager.py
91
92
  tests/test_keyring_bitwarden.py
@@ -100,9 +101,12 @@ tests/test_rdf_dao.py
100
101
  tests/test_rdf_dao_multi_url.py
101
102
  tests/test_rdf_dao_scheme.py
102
103
  tests/test_rdf_dao_signatures.py
104
+ tests/test_resolver_recursion_guard.py
103
105
  tests/test_scheme_force_impl.py
106
+ tests/test_scheme_force_url.py
104
107
  tests/test_signature_exact.py
105
108
  tests/test_signature_hierarchy.py
109
+ tests/test_signature_inline_flags.py
106
110
  tests/test_signatures.py
107
111
  tests/test_topic.py
108
112
  tests/test_topic_descriptor_discovery.py
@@ -0,0 +1,71 @@
1
+ import os
2
+ import tempfile
3
+ import unittest
4
+ import uuid
5
+
6
+ from drb.nodes.logical_node import DrbLogicalNode
7
+ from drb.topics.dao import ManagerDao
8
+ from drb.topics.dao.rdf_dao import RDFDao
9
+ import drb.topics.resolver as resolver
10
+
11
+ _IDS = ("a1a1a1a1-0000-0000-0000-00000000000a",
12
+ "b2b2b2b2-0000-0000-0000-00000000000b",
13
+ "c3c3c3c3-0000-0000-0000-00000000000c")
14
+
15
+ _TTL = """\
16
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
17
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
18
+ @prefix drb: <http://www.gael.fr/drb#> .
19
+ @prefix t: <http://example.org/mp/> .
20
+
21
+ t:base_a a owl:Class ; rdfs:label "mp_base_a" ;
22
+ drb:id "a1a1a1a1-0000-0000-0000-00000000000a" ;
23
+ drb:category "CONTAINER" ;
24
+ rdfs:subClassOf <http://www.gael.fr/drb#item> ;
25
+ drb:signature [ drb:nameMatch "mpnode" ] .
26
+ t:base_b a owl:Class ; rdfs:label "mp_base_b" ;
27
+ drb:id "b2b2b2b2-0000-0000-0000-00000000000b" ;
28
+ drb:category "CONTAINER" ;
29
+ rdfs:subClassOf <http://www.gael.fr/drb#item> ;
30
+ drb:signature [ drb:nameMatch "mpnode" ] .
31
+ t:child a owl:Class ; rdfs:label "mp_child" ;
32
+ drb:id "c3c3c3c3-0000-0000-0000-00000000000c" ;
33
+ drb:category "CONTAINER" ;
34
+ rdfs:subClassOf t:base_a, t:base_b ;
35
+ drb:signature [ drb:nameMatch "mpnode" ] .
36
+ """
37
+
38
+
39
+ class TestFinestMultiParent(unittest.TestCase):
40
+ def setUp(self):
41
+ fd, self._ttl = tempfile.mkstemp(suffix=".ttl")
42
+ with os.fdopen(fd, "w") as f:
43
+ f.write(_TTL)
44
+ ManagerDao().add_dao_instance(RDFDao(self._ttl))
45
+ self.addCleanup(self._clean)
46
+
47
+ def _clean(self):
48
+ for u in _IDS:
49
+ try:
50
+ ManagerDao().delete(uuid.UUID(u))
51
+ except Exception:
52
+ pass
53
+ try:
54
+ os.unlink(self._ttl)
55
+ except OSError:
56
+ pass
57
+
58
+ def test_descends_via_non_first_parent(self):
59
+ topics = ManagerDao().get_all_drb_topics()
60
+ child = [t for t in topics
61
+ if t.uri and t.uri.endswith('/mp/child')][0]
62
+ # child has two parents; pick one that is NOT subClassOf[0]
63
+ self.assertEqual(2, len(child.subClassOf))
64
+ non_first = child.subClassOf[1]
65
+ parent = [t for t in topics if t.id == non_first][0]
66
+ res = resolver._DrbFactoryResolver()
67
+ node = DrbLogicalNode("mpnode")
68
+ topic, _ = res._DrbFactoryResolver__finest_drb_topic(node, parent)
69
+ self.assertTrue(
70
+ str(topic.uri).endswith('/mp/child'),
71
+ f"descent stopped at {topic.uri}, expected .../mp/child")
@@ -0,0 +1,65 @@
1
+ import os
2
+ import tempfile
3
+ import unittest
4
+
5
+ import drb.topics.resolver as resolver
6
+ from drb.nodes.logical_node import DrbLogicalNode
7
+ from drb.topics.dao import ManagerDao
8
+ from drb.topics.dao.rdf_dao import RDFDao
9
+ from drb.topics.resolver import DrbNodeList
10
+
11
+
12
+ # A node whose resolved children list contains itself -> a resolution cycle.
13
+ class _CyclicNode(DrbLogicalNode):
14
+ @property
15
+ def children(self):
16
+ return DrbNodeList([self])
17
+
18
+
19
+ _LOOP_TTL = """\
20
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
21
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
22
+ @prefix drb: <http://www.gael.fr/drb#> .
23
+ @prefix t: <http://example.org/test/> .
24
+
25
+ t:loop a owl:Class ;
26
+ rdfs:label "loop" ;
27
+ drb:id "aaaaaaaa-0000-0000-0000-000000000001" ;
28
+ drb:category "CONTAINER" ;
29
+ rdfs:subClassOf <http://www.gael.fr/drb#item> ;
30
+ drb:signature [ drb:nameMatch "loop" ;
31
+ drb:xqueryTest "*[fn:matches(fn:name(),'loop')]" ] .
32
+ """
33
+
34
+
35
+ class TestResolverRecursionGuard(unittest.TestCase):
36
+ def setUp(self):
37
+ fd, self._ttl = tempfile.mkstemp(suffix=".ttl")
38
+ with os.fdopen(fd, "w") as f:
39
+ f.write(_LOOP_TTL)
40
+ self._dao = ManagerDao().add_dao_instance(RDFDao(self._ttl))
41
+ self.addCleanup(self._cleanup)
42
+
43
+ def _cleanup(self):
44
+ try:
45
+ import uuid
46
+ ManagerDao().delete(
47
+ uuid.UUID("aaaaaaaa-0000-0000-0000-000000000001"))
48
+ except Exception:
49
+ pass
50
+ try:
51
+ os.unlink(self._ttl)
52
+ except OSError:
53
+ pass
54
+
55
+ def test_cyclic_resolution_terminates(self):
56
+ # Without the guard this recurses without bound (RecursionError, then
57
+ # swallowed -> DrbFactoryException); with the guard it terminates and
58
+ # classifies the node as the 'loop' container.
59
+ # The node must have a parent so the resolver skips the protocol/root
60
+ # dispatch path and goes directly to container signature matching,
61
+ # where the xquery '*' iterates children via DrbNodeList -> create().
62
+ parent = DrbLogicalNode("root")
63
+ node = _CyclicNode("loop", parent=parent)
64
+ topic, _ = resolver.resolve(node)
65
+ self.assertEqual("loop", topic.label)
@@ -0,0 +1,81 @@
1
+ import unittest
2
+ from unittest.mock import patch, MagicMock
3
+
4
+ import drb.topics.resolver as resolver_mod
5
+ from drb.core.factory import DrbFactory
6
+ from drb.core.node import DrbNode
7
+ from drb.nodes.logical_node import DrbLogicalNode
8
+ from drb.nodes.url_node import UrlNode
9
+ from drb.exceptions.core import DrbFactoryException
10
+
11
+
12
+ class _WrapFactory(DrbFactory):
13
+ """Wraps the received node and records the call order on a shared list."""
14
+ def __init__(self, tag, order):
15
+ self.tag = tag
16
+ self.order = order
17
+
18
+ def _create(self, node: DrbNode) -> DrbNode:
19
+ self.order.append(self.tag)
20
+ wrapped = DrbLogicalNode(self.tag)
21
+ wrapped.append_child(node)
22
+ return wrapped
23
+
24
+
25
+ class TestForcedSchemeUrl(unittest.TestCase):
26
+ """``__create_from_url`` on a chained ``<base>+<forced>://`` scheme must
27
+ resolve the *base* URL (token stripped, so the existing path-walking can
28
+ descend any container) and then apply each forced driver to the resulting
29
+ leaf -- not cram the forced driver onto the whole-path base resource.
30
+ """
31
+
32
+ def _from_url(self, url, base_leaf, token_factories):
33
+ r = resolver_mod._DrbFactoryResolver()
34
+ with patch.object(r, 'create', return_value=base_leaf) as create, \
35
+ patch.object(resolver_mod, '_resolve_token',
36
+ side_effect=lambda tok: (MagicMock(),
37
+ token_factories[tok])):
38
+ result = r._DrbFactoryResolver__create_from_url(url)
39
+ return result, create
40
+
41
+ def test_base_url_resolved_with_token_stripped(self):
42
+ leaf = DrbLogicalNode('base-leaf')
43
+ order = []
44
+ result, create = self._from_url(
45
+ 's3+xml://bucket/a.zip/inner', leaf,
46
+ {'xml': _WrapFactory('xml', order)})
47
+ # The base resolution must receive the clean s3:// URL.
48
+ self.assertEqual(1, create.call_count)
49
+ received = create.call_args[0][0]
50
+ self.assertIsInstance(received, UrlNode)
51
+ self.assertEqual('s3', received.path.scheme)
52
+ self.assertEqual('s3://bucket/a.zip/inner', received.path.name)
53
+
54
+ def test_forced_driver_applied_to_base_leaf(self):
55
+ leaf = DrbLogicalNode('base-leaf')
56
+ order = []
57
+ result, _ = self._from_url(
58
+ 's3+xml://bucket/a.zip/inner', leaf,
59
+ {'xml': _WrapFactory('xml', order)})
60
+ self.assertEqual('xml', result.name) # forced driver on top
61
+ self.assertEqual('base-leaf', result[0].name) # wrapping the leaf
62
+ self.assertEqual(['xml'], order)
63
+
64
+ def test_multiple_tokens_applied_in_order(self):
65
+ leaf = DrbLogicalNode('base-leaf')
66
+ order = []
67
+ result, _ = self._from_url(
68
+ 'https+odata+xml://host/x', leaf,
69
+ {'odata': _WrapFactory('odata', order),
70
+ 'xml': _WrapFactory('xml', order)})
71
+ self.assertEqual(['odata', 'xml'], order)
72
+ self.assertEqual('xml', result.name)
73
+
74
+ def test_empty_scheme_segment_raises(self):
75
+ r = resolver_mod._DrbFactoryResolver()
76
+ with self.assertRaises(DrbFactoryException):
77
+ r._DrbFactoryResolver__create_from_url('s3++xml://host/x')
78
+
79
+
80
+ if __name__ == '__main__':
81
+ unittest.main()
@@ -0,0 +1,18 @@
1
+ import unittest
2
+
3
+ from drb.nodes.logical_node import DrbLogicalNode
4
+ from drb.signatures.core import NameSignature
5
+
6
+
7
+ class TestNameSignatureInlineFlags(unittest.TestCase):
8
+ def test_misplaced_inline_flag_compiles_and_matches(self):
9
+ # (?i) after the ^ anchor: invalid as a raw py3.11 regex.
10
+ sig = NameSignature(r'^(?i)S2[AB]_.*\.SAFE$')
11
+ self.assertTrue(sig.matches(DrbLogicalNode('S2B_x.SAFE')))
12
+ # global case-insensitivity preserved
13
+ self.assertTrue(sig.matches(DrbLogicalNode('s2b_x.safe')))
14
+ self.assertFalse(sig.matches(DrbLogicalNode('X9_x.SAFE')))
15
+
16
+ def test_to_dict_keeps_the_original_pattern(self):
17
+ pat = r'^(?i)S2[AB]_.*\.SAFE$'
18
+ self.assertEqual({'name': pat}, NameSignature(pat).to_dict())
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
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
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
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
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
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
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
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
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
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