drb 2.7.2__tar.gz → 2.7.3__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 (128) hide show
  1. {drb-2.7.2/drb.egg-info → drb-2.7.3}/PKG-INFO +1 -1
  2. {drb-2.7.2 → drb-2.7.3}/drb/core/_version.py +3 -3
  3. {drb-2.7.2 → drb-2.7.3}/drb/nodes/abstract_node.py +11 -7
  4. {drb-2.7.2 → drb-2.7.3}/drb/topics/resolver.py +32 -4
  5. {drb-2.7.2 → drb-2.7.3/drb.egg-info}/PKG-INFO +1 -1
  6. {drb-2.7.2 → drb-2.7.3}/drb.egg-info/SOURCES.txt +2 -0
  7. drb-2.7.3/tests/test_named_child_no_sibling_resolution.py +60 -0
  8. drb-2.7.3/tests/test_resolver_factoryless_refinement.py +75 -0
  9. {drb-2.7.2 → drb-2.7.3}/LICENCE.txt +0 -0
  10. {drb-2.7.2 → drb-2.7.3}/MANIFEST.in +0 -0
  11. {drb-2.7.2 → drb-2.7.3}/README.md +0 -0
  12. {drb-2.7.2 → drb-2.7.3}/drb/addons/addon.py +0 -0
  13. {drb-2.7.2 → drb-2.7.3}/drb/core/__init__.py +0 -0
  14. {drb-2.7.2 → drb-2.7.3}/drb/core/auth.py +0 -0
  15. {drb-2.7.2 → drb-2.7.3}/drb/core/context.py +0 -0
  16. {drb-2.7.2 → drb-2.7.3}/drb/core/cortex_validator.py +0 -0
  17. {drb-2.7.2 → drb-2.7.3}/drb/core/events.py +0 -0
  18. {drb-2.7.2 → drb-2.7.3}/drb/core/factory.py +0 -0
  19. {drb-2.7.2 → drb-2.7.3}/drb/core/impls.py +0 -0
  20. {drb-2.7.2 → drb-2.7.3}/drb/core/item.py +0 -0
  21. {drb-2.7.2 → drb-2.7.3}/drb/core/node.py +0 -0
  22. {drb-2.7.2 → drb-2.7.3}/drb/core/path.py +0 -0
  23. {drb-2.7.2 → drb-2.7.3}/drb/core/predicate.py +0 -0
  24. {drb-2.7.2 → drb-2.7.3}/drb/core/signature.py +0 -0
  25. {drb-2.7.2 → drb-2.7.3}/drb/drivers/file/__init__.py +0 -0
  26. {drb-2.7.2 → drb-2.7.3}/drb/drivers/file/cortex.ttl +0 -0
  27. {drb-2.7.2 → drb-2.7.3}/drb/drivers/file/file.py +0 -0
  28. {drb-2.7.2 → drb-2.7.3}/drb/drivers/xml/__init__.py +0 -0
  29. {drb-2.7.2 → drb-2.7.3}/drb/drivers/xml/cortex.ttl +0 -0
  30. {drb-2.7.2 → drb-2.7.3}/drb/drivers/xml/factory.py +0 -0
  31. {drb-2.7.2 → drb-2.7.3}/drb/drivers/xml/node.py +0 -0
  32. {drb-2.7.2 → drb-2.7.3}/drb/exceptions/core.py +0 -0
  33. {drb-2.7.2 → drb-2.7.3}/drb/exceptions/file.py +0 -0
  34. {drb-2.7.2 → drb-2.7.3}/drb/keyring/bitwarden/__init__.py +0 -0
  35. {drb-2.7.2 → drb-2.7.3}/drb/keyring/bitwarden/bitwarden.py +0 -0
  36. {drb-2.7.2 → drb-2.7.3}/drb/nodes/logical_node.py +0 -0
  37. {drb-2.7.2 → drb-2.7.3}/drb/nodes/mutable_node.py +0 -0
  38. {drb-2.7.2 → drb-2.7.3}/drb/nodes/url_node.py +0 -0
  39. {drb-2.7.2 → drb-2.7.3}/drb/signatures/core.py +0 -0
  40. {drb-2.7.2 → drb-2.7.3}/drb/signatures/hierarchy.py +0 -0
  41. {drb-2.7.2 → drb-2.7.3}/drb/signatures/xquery.py +0 -0
  42. {drb-2.7.2 → drb-2.7.3}/drb/topics/dao/__init__.py +0 -0
  43. {drb-2.7.2 → drb-2.7.3}/drb/topics/dao/manager_dao.py +0 -0
  44. {drb-2.7.2 → drb-2.7.3}/drb/topics/dao/rdf_dao.py +0 -0
  45. {drb-2.7.2 → drb-2.7.3}/drb/topics/dao/topic_dao.py +0 -0
  46. {drb-2.7.2 → drb-2.7.3}/drb/topics/dao/topic_schema.yml +0 -0
  47. {drb-2.7.2 → drb-2.7.3}/drb/topics/dao/xml_dao.py +0 -0
  48. {drb-2.7.2 → drb-2.7.3}/drb/topics/file/__init__.py +0 -0
  49. {drb-2.7.2 → drb-2.7.3}/drb/topics/file/cortex.ttl +0 -0
  50. {drb-2.7.2 → drb-2.7.3}/drb/topics/topic.py +0 -0
  51. {drb-2.7.2 → drb-2.7.3}/drb/topics/xml/__init__.py +0 -0
  52. {drb-2.7.2 → drb-2.7.3}/drb/topics/xml/cortex.ttl +0 -0
  53. {drb-2.7.2 → drb-2.7.3}/drb/utils/__init__.py +0 -0
  54. {drb-2.7.2 → drb-2.7.3}/drb/utils/drb_python_script.py +0 -0
  55. {drb-2.7.2 → drb-2.7.3}/drb/utils/io.py +0 -0
  56. {drb-2.7.2 → drb-2.7.3}/drb/utils/plugins.py +0 -0
  57. {drb-2.7.2 → drb-2.7.3}/drb/utils/url_security.py +0 -0
  58. {drb-2.7.2 → drb-2.7.3}/drb/utils/utils.py +0 -0
  59. {drb-2.7.2 → drb-2.7.3}/drb/xquery/XQueryLexer.py +0 -0
  60. {drb-2.7.2 → drb-2.7.3}/drb/xquery/XQueryParser.py +0 -0
  61. {drb-2.7.2 → drb-2.7.3}/drb/xquery/XQueryParserListener.py +0 -0
  62. {drb-2.7.2 → drb-2.7.3}/drb/xquery/XQueryParserVisitor.py +0 -0
  63. {drb-2.7.2 → drb-2.7.3}/drb/xquery/__init__.py +0 -0
  64. {drb-2.7.2 → drb-2.7.3}/drb/xquery/drb_xquery.py +0 -0
  65. {drb-2.7.2 → drb-2.7.3}/drb/xquery/drb_xquery_cmd.py +0 -0
  66. {drb-2.7.2 → drb-2.7.3}/drb/xquery/drb_xquery_context.py +0 -0
  67. {drb-2.7.2 → drb-2.7.3}/drb/xquery/drb_xquery_func.py +0 -0
  68. {drb-2.7.2 → drb-2.7.3}/drb/xquery/drb_xquery_item.py +0 -0
  69. {drb-2.7.2 → drb-2.7.3}/drb/xquery/drb_xquery_res_to_string.py +0 -0
  70. {drb-2.7.2 → drb-2.7.3}/drb/xquery/drb_xquery_utils.py +0 -0
  71. {drb-2.7.2 → drb-2.7.3}/drb/xquery/drb_xquery_variable.py +0 -0
  72. {drb-2.7.2 → drb-2.7.3}/drb/xquery/drb_xquery_visitor.py +0 -0
  73. {drb-2.7.2 → drb-2.7.3}/drb/xquery/exceptions.py +0 -0
  74. {drb-2.7.2 → drb-2.7.3}/drb.egg-info/dependency_links.txt +0 -0
  75. {drb-2.7.2 → drb-2.7.3}/drb.egg-info/entry_points.txt +0 -0
  76. {drb-2.7.2 → drb-2.7.3}/drb.egg-info/not-zip-safe +0 -0
  77. {drb-2.7.2 → drb-2.7.3}/drb.egg-info/requires.txt +0 -0
  78. {drb-2.7.2 → drb-2.7.3}/drb.egg-info/top_level.txt +0 -0
  79. {drb-2.7.2 → drb-2.7.3}/pyproject.toml +0 -0
  80. {drb-2.7.2 → drb-2.7.3}/requirements.txt +0 -0
  81. {drb-2.7.2 → drb-2.7.3}/setup.cfg +0 -0
  82. {drb-2.7.2 → drb-2.7.3}/setup.py +0 -0
  83. {drb-2.7.2 → drb-2.7.3}/tests/test_abstract_node.py +0 -0
  84. {drb-2.7.2 → drb-2.7.3}/tests/test_addon.py +0 -0
  85. {drb-2.7.2 → drb-2.7.3}/tests/test_auth.py +0 -0
  86. {drb-2.7.2 → drb-2.7.3}/tests/test_children_unresolved.py +0 -0
  87. {drb-2.7.2 → drb-2.7.3}/tests/test_core_topic_ttl.py +0 -0
  88. {drb-2.7.2 → drb-2.7.3}/tests/test_cortex_validator.py +0 -0
  89. {drb-2.7.2 → drb-2.7.3}/tests/test_drb_node_list.py +0 -0
  90. {drb-2.7.2 → drb-2.7.3}/tests/test_drb_python_script.py +0 -0
  91. {drb-2.7.2 → drb-2.7.3}/tests/test_event.py +0 -0
  92. {drb-2.7.2 → drb-2.7.3}/tests/test_factory.py +0 -0
  93. {drb-2.7.2 → drb-2.7.3}/tests/test_factory_resolver.py +0 -0
  94. {drb-2.7.2 → drb-2.7.3}/tests/test_finest_multiparent.py +0 -0
  95. {drb-2.7.2 → drb-2.7.3}/tests/test_implementation_coupling.py +0 -0
  96. {drb-2.7.2 → drb-2.7.3}/tests/test_implementation_manager.py +0 -0
  97. {drb-2.7.2 → drb-2.7.3}/tests/test_keyring_bitwarden.py +0 -0
  98. {drb-2.7.2 → drb-2.7.3}/tests/test_logical_node.py +0 -0
  99. {drb-2.7.2 → drb-2.7.3}/tests/test_manager_dao.py +0 -0
  100. {drb-2.7.2 → drb-2.7.3}/tests/test_manager_get_dao.py +0 -0
  101. {drb-2.7.2 → drb-2.7.3}/tests/test_node_context.py +0 -0
  102. {drb-2.7.2 → drb-2.7.3}/tests/test_node_context_manager.py +0 -0
  103. {drb-2.7.2 → drb-2.7.3}/tests/test_node_descendant.py +0 -0
  104. {drb-2.7.2 → drb-2.7.3}/tests/test_path.py +0 -0
  105. {drb-2.7.2 → drb-2.7.3}/tests/test_protocol_dispatch.py +0 -0
  106. {drb-2.7.2 → drb-2.7.3}/tests/test_rdf_dao.py +0 -0
  107. {drb-2.7.2 → drb-2.7.3}/tests/test_rdf_dao_multi_url.py +0 -0
  108. {drb-2.7.2 → drb-2.7.3}/tests/test_rdf_dao_scheme.py +0 -0
  109. {drb-2.7.2 → drb-2.7.3}/tests/test_rdf_dao_signatures.py +0 -0
  110. {drb-2.7.2 → drb-2.7.3}/tests/test_resolver_auth.py +0 -0
  111. {drb-2.7.2 → drb-2.7.3}/tests/test_resolver_format_alias.py +0 -0
  112. {drb-2.7.2 → drb-2.7.3}/tests/test_resolver_implicit_file.py +0 -0
  113. {drb-2.7.2 → drb-2.7.3}/tests/test_resolver_recursion_guard.py +0 -0
  114. {drb-2.7.2 → drb-2.7.3}/tests/test_scheme_force_impl.py +0 -0
  115. {drb-2.7.2 → drb-2.7.3}/tests/test_scheme_force_url.py +0 -0
  116. {drb-2.7.2 → drb-2.7.3}/tests/test_signature_duplicate_warning.py +0 -0
  117. {drb-2.7.2 → drb-2.7.3}/tests/test_signature_exact.py +0 -0
  118. {drb-2.7.2 → drb-2.7.3}/tests/test_signature_hierarchy.py +0 -0
  119. {drb-2.7.2 → drb-2.7.3}/tests/test_signature_inline_flags.py +0 -0
  120. {drb-2.7.2 → drb-2.7.3}/tests/test_signatures.py +0 -0
  121. {drb-2.7.2 → drb-2.7.3}/tests/test_topic.py +0 -0
  122. {drb-2.7.2 → drb-2.7.3}/tests/test_topic_descriptor_discovery.py +0 -0
  123. {drb-2.7.2 → drb-2.7.3}/tests/test_topic_merge_crosspackage.py +0 -0
  124. {drb-2.7.2 → drb-2.7.3}/tests/test_url_node.py +0 -0
  125. {drb-2.7.2 → drb-2.7.3}/tests/test_url_security.py +0 -0
  126. {drb-2.7.2 → drb-2.7.3}/tests/test_xml_dao.py +0 -0
  127. {drb-2.7.2 → drb-2.7.3}/tests/test_xml_xquery_import_order.py +0 -0
  128. {drb-2.7.2 → drb-2.7.3}/versioneer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drb
