drb 2.4.0__tar.gz → 2.4.2__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 (112) hide show
  1. {drb-2.4.0/drb.egg-info → drb-2.4.2}/PKG-INFO +1 -1
  2. {drb-2.4.0 → drb-2.4.2}/drb/core/_version.py +3 -3
  3. {drb-2.4.0 → drb-2.4.2}/drb/topics/file/cortex.ttl +1 -0
  4. {drb-2.4.0 → drb-2.4.2}/drb/topics/resolver.py +68 -20
  5. {drb-2.4.0 → drb-2.4.2/drb.egg-info}/PKG-INFO +1 -1
  6. {drb-2.4.0 → drb-2.4.2}/drb.egg-info/SOURCES.txt +1 -0
  7. {drb-2.4.0 → drb-2.4.2}/tests/test_core_topic_ttl.py +8 -0
  8. {drb-2.4.0 → drb-2.4.2}/tests/test_protocol_dispatch.py +3 -0
  9. drb-2.4.2/tests/test_scheme_force_impl.py +170 -0
  10. {drb-2.4.0 → drb-2.4.2}/LICENCE.txt +0 -0
  11. {drb-2.4.0 → drb-2.4.2}/MANIFEST.in +0 -0
  12. {drb-2.4.0 → drb-2.4.2}/README.md +0 -0
  13. {drb-2.4.0 → drb-2.4.2}/drb/addons/addon.py +0 -0
  14. {drb-2.4.0 → drb-2.4.2}/drb/core/__init__.py +0 -0
  15. {drb-2.4.0 → drb-2.4.2}/drb/core/context.py +0 -0
  16. {drb-2.4.0 → drb-2.4.2}/drb/core/cortex_validator.py +0 -0
  17. {drb-2.4.0 → drb-2.4.2}/drb/core/events.py +0 -0
  18. {drb-2.4.0 → drb-2.4.2}/drb/core/factory.py +0 -0
  19. {drb-2.4.0 → drb-2.4.2}/drb/core/impls.py +0 -0
  20. {drb-2.4.0 → drb-2.4.2}/drb/core/item.py +0 -0
  21. {drb-2.4.0 → drb-2.4.2}/drb/core/node.py +0 -0
  22. {drb-2.4.0 → drb-2.4.2}/drb/core/path.py +0 -0
  23. {drb-2.4.0 → drb-2.4.2}/drb/core/predicate.py +0 -0
  24. {drb-2.4.0 → drb-2.4.2}/drb/core/signature.py +0 -0
  25. {drb-2.4.0 → drb-2.4.2}/drb/drivers/file/__init__.py +0 -0
  26. {drb-2.4.0 → drb-2.4.2}/drb/drivers/file/cortex.ttl +0 -0
  27. {drb-2.4.0 → drb-2.4.2}/drb/drivers/file/file.py +0 -0
  28. {drb-2.4.0 → drb-2.4.2}/drb/drivers/xml/__init__.py +0 -0
  29. {drb-2.4.0 → drb-2.4.2}/drb/drivers/xml/cortex.ttl +0 -0
  30. {drb-2.4.0 → drb-2.4.2}/drb/drivers/xml/factory.py +0 -0
  31. {drb-2.4.0 → drb-2.4.2}/drb/drivers/xml/node.py +0 -0
  32. {drb-2.4.0 → drb-2.4.2}/drb/exceptions/core.py +0 -0
  33. {drb-2.4.0 → drb-2.4.2}/drb/exceptions/file.py +0 -0
  34. {drb-2.4.0 → drb-2.4.2}/drb/keyring/bitwarden/__init__.py +0 -0
  35. {drb-2.4.0 → drb-2.4.2}/drb/keyring/bitwarden/bitwarden.py +0 -0
  36. {drb-2.4.0 → drb-2.4.2}/drb/nodes/abstract_node.py +0 -0
  37. {drb-2.4.0 → drb-2.4.2}/drb/nodes/logical_node.py +0 -0
  38. {drb-2.4.0 → drb-2.4.2}/drb/nodes/mutable_node.py +0 -0
  39. {drb-2.4.0 → drb-2.4.2}/drb/nodes/url_node.py +0 -0
  40. {drb-2.4.0 → drb-2.4.2}/drb/signatures/core.py +0 -0
  41. {drb-2.4.0 → drb-2.4.2}/drb/signatures/hierarchy.py +0 -0
  42. {drb-2.4.0 → drb-2.4.2}/drb/signatures/xquery.py +0 -0
  43. {drb-2.4.0 → drb-2.4.2}/drb/topics/dao/__init__.py +0 -0
  44. {drb-2.4.0 → drb-2.4.2}/drb/topics/dao/manager_dao.py +0 -0
  45. {drb-2.4.0 → drb-2.4.2}/drb/topics/dao/rdf_dao.py +0 -0
  46. {drb-2.4.0 → drb-2.4.2}/drb/topics/dao/topic_dao.py +0 -0
  47. {drb-2.4.0 → drb-2.4.2}/drb/topics/dao/topic_schema.yml +0 -0
  48. {drb-2.4.0 → drb-2.4.2}/drb/topics/dao/xml_dao.py +0 -0
  49. {drb-2.4.0 → drb-2.4.2}/drb/topics/file/__init__.py +0 -0
  50. {drb-2.4.0 → drb-2.4.2}/drb/topics/topic.py +0 -0
  51. {drb-2.4.0 → drb-2.4.2}/drb/topics/xml/__init__.py +0 -0
  52. {drb-2.4.0 → drb-2.4.2}/drb/topics/xml/cortex.ttl +0 -0
  53. {drb-2.4.0 → drb-2.4.2}/drb/utils/__init__.py +0 -0
  54. {drb-2.4.0 → drb-2.4.2}/drb/utils/drb_python_script.py +0 -0
  55. {drb-2.4.0 → drb-2.4.2}/drb/utils/plugins.py +0 -0
  56. {drb-2.4.0 → drb-2.4.2}/drb/utils/url_security.py +0 -0
  57. {drb-2.4.0 → drb-2.4.2}/drb/utils/utils.py +0 -0
  58. {drb-2.4.0 → drb-2.4.2}/drb/xquery/XQueryLexer.py +0 -0
  59. {drb-2.4.0 → drb-2.4.2}/drb/xquery/XQueryParser.py +0 -0
  60. {drb-2.4.0 → drb-2.4.2}/drb/xquery/XQueryParserListener.py +0 -0
  61. {drb-2.4.0 → drb-2.4.2}/drb/xquery/XQueryParserVisitor.py +0 -0
  62. {drb-2.4.0 → drb-2.4.2}/drb/xquery/__init__.py +0 -0
  63. {drb-2.4.0 → drb-2.4.2}/drb/xquery/drb_xquery.py +0 -0
  64. {drb-2.4.0 → drb-2.4.2}/drb/xquery/drb_xquery_cmd.py +0 -0
  65. {drb-2.4.0 → drb-2.4.2}/drb/xquery/drb_xquery_context.py +0 -0
  66. {drb-2.4.0 → drb-2.4.2}/drb/xquery/drb_xquery_func.py +0 -0
  67. {drb-2.4.0 → drb-2.4.2}/drb/xquery/drb_xquery_item.py +0 -0
  68. {drb-2.4.0 → drb-2.4.2}/drb/xquery/drb_xquery_res_to_string.py +0 -0
  69. {drb-2.4.0 → drb-2.4.2}/drb/xquery/drb_xquery_utils.py +0 -0
  70. {drb-2.4.0 → drb-2.4.2}/drb/xquery/drb_xquery_variable.py +0 -0
  71. {drb-2.4.0 → drb-2.4.2}/drb/xquery/drb_xquery_visitor.py +0 -0
  72. {drb-2.4.0 → drb-2.4.2}/drb/xquery/exceptions.py +0 -0
  73. {drb-2.4.0 → drb-2.4.2}/drb.egg-info/dependency_links.txt +0 -0
  74. {drb-2.4.0 → drb-2.4.2}/drb.egg-info/entry_points.txt +0 -0
  75. {drb-2.4.0 → drb-2.4.2}/drb.egg-info/not-zip-safe +0 -0
  76. {drb-2.4.0 → drb-2.4.2}/drb.egg-info/requires.txt +0 -0
  77. {drb-2.4.0 → drb-2.4.2}/drb.egg-info/top_level.txt +0 -0
  78. {drb-2.4.0 → drb-2.4.2}/pyproject.toml +0 -0
  79. {drb-2.4.0 → drb-2.4.2}/requirements.txt +0 -0
  80. {drb-2.4.0 → drb-2.4.2}/setup.cfg +0 -0
  81. {drb-2.4.0 → drb-2.4.2}/setup.py +0 -0
  82. {drb-2.4.0 → drb-2.4.2}/tests/test_abstract_node.py +0 -0
  83. {drb-2.4.0 → drb-2.4.2}/tests/test_addon.py +0 -0
  84. {drb-2.4.0 → drb-2.4.2}/tests/test_children_unresolved.py +0 -0
  85. {drb-2.4.0 → drb-2.4.2}/tests/test_cortex_validator.py +0 -0
  86. {drb-2.4.0 → drb-2.4.2}/tests/test_drb_node_list.py +0 -0
  87. {drb-2.4.0 → drb-2.4.2}/tests/test_drb_python_script.py +0 -0
  88. {drb-2.4.0 → drb-2.4.2}/tests/test_event.py +0 -0
  89. {drb-2.4.0 → drb-2.4.2}/tests/test_factory.py +0 -0
  90. {drb-2.4.0 → drb-2.4.2}/tests/test_factory_resolver.py +0 -0
  91. {drb-2.4.0 → drb-2.4.2}/tests/test_implementation_coupling.py +0 -0
  92. {drb-2.4.0 → drb-2.4.2}/tests/test_implementation_manager.py +0 -0
  93. {drb-2.4.0 → drb-2.4.2}/tests/test_keyring_bitwarden.py +0 -0
  94. {drb-2.4.0 → drb-2.4.2}/tests/test_logical_node.py +0 -0
  95. {drb-2.4.0 → drb-2.4.2}/tests/test_manager_dao.py +0 -0
  96. {drb-2.4.0 → drb-2.4.2}/tests/test_manager_get_dao.py +0 -0
  97. {drb-2.4.0 → drb-2.4.2}/tests/test_node_context.py +0 -0
  98. {drb-2.4.0 → drb-2.4.2}/tests/test_node_descendant.py +0 -0
  99. {drb-2.4.0 → drb-2.4.2}/tests/test_path.py +0 -0
  100. {drb-2.4.0 → drb-2.4.2}/tests/test_rdf_dao.py +0 -0
  101. {drb-2.4.0 → drb-2.4.2}/tests/test_rdf_dao_multi_url.py +0 -0
  102. {drb-2.4.0 → drb-2.4.2}/tests/test_rdf_dao_scheme.py +0 -0
  103. {drb-2.4.0 → drb-2.4.2}/tests/test_rdf_dao_signatures.py +0 -0
  104. {drb-2.4.0 → drb-2.4.2}/tests/test_signature_exact.py +0 -0
  105. {drb-2.4.0 → drb-2.4.2}/tests/test_signature_hierarchy.py +0 -0
  106. {drb-2.4.0 → drb-2.4.2}/tests/test_signatures.py +0 -0
  107. {drb-2.4.0 → drb-2.4.2}/tests/test_topic.py +0 -0
  108. {drb-2.4.0 → drb-2.4.2}/tests/test_topic_descriptor_discovery.py +0 -0
  109. {drb-2.4.0 → drb-2.4.2}/tests/test_url_node.py +0 -0
  110. {drb-2.4.0 → drb-2.4.2}/tests/test_url_security.py +0 -0
  111. {drb-2.4.0 → drb-2.4.2}/tests/test_xml_dao.py +0 -0
  112. {drb-2.4.0 → drb-2.4.2}/versioneer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drb
