drb 2.4.3__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.3/drb.egg-info → drb-2.4.4}/PKG-INFO +1 -1
  2. {drb-2.4.3 → drb-2.4.4}/drb/core/_version.py +3 -3
  3. {drb-2.4.3 → drb-2.4.4}/drb/signatures/core.py +42 -5
  4. {drb-2.4.3 → drb-2.4.4}/drb/topics/resolver.py +54 -32
  5. {drb-2.4.3 → drb-2.4.4/drb.egg-info}/PKG-INFO +1 -1
  6. {drb-2.4.3 → drb-2.4.4}/drb.egg-info/SOURCES.txt +3 -0
  7. drb-2.4.4/tests/test_finest_multiparent.py +71 -0
  8. drb-2.4.4/tests/test_resolver_recursion_guard.py +65 -0
  9. drb-2.4.4/tests/test_signature_inline_flags.py +18 -0
  10. {drb-2.4.3 → drb-2.4.4}/LICENCE.txt +0 -0
  11. {drb-2.4.3 → drb-2.4.4}/MANIFEST.in +0 -0
  12. {drb-2.4.3 → drb-2.4.4}/README.md +0 -0
  13. {drb-2.4.3 → drb-2.4.4}/drb/addons/addon.py +0 -0
  14. {drb-2.4.3 → drb-2.4.4}/drb/core/__init__.py +0 -0
  15. {drb-2.4.3 → drb-2.4.4}/drb/core/context.py +0 -0
  16. {drb-2.4.3 → drb-2.4.4}/drb/core/cortex_validator.py +0 -0
  17. {drb-2.4.3 → drb-2.4.4}/drb/core/events.py +0 -0
  18. {drb-2.4.3 → drb-2.4.4}/drb/core/factory.py +0 -0
  19. {drb-2.4.3 → drb-2.4.4}/drb/core/impls.py +0 -0
  20. {drb-2.4.3 → drb-2.4.4}/drb/core/item.py +0 -0
  21. {drb-2.4.3 → drb-2.4.4}/drb/core/node.py +0 -0
  22. {drb-2.4.3 → drb-2.4.4}/drb/core/path.py +0 -0
  23. {drb-2.4.3 → drb-2.4.4}/drb/core/predicate.py +0 -0
  24. {drb-2.4.3 → drb-2.4.4}/drb/core/signature.py +0 -0
  25. {drb-2.4.3 → drb-2.4.4}/drb/drivers/file/__init__.py +0 -0
  26. {drb-2.4.3 → drb-2.4.4}/drb/drivers/file/cortex.ttl +0 -0
  27. {drb-2.4.3 → drb-2.4.4}/drb/drivers/file/file.py +0 -0
  28. {drb-2.4.3 → drb-2.4.4}/drb/drivers/xml/__init__.py +0 -0
  29. {drb-2.4.3 → drb-2.4.4}/drb/drivers/xml/cortex.ttl +0 -0
  30. {drb-2.4.3 → drb-2.4.4}/drb/drivers/xml/factory.py +0 -0
  31. {drb-2.4.3 → drb-2.4.4}/drb/drivers/xml/node.py +0 -0
  32. {drb-2.4.3 → drb-2.4.4}/drb/exceptions/core.py +0 -0
  33. {drb-2.4.3 → drb-2.4.4}/drb/exceptions/file.py +0 -0
  34. {drb-2.4.3 → drb-2.4.4}/drb/keyring/bitwarden/__init__.py +0 -0
  35. {drb-2.4.3 → drb-2.4.4}/drb/keyring/bitwarden/bitwarden.py +0 -0
  36. {drb-2.4.3 → drb-2.4.4}/drb/nodes/abstract_node.py +0 -0
  37. {drb-2.4.3 → drb-2.4.4}/drb/nodes/logical_node.py +0 -0
  38. {drb-2.4.3 → drb-2.4.4}/drb/nodes/mutable_node.py +0 -0
  39. {drb-2.4.3 → drb-2.4.4}/drb/nodes/url_node.py +0 -0
  40. {drb-2.4.3 → drb-2.4.4}/drb/signatures/hierarchy.py +0 -0
  41. {drb-2.4.3 → drb-2.4.4}/drb/signatures/xquery.py +0 -0
  42. {drb-2.4.3 → drb-2.4.4}/drb/topics/dao/__init__.py +0 -0
  43. {drb-2.4.3 → drb-2.4.4}/drb/topics/dao/manager_dao.py +0 -0
  44. {drb-2.4.3 → drb-2.4.4}/drb/topics/dao/rdf_dao.py +0 -0
  45. {drb-2.4.3 → drb-2.4.4}/drb/topics/dao/topic_dao.py +0 -0
  46. {drb-2.4.3 → drb-2.4.4}/drb/topics/dao/topic_schema.yml +0 -0
  47. {drb-2.4.3 → drb-2.4.4}/drb/topics/dao/xml_dao.py +0 -0
  48. {drb-2.4.3 → drb-2.4.4}/drb/topics/file/__init__.py +0 -0
  49. {drb-2.4.3 → drb-2.4.4}/drb/topics/file/cortex.ttl +0 -0
  50. {drb-2.4.3 → drb-2.4.4}/drb/topics/topic.py +0 -0
  51. {drb-2.4.3 → drb-2.4.4}/drb/topics/xml/__init__.py +0 -0
  52. {drb-2.4.3 → drb-2.4.4}/drb/topics/xml/cortex.ttl +0 -0
  53. {drb-2.4.3 → drb-2.4.4}/drb/utils/__init__.py +0 -0
  54. {drb-2.4.3 → drb-2.4.4}/drb/utils/drb_python_script.py +0 -0
  55. {drb-2.4.3 → drb-2.4.4}/drb/utils/plugins.py +0 -0
  56. {drb-2.4.3 → drb-2.4.4}/drb/utils/url_security.py +0 -0
  57. {drb-2.4.3 → drb-2.4.4}/drb/utils/utils.py +0 -0
  58. {drb-2.4.3 → drb-2.4.4}/drb/xquery/XQueryLexer.py +0 -0
  59. {drb-2.4.3 → drb-2.4.4}/drb/xquery/XQueryParser.py +0 -0
  60. {drb-2.4.3 → drb-2.4.4}/drb/xquery/XQueryParserListener.py +0 -0
  61. {drb-2.4.3 → drb-2.4.4}/drb/xquery/XQueryParserVisitor.py +0 -0
  62. {drb-2.4.3 → drb-2.4.4}/drb/xquery/__init__.py +0 -0
  63. {drb-2.4.3 → drb-2.4.4}/drb/xquery/drb_xquery.py +0 -0
  64. {drb-2.4.3 → drb-2.4.4}/drb/xquery/drb_xquery_cmd.py +0 -0
  65. {drb-2.4.3 → drb-2.4.4}/drb/xquery/drb_xquery_context.py +0 -0
  66. {drb-2.4.3 → drb-2.4.4}/drb/xquery/drb_xquery_func.py +0 -0
  67. {drb-2.4.3 → drb-2.4.4}/drb/xquery/drb_xquery_item.py +0 -0
  68. {drb-2.4.3 → drb-2.4.4}/drb/xquery/drb_xquery_res_to_string.py +0 -0
  69. {drb-2.4.3 → drb-2.4.4}/drb/xquery/drb_xquery_utils.py +0 -0
  70. {drb-2.4.3 → drb-2.4.4}/drb/xquery/drb_xquery_variable.py +0 -0
  71. {drb-2.4.3 → drb-2.4.4}/drb/xquery/drb_xquery_visitor.py +0 -0
  72. {drb-2.4.3 → drb-2.4.4}/drb/xquery/exceptions.py +0 -0
  73. {drb-2.4.3 → drb-2.4.4}/drb.egg-info/dependency_links.txt +0 -0
  74. {drb-2.4.3 → drb-2.4.4}/drb.egg-info/entry_points.txt +0 -0
  75. {drb-2.4.3 → drb-2.4.4}/drb.egg-info/not-zip-safe +0 -0
  76. {drb-2.4.3 → drb-2.4.4}/drb.egg-info/requires.txt +0 -0
  77. {drb-2.4.3 → drb-2.4.4}/drb.egg-info/top_level.txt +0 -0
  78. {drb-2.4.3 → drb-2.4.4}/pyproject.toml +0 -0
  79. {drb-2.4.3 → drb-2.4.4}/requirements.txt +0 -0
  80. {drb-2.4.3 → drb-2.4.4}/setup.cfg +0 -0
  81. {drb-2.4.3 → drb-2.4.4}/setup.py +0 -0
  82. {drb-2.4.3 → drb-2.4.4}/tests/test_abstract_node.py +0 -0
  83. {drb-2.4.3 → drb-2.4.4}/tests/test_addon.py +0 -0
  84. {drb-2.4.3 → drb-2.4.4}/tests/test_children_unresolved.py +0 -0
  85. {drb-2.4.3 → drb-2.4.4}/tests/test_core_topic_ttl.py +0 -0
  86. {drb-2.4.3 → drb-2.4.4}/tests/test_cortex_validator.py +0 -0
  87. {drb-2.4.3 → drb-2.4.4}/tests/test_drb_node_list.py +0 -0
  88. {drb-2.4.3 → drb-2.4.4}/tests/test_drb_python_script.py +0 -0
  89. {drb-2.4.3 → drb-2.4.4}/tests/test_event.py +0 -0
  90. {drb-2.4.3 → drb-2.4.4}/tests/test_factory.py +0 -0
  91. {drb-2.4.3 → drb-2.4.4}/tests/test_factory_resolver.py +0 -0
  92. {drb-2.4.3 → drb-2.4.4}/tests/test_implementation_coupling.py +0 -0
  93. {drb-2.4.3 → drb-2.4.4}/tests/test_implementation_manager.py +0 -0
  94. {drb-2.4.3 → drb-2.4.4}/tests/test_keyring_bitwarden.py +0 -0
  95. {drb-2.4.3 → drb-2.4.4}/tests/test_logical_node.py +0 -0
  96. {drb-2.4.3 → drb-2.4.4}/tests/test_manager_dao.py +0 -0
  97. {drb-2.4.3 → drb-2.4.4}/tests/test_manager_get_dao.py +0 -0
  98. {drb-2.4.3 → drb-2.4.4}/tests/test_node_context.py +0 -0
  99. {drb-2.4.3 → drb-2.4.4}/tests/test_node_descendant.py +0 -0
  100. {drb-2.4.3 → drb-2.4.4}/tests/test_path.py +0 -0
  101. {drb-2.4.3 → drb-2.4.4}/tests/test_protocol_dispatch.py +0 -0
  102. {drb-2.4.3 → drb-2.4.4}/tests/test_rdf_dao.py +0 -0
  103. {drb-2.4.3 → drb-2.4.4}/tests/test_rdf_dao_multi_url.py +0 -0
  104. {drb-2.4.3 → drb-2.4.4}/tests/test_rdf_dao_scheme.py +0 -0
  105. {drb-2.4.3 → drb-2.4.4}/tests/test_rdf_dao_signatures.py +0 -0
  106. {drb-2.4.3 → drb-2.4.4}/tests/test_scheme_force_impl.py +0 -0
  107. {drb-2.4.3 → drb-2.4.4}/tests/test_scheme_force_url.py +0 -0
  108. {drb-2.4.3 → drb-2.4.4}/tests/test_signature_exact.py +0 -0
  109. {drb-2.4.3 → drb-2.4.4}/tests/test_signature_hierarchy.py +0 -0
  110. {drb-2.4.3 → drb-2.4.4}/tests/test_signatures.py +0 -0
  111. {drb-2.4.3 → drb-2.4.4}/tests/test_topic.py +0 -0
  112. {drb-2.4.3 → drb-2.4.4}/tests/test_topic_descriptor_discovery.py +0 -0
  113. {drb-2.4.3 → drb-2.4.4}/tests/test_url_node.py +0 -0
  114. {drb-2.4.3 → drb-2.4.4}/tests/test_url_security.py +0 -0
  115. {drb-2.4.3 → drb-2.4.4}/tests/test_xml_dao.py +0 -0
  116. {drb-2.4.3 → 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.3
3
+ Version: 2.4.4
4
4
  Summary: Data Request Broker
5
5
  Author: GAEL Systems
6
6
  Author-email: drb-python@gael.fr
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2026-06-26T23:35:37+0000",
11
+ "date": "2026-06-30T02:14:50+0000",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "3fe10812b0582229d483dc9325051d2e70c16653",
15
- "version": "2.4.3"
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):
@@ -356,38 +359,57 @@ class _DrbFactoryResolver(DrbFactory):
356
359
  node = UrlNode(source)