3
- Version: 2.7.2
3
+ Version: 2.7.3
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-07-07T13:58:00+0000",
11
+ "date": "2026-07-09T07:37:18+0000",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "0ca7a30d2606890cfee27aa9c615247cf1a7be67",
15
- "version": "2.7.2"
14
+ "full-revisionid": "db4dbac9d91e0226023e52ccca1fda79506fb923",
15
+ "version": "2.7.3"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -162,14 +162,14 @@ class AbstractNode(DrbNode, abc.ABC):
162
162
  occ = occurrence
163
163
  cpt = 0
164
164
  if self.namespace_aware or namespace_uri is not None:
165
- for x in self._browse_children():
165
+ for x in self.children_unresolved:
166
166
  if x.name == name and x.namespace_uri == namespace_uri:
167
167
  if occ <= 0:
168
168
  return cpt
169
169
  occ -= 1
170
170
  cpt += 1
171
171
  else:
172
- for x in self._browse_children():
172
+ for x in self.children_unresolved:
173
173
  if x.name == name:
174
174
  return cpt
175
175
  cpt += 1
@@ -199,12 +199,16 @@ class AbstractNode(DrbNode, abc.ABC):
199
199
  IndexError: if item is out of range of found children
200
200
  DrbException: if no child following given criteria is found