3
- Version: 2.4.0
3
+ Version: 2.4.2
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-26T09:21:52+0000",
11
+ "date": "2026-06-26T17:44:46+0000",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "25b4cf76ffd3fba8d829bfb3b992a9ca4351a815",
15
- "version": "2.4.0"
14
+ "full-revisionid": "d6abeea9ba11991cf33d5cc12fc33405c6032658",
15
+ "version": "2.4.2"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -8,5 +8,6 @@ drb:file
8
8
  drb:category "PROTOCOL" ;
9
9
  rdfs:subClassOf drb:item ;
10
10
  rdfs:label "file" ;
11
+ drb:scheme "file" ;
11
12
  drb:signature [ drb:pythonTest "return node.path.scheme is None or '' == node.path.scheme or 'file' == node.path.scheme" ] ;
12
13
  drb:implementationIdentifier "file" .
@@ -53,14 +53,59 @@ def _create_via_factory(topic: DrbTopic, node: DrbNode) -> DrbNode:
53
53
 
54
54
 
55
55
  def _topic_by_factory(name: str):
56
- """The PROTOCOL topic whose implementationIdentifier == name, or None."""
57
- for topic in ManagerDao().get_drb_topics_by_category(
58
- TopicCategory.PROTOCOL):
56
+ """The topic (any category) whose implementationIdentifier == name.
57
+
58
+ Returns ``None`` when no topic declares that factory."""
59
+ for topic in ManagerDao().get_all_drb_topics():
59
60
  if topic.factory == name:
60
61
  return topic
61
62
  return None
62
63
 
63
64
 
65
+ def _topic_by_scheme_any(scheme: str):
66
+ """The topic (any category) declaring ``scheme`` in its ``drb:scheme``.
67
+
68
+ Unlike :func:`_topic_by_scheme` (PROTOCOL-only, for transport detection),
69
+ this searches every category so a forced token can resolve to a
70
+ CONTAINER/FORMATTING topic. Raises :class:`DrbFactoryException` when two
71
+ distinct topics declare the same scheme (ambiguity)."""
72
+ matches = [t for t in ManagerDao().get_all_drb_topics()
73
+ if scheme in (t.scheme or [])]
74
+ if len(matches) > 1:
75
+ raise DrbFactoryException(
76
+ f'Ambiguous scheme token "{scheme}": '
77
+ f'{[getattr(t, "label", t) for t in matches]}')
78
+ return matches[0] if matches else None
79
+
80
+
81
+ def _resolve_token(token: str):
82
+ """Resolve a forced scheme token to ``(topic, factory)``.
83
+
84
+ Primary: a topic declaring ``token`` in ``drb:scheme`` (any category),
85
+ with its installed factory. Fallback: ``token`` as a driver entry-point
86
+ name (topic via :func:`_topic_by_factory`). Raises
87
+ :class:`DrbFactoryException` when neither resolves or the matched topic's
88
+ factory is not installed."""
89
+ topic = _topic_by_scheme_any(token)
90
+ if topic is not None:
91
+ factory = FactoryLoader().get_factory(topic.factory)
92
+ if factory is None:
93
+ raise DrbFactoryException(
94
+ f"No driver implementation '{topic.factory}' installed for "
95
+ f'forced scheme token "{token}"')
96
+ return topic, factory
97
+ factory = FactoryLoader().get_factory(token)
98
+ if factory is None:
99
+ raise DrbFactoryException(
100
+ f'No driver registered for forced scheme "+{token}"')
101
+ topic = _topic_by_factory(token)
102
+ if topic is None:
103
+ raise DrbFactoryException(
104
+ f'Driver "{token}" is registered but declares no topic; '
105
+ f'cannot classify forced scheme token')
106
+ return topic, factory
107
+
108
+
64
109
  def _topic_by_scheme(scheme: str):