357
360
  else:
358
361
  node = source
359
- protocol = None
360
- if node.parent is None:
361
- dispatched = self.__dispatch_scheme(node)
362
- if dispatched is not None:
363
- 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
364
401
  else:
365
- protocol = self.__retrieve_protocol(node)
366
- if protocol is None:
367
- raise DrbFactoryException(f'Cannot resolve: {source}')
368
- node = _create_via_factory(protocol, node)
369
-
370
- container, node = self.__retrieve_container(node)
371
- if container is not None and container.factory is not None:
372
- node = _create_via_factory(container, node)
373
-
374
- formatting = self.__retrieve_formatting(node)
375
- if formatting is not None:
376
- if formatting.factory is not None:
377
- node = _create_via_factory(formatting, node)
378
- t, n = self.__finest_drb_topic(node, formatting)
379
- elif container is not None:
380
- t, n = container, node
381
- elif protocol is not None:
382
- t, n = protocol, node
383
- else:
384
- raise DrbFactoryException(f'Cannot resolve: {source}')
385
-
386
- addons = filter(lambda x: x.can_apply(t),
387
- AddonManager().get_all_addons())
388
- for addon in addons:
389
- node.add_impl(addon.return_type(), addon.apply, addon.identifier())
390
- 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)
391
413
 
392
414
  def resolve(self, source: Union[str, DrbNode]) \
393
415
  -> Tuple[DrbTopic, Optional[DrbNode]]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drb
3
- Version: 2.4.3
3
+ Version: 2.4.4
4
4
  Summary: Data Request Broker
5
5
  Author: GAEL Systems
6
6
  Author-email: drb-python@gael.fr
@@ -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,10 +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
104
106
  tests/test_scheme_force_url.py
105
107
  tests/test_signature_exact.py
106
108
  tests/test_signature_hierarchy.py
109
+ tests/test_signature_inline_flags.py
107
110
  tests/test_signatures.py
108
111
  tests/test_topic.py
109
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,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
File without changes
File without changes