201
201
  """
202
+ # Match unresolved children and let the caller resolve only the
203
+ # selected node: a targeted named lookup must not resolve unrelated
204
+ # siblings (contract: the driver owns optimized access; the core
205
+ # resolves only when necessary). drb-testbench#21.
202
206
  if self.namespace_aware or namespace_uri is not None:
203
- named_children = [x for x in self._browse_children()
207
+ named_children = [x for x in self.children_unresolved
204
208
  if x.name == name
205
209
  and x.namespace_uri == namespace_uri]
206
210
  else:
207
- named_children = [x for x in self._browse_children()
211
+ named_children = [x for x in self.children_unresolved
208
212
  if x.name == name]
209
213
  if len(named_children) <= 0:
210
214
  raise DrbException(f'No child found having name: {name} and'
@@ -225,13 +229,13 @@ class AbstractNode(DrbNode, abc.ABC):
225
229
 
226
230
  result = []
227
231
  if name is not None and namespace is not None:
228
- result = [x for x in self._browse_children()
232
+ result = [x for x in self.children_unresolved
229
233
  if x.name == name and x.namespace_uri == namespace]
230
234
  if name is None:
231
- result = [x for x in self._browse_children()
235
+ result = [x for x in self.children_unresolved
232
236
  if x.namespace_uri == namespace]
233
237
  if namespace is None:
234
- result = [x for x in self._browse_children() if x.name == name]
238
+ result = [x for x in self.children_unresolved if x.name == name]
235
239
 
236
240
  return len(result) > 0
237
241
 
@@ -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 and container.factory is not None:
481
- node = _create_via_factory(container, node)
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
- if formatting.factory is not None:
486
- node = _create_via_factory(formatting, node)
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.7.2
3
+ Version: 2.7.3
4
4
  Summary: Data Request Broker
5
5
  Author: GAEL Systems
6
6
  Author-email: drb-python@gael.fr
@@ -96,6 +96,7 @@ tests/test_keyring_bitwarden.py
96
96
  tests/test_logical_node.py
97
97
  tests/test_manager_dao.py
98
98
  tests/test_manager_get_dao.py
99
+ tests/test_named_child_no_sibling_resolution.py
99
100
  tests/test_node_context.py
100
101
  tests/test_node_context_manager.py
101
102
  tests/test_node_descendant.py
@@ -106,6 +107,7 @@ tests/test_rdf_dao_multi_url.py
106
107
  tests/test_rdf_dao_scheme.py
107
108
  tests/test_rdf_dao_signatures.py
108
109
  tests/test_resolver_auth.py
110
+ tests/test_resolver_factoryless_refinement.py
109
111
  tests/test_resolver_format_alias.py
110
112
  tests/test_resolver_implicit_file.py
111
113
  tests/test_resolver_recursion_guard.py
@@ -0,0 +1,60 @@
1
+ """A named-child lookup must not resolve unrelated siblings (drb-testbench#21).
2
+
3
+ ``_get_named_child`` served a targeted lookup by iterating the *resolved*
4
+ children list and filtering by name -- so it resolved **every** sibling to
5
+ return one child. That scales as O(N) resolutions on large containers and lets
6
+ an unrelated sibling's factory crash the lookup (e.g. a GRIB signature firing
7
+ ``io.FileIO`` on ``~/.gradle``). Per the driver/core contract the core must
8
+ delegate optimized access to the driver and resolve only when necessary: a
9
+ named lookup matches unresolved children and resolves only the selected node.
10
+ """
11
+ import os
12
+ import tempfile
13
+ import unittest
14
+ from unittest import mock
15
+
16
+ import drb.topics.resolver as resolver
17
+
18
+
19
+ class TestNamedChildNoSiblingResolution(unittest.TestCase):
20
+ def setUp(self):
21
+ # A real directory: the file driver's children are resolved on
22
+ # iteration (@resolve_children), which is where sibling resolution
23
+ # leaks in.
24
+ self.dir = tempfile.mkdtemp()
25
+ for name in ('good', 'bad'):
26
+ open(os.path.join(self.dir, name), 'w').close()
27
+
28
+ def test_named_lookup_resolves_only_the_target(self):
29
+ node = resolver.create(self.dir)
30
+ resolved = []
31
+ real = resolver.create
32
+
33
+ def spy(n, *args, **kwargs):
34
+ resolved.append(getattr(n, 'name', None))
35
+ return real(n, *args, **kwargs)
36
+
37
+ with mock.patch.object(resolver, 'create', side_effect=spy):
38
+ child = node['good']
39
+
40
+ self.assertEqual('good', child.name)
41
+ self.assertNotIn('bad', resolved,
42
+ 'named lookup must not resolve unrelated siblings')
43
+
44
+ def test_named_lookup_survives_unresolvable_sibling(self):
45
+ node = resolver.create(self.dir)
46
+ real = resolver.create
47
+
48
+ def spy(n, *args, **kwargs):
49
+ if getattr(n, 'name', None) == 'bad':
50
+ raise OSError('sibling factory blew up (must not be touched)')
51
+ return real(n, *args, **kwargs)
52
+
53
+ with mock.patch.object(resolver, 'create', side_effect=spy):
54
+ child = node['good']
55
+
56
+ self.assertEqual('good', child.name)
57
+
58
+
59
+ if __name__ == '__main__':
60
+ unittest.main()
@@ -0,0 +1,75 @@
1
+ """A factory-less semantic refinement must not suppress its driver.
2
+
3
+ Regression for drb-python/drb-testbench#20: when a container/formatting topic
4
+ is refined by a sub-topic that declares no driver ``factory`` (e.g. the
5
+ Sentinel-2 KB topic ``… : Zip`` which ``subClassOf`` the generic ``zip``
6
+ topic), resolution used to apply only the *finest* topic's factory -- so the
7
+ generic ``zip`` driver was never applied and the node was left un-navigable.
8
+
9
+ ``resolver._driver_topic`` returns the nearest topic in the ``subClassOf``
10
+ chain that actually declares a driver, so the node is built by that driver
11
+ while the finest topic still labels it.
12
+ """
13
+ import unittest
14
+ import uuid
15
+ from unittest import mock
16
+
17
+ from drb.topics import resolver
18
+ from drb.topics.topic import TopicCategory
19
+
20
+
21
+ class _Topic:
22
+ def __init__(self, factory, sub=None):
23
+ self.id = uuid.uuid4()
24
+ self.factory = factory
25
+ self.subClassOf = sub
26
+ self.label = f'topic-{factory}-{self.id.hex[:6]}'
27
+ self.category = TopicCategory.CONTAINER
28
+ self.signatures = []
29
+
30
+ def matches(self, node): # pragma: no cover - not exercised here
31
+ return True
32
+
33
+
34
+ class TestDriverTopic(unittest.TestCase):
35
+ @staticmethod
36
+ def _driver(topic, all_topics):
37
+ with mock.patch.object(resolver.ManagerDao, 'get_all_drb_topics',
38
+ return_value=all_topics):
39
+ return resolver._driver_topic(topic)
40
+
41
+ def test_topic_with_factory_returns_itself(self):
42
+ zip_t = _Topic('zip')
43
+ self.assertIs(zip_t, self._driver(zip_t, [zip_t]))
44
+
45
+ def test_factoryless_refinement_ascends_to_driver(self):
46
+ # zip (driver) <- abstract-zip (no factory) <- product-zip (no factory)
47
+ zip_t = _Topic('zip')
48
+ abstract = _Topic(None, sub=[zip_t.id])
49
+ product = _Topic(None, sub=[abstract.id])
50
+ self.assertIs(
51
+ zip_t, self._driver(product, [zip_t, abstract, product]))
52
+
53
+ def test_returns_nearest_driver_not_the_root(self):
54
+ # inner driver must win over a further ancestor driver
55
+ outer = _Topic('outer')
56
+ inner = _Topic('inner', sub=[outer.id])
57
+ leaf = _Topic(None, sub=[inner.id])
58
+ self.assertIs(inner, self._driver(leaf, [outer, inner, leaf]))
59
+
60
+ def test_no_factory_anywhere_returns_none(self):
61
+ a = _Topic(None)
62
+ b = _Topic(None, sub=[a.id])
63
+ self.assertIsNone(self._driver(b, [a, b]))
64
+
65
+ def test_multi_parent_chain_finds_the_driver_branch(self):
66
+ # a refinement subClassOf a semantic parent AND the zip driver
67
+ semantic = _Topic(None)
68
+ zip_t = _Topic('zip')
69
+ refine = _Topic(None, sub=[semantic.id, zip_t.id])
70
+ self.assertIs(
71
+ zip_t, self._driver(refine, [semantic, zip_t, refine]))
72
+
73
+
74
+ if __name__ == '__main__':
75
+ unittest.main()
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
File without changes
File without changes
File without changes
File without changes