65
110
  """The PROTOCOL topic declaring this scheme (drb:scheme), or None."""
66
111
  for topic in ManagerDao().get_drb_topics_by_category(
@@ -107,35 +152,38 @@ class _DrbFactoryResolver(DrbFactory):
107
152
  return None
108
153
 
109
154
  def __dispatch_scheme(self, node):
110
- """Dispatch a root URL by drb:scheme, then apply a +<name> forced
111
- driver if present. Returns (topic, node), or None when no PROTOCOL
112
- topic declares the base scheme or its factory is not installed
113
- (caller falls back to signature matching)."""
155
+ """Dispatch a root URL by drb:scheme, then apply an ordered stack of
156
+ +<token> forced drivers. Returns (topic, node), or None when no
157
+ PROTOCOL topic declares the base scheme or its factory is not
158
+ installed (caller falls back to signature matching). Raises on an
159
+ empty scheme segment."""
114
160
  scheme = node.path.scheme
115
161
  if not scheme:
116
162
  return None
117
- parts = scheme.split('+', 1)
163
+ parts = scheme.split('+')
118
164
  base = parts[0]
119
- forced = parts[1] if len(parts) > 1 else None
165
+ tokens = parts[1:]
166
+ if base == '' or any(t == '' for t in tokens):
167
+ raise DrbFactoryException(
168
+ f'Invalid scheme "{scheme}": empty segment')
120
169
  base_topic = _topic_by_scheme(base)
121
170
  if base_topic is None:
122
171
  return None
123
172
  base_factory = FactoryLoader().get_factory(base_topic.factory)
124
173
  if base_factory is None:
125
174
  return None
126
- if forced is not None:
127
- # Strip the +forced token so the base factory receives a clean
128
- # <base>:// URL (e.g. https+odata://h/x -> https://h/x).
129
- clean_url = base + node.path.name[len(node.path.scheme):]
175
+ if tokens:
176
+ # Strip every +token so the base factory receives a clean
177
+ # <base>:// URL (https+odata+xml://h/x -> https://h/x).
178
+ clean_url = base + node.path.name[len(scheme):]
130
179
  node = UrlNode(clean_url)
131
180
  created = base_factory.create(node)
132
- if forced is None:
133
- return base_topic, created
134
- forced_factory = FactoryLoader().get_factory(forced)
135
- if forced_factory is None:
136
- raise DrbFactoryException(
137
- f'No driver registered for forced scheme "+{forced}"')
138
- return _topic_by_factory(forced), forced_factory.create(created)
181
+ topic = base_topic
182
+ for token in tokens:
183
+ token_topic, token_factory = _resolve_token(token)
184
+ created = token_factory.create(created)
185
+ topic = token_topic
186
+ return topic, created
139
187
 
140
188
  def __retrieve_container(
141
189
  self, node: DrbNode) -> Tuple[Optional[DrbTopic], DrbNode]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: drb
3
- Version: 2.4.0
3
+ Version: 2.4.2
4
4
  Summary: Data Request Broker
5
5
  Author: GAEL Systems
6
6
  Author-email: drb-python@gael.fr
@@ -100,6 +100,7 @@ tests/test_rdf_dao.py
100
100
  tests/test_rdf_dao_multi_url.py
101
101
  tests/test_rdf_dao_scheme.py
102
102
  tests/test_rdf_dao_signatures.py
103
+ tests/test_scheme_force_impl.py
103
104
  tests/test_signature_exact.py
104
105
  tests/test_signature_hierarchy.py
105
106
  tests/test_signatures.py
@@ -40,3 +40,11 @@ class TestCoreTopicTtl(unittest.TestCase):
40
40
  self.assertTrue(any(s.matches(_Node(scheme='')) for s in sigs))
41
41
  self.assertTrue(any(s.matches(_Node(scheme=None)) for s in sigs))
42
42
  self.assertFalse(any(s.matches(_Node(scheme='http')) for s in sigs))
43
+
44
+ def test_file_topic_declares_file_scheme(self):
45
+ # drb:scheme "file" makes 'file' a routable base scheme for the
46
+ # dispatcher, enabling forced drivers such as ``file+zip://`` on
47
+ # paths whose extension does not match the container signature.
48
+ dao = RDFDao(_FILE_TTL)
49
+ topic = next(t for t in dao.read_all() if t.label == 'file')
50
+ self.assertEqual(['file'], topic.scheme)
@@ -89,6 +89,7 @@ class TestSchemeDispatch(unittest.TestCase):
89
89
  patch.object(resolver_mod, '_topic_by_factory',
90
90
  return_value=forced):
91
91
  md.return_value.get_drb_topics_by_category.return_value = [base]
92
+ md.return_value.get_all_drb_topics.return_value = [base]
92
93
  facs = {'http': _BaseFactory(), 'odata': _ForcedFactory()}
93
94
  gf.side_effect = lambda name: facs.get(name)
94
95
  t, n = r._DrbFactoryResolver__dispatch_scheme(
@@ -104,6 +105,7 @@ class TestSchemeDispatch(unittest.TestCase):
104
105
  with patch.object(resolver_mod, 'ManagerDao') as md, \
105
106
  patch.object(resolver_mod.FactoryLoader, 'get_factory') as gf:
106
107
  md.return_value.get_drb_topics_by_category.return_value = [base]
108
+ md.return_value.get_all_drb_topics.return_value = [base]
107
109
  gf.side_effect = lambda name: (
108
110
  {'http': _BaseFactory()}).get(name)
109
111
  with self.assertRaises(DrbFactoryException):
@@ -121,6 +123,7 @@ class TestSchemeDispatch(unittest.TestCase):
121
123
  patch.object(resolver_mod, '_topic_by_factory',
122
124
  return_value=forced_topic):
123
125
  md.return_value.get_drb_topics_by_category.return_value = [base]
126
+ md.return_value.get_all_drb_topics.return_value = [base]
124
127
  facs = {
125
128
  'http': _RecordingBaseFactory(),
126
129
  'odata': _ForcedFactory(),
@@ -0,0 +1,170 @@
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.nodes.logical_node import DrbLogicalNode
7
+ from drb.exceptions.core import DrbFactoryException
8
+
9
+
10
+ def _topic(factory, scheme, label='t'):
11
+ t = MagicMock()
12
+ t.factory = factory
13
+ t.scheme = scheme
14
+ t.label = label
15
+ return t
16
+
17
+
18
+ class TestTopicBySchemeAny(unittest.TestCase):
19
+ def _call(self, scheme, topics):
20
+ with patch.object(resolver_mod, 'ManagerDao') as md:
21
+ md.return_value.get_all_drb_topics.return_value = topics
22
+ return resolver_mod._topic_by_scheme_any(scheme)
23
+
24
+ def test_returns_topic_any_category(self):
25
+ zip_t = _topic('zip', ['zip'])
26
+ result = self._call('zip', [_topic('aws3', ['s3']), zip_t])
27
+ self.assertIs(result, zip_t)
28
+
29
+ def test_returns_none_when_absent(self):
30
+ self.assertIsNone(self._call('nope', [_topic('aws3', ['s3'])]))
31
+
32
+ def test_raises_on_ambiguity(self):
33
+ a = _topic('f1', ['x'], 'A')
34
+ b = _topic('f2', ['x'], 'B')
35
+ with self.assertRaises(DrbFactoryException):
36
+ self._call('x', [a, b])
37
+
38
+
39
+ class TestResolveToken(unittest.TestCase):
40
+ def test_via_scheme_index(self):
41
+ zip_t = _topic('zip', ['zip'])
42
+ fac = object()
43
+ with patch.object(resolver_mod, '_topic_by_scheme_any',
44
+ return_value=zip_t), \
45
+ patch.object(resolver_mod.FactoryLoader, 'get_factory',
46
+ return_value=fac) as gf:
47
+ topic, factory = resolver_mod._resolve_token('zip')
48
+ self.assertIs(topic, zip_t)
49
+ self.assertIs(factory, fac)
50
+ gf.assert_called_with('zip') # looked up by topic.factory
51
+
52
+ def test_factory_name_fallback(self):
53
+ forced_t = _topic('odata', [])
54
+ fac = object()
55
+ with patch.object(resolver_mod, '_topic_by_scheme_any',
56
+ return_value=None), \
57
+ patch.object(resolver_mod, '_topic_by_factory',
58
+ return_value=forced_t), \
59
+ patch.object(resolver_mod.FactoryLoader, 'get_factory',
60
+ return_value=fac):
61
+ topic, factory = resolver_mod._resolve_token('odata')
62
+ self.assertIs(topic, forced_t)
63
+ self.assertIs(factory, fac)
64
+
65
+ def test_factory_name_fallback_non_protocol_topic(self):
66
+ """Fallback path must accept a non-PROTOCOL (e.g. CONTAINER) topic."""
67
+ container_t = _topic('zip', [], 'ZipContainer')
68
+ fac = object()
69
+ with patch.object(resolver_mod, '_topic_by_scheme_any',
70
+ return_value=None), \
71
+ patch.object(resolver_mod, '_topic_by_factory',
72
+ return_value=container_t), \
73
+ patch.object(resolver_mod.FactoryLoader, 'get_factory',
74
+ return_value=fac):
75
+ topic, factory = resolver_mod._resolve_token('zip')
76
+ self.assertIs(topic, container_t)
77
+ self.assertIs(factory, fac)
78
+
79
+ def test_factory_registered_but_no_topic_raises(self):
80
+ """When factory is registered but _topic_by_factory returns None,
81
+ a clear DrbFactoryException must be raised."""
82
+ fac = object()
83
+ with patch.object(resolver_mod, '_topic_by_scheme_any',
84
+ return_value=None), \
85
+ patch.object(resolver_mod, '_topic_by_factory',
86
+ return_value=None), \
87
+ patch.object(resolver_mod.FactoryLoader, 'get_factory',
88
+ return_value=fac):
89
+ with self.assertRaises(DrbFactoryException):
90
+ resolver_mod._resolve_token('orphan')
91
+
92
+ def test_unknown_raises(self):
93
+ with patch.object(resolver_mod, '_topic_by_scheme_any',
94
+ return_value=None), \
95
+ patch.object(resolver_mod.FactoryLoader, 'get_factory',
96
+ return_value=None):
97
+ with self.assertRaises(DrbFactoryException):
98
+ resolver_mod._resolve_token('nosuch')
99
+
100
+ def test_scheme_topic_factory_not_installed_raises(self):
101
+ zip_t = _topic('zip', ['zip'])
102
+ with patch.object(resolver_mod, '_topic_by_scheme_any',
103
+ return_value=zip_t), \
104
+ patch.object(resolver_mod.FactoryLoader, 'get_factory',
105
+ return_value=None):
106
+ with self.assertRaises(DrbFactoryException):
107
+ resolver_mod._resolve_token('zip')
108
+
109
+
110
+ class _TagFactory(DrbFactory):
111
+ """Records call order via a shared list; returns a tagged node."""
112
+ def __init__(self, tag, order):
113
+ self.tag = tag
114
+ self.order = order
115
+
116
+ def _create(self, node):
117
+ self.order.append(self.tag)
118
+ return DrbLogicalNode(self.tag)
119
+
120
+
121
+ class TestMultiTokenDispatch(unittest.TestCase):
122
+ def test_ordered_stack(self):
123
+ order = []
124
+ base = _topic('http', ['https'])
125
+ oda = _topic('odata', ['odata'])
126
+ xml = _topic('xml', ['xml'])
127
+ tok = {'odata': oda, 'xml': xml}
128
+ facs = {
129
+ 'http': _TagFactory('http', order),
130
+ 'odata': _TagFactory('odata', order),
131
+ 'xml': _TagFactory('xml', order),
132
+ }
133
+ r = resolver_mod._DrbFactoryResolver()
134
+ with patch.object(resolver_mod, '_topic_by_scheme',
135
+ return_value=base), \
136
+ patch.object(resolver_mod, '_topic_by_scheme_any',
137
+ side_effect=lambda s: tok.get(s)), \
138
+ patch.object(resolver_mod.FactoryLoader, 'get_factory',
139
+ side_effect=lambda n: facs.get(n)):
140
+ t, n = r._DrbFactoryResolver__dispatch_scheme(
141
+ DrbLogicalNode('https+odata+xml://host/x'))
142
+ self.assertEqual(order, ['http', 'odata', 'xml'])
143
+ self.assertIs(t, xml)
144
+ self.assertEqual(n.name, 'xml')
145
+
146
+ def test_empty_segment_raises(self):
147
+ r = resolver_mod._DrbFactoryResolver()
148
+ with self.assertRaises(DrbFactoryException):
149
+ r._DrbFactoryResolver__dispatch_scheme(
150
+ DrbLogicalNode('https++xml://host'))
151
+
152
+ def test_unknown_base_returns_none(self):
153
+ r = resolver_mod._DrbFactoryResolver()
154
+ with patch.object(resolver_mod, '_topic_by_scheme',
155
+ return_value=None):
156
+ self.assertIsNone(
157
+ r._DrbFactoryResolver__dispatch_scheme(
158
+ DrbLogicalNode('ftp://host')))
159
+
160
+
161
+ class TestTopicByFactory(unittest.TestCase):
162
+ def test_finds_container_category_topic(self):
163
+ """_topic_by_factory must find topics from any category, not just
164
+ PROTOCOL. Regression for the bug where only PROTOCOL topics were
165
+ searched."""
166
+ zip_t = _topic('zip', ['zip'], 'ZipContainer')
167
+ with patch.object(resolver_mod, 'ManagerDao') as md:
168
+ md.return_value.get_all_drb_topics.return_value = [zip_t]
169
+ result = resolver_mod._topic_by_factory('zip')
170
+ self.assertIs(result, zip_t)
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