Skip to content

Commit ea3e7b0

Browse files
felipemontoyaclaude
andcommitted
feat: addressing feedback on tests and key-presence
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 033fa5a commit ea3e7b0

4 files changed

Lines changed: 29 additions & 4 deletions

File tree

docs/decisions/0001-service-entry-points.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,12 @@ workbench, third-party runtimes that don't override ``service()``) for a
8888
single small change, and gives a hard guarantee: *runtime-provided services
8989
always shadow plugin-provided ones*. A pip package cannot replace or
9090
intercept ``user``, ``field-data``, ``i18n``, or any other service the host
91-
application provides deliberately. Runtimes that override ``service()``
91+
application provides deliberately. "Provided" is decided by key presence in
92+
``_services``, not truthiness: runtimes use an explicit ``None`` to mean
93+
"this service exists but is disabled here" — the Open edX LMS maps
94+
``completion`` to ``None`` for anonymous users, and this library's own test
95+
suite passes ``services={'i18n': None}`` — and a plugin must not resurrect a
96+
service the runtime switched off. Runtimes that override ``service()``
9297
entirely keep that freedom — the fallback only exists in the default path
9398
they opt into by calling ``super().service()``.
9499

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@ in depth and guides developers through the process of creating an XBlock.
2222
plugins
2323
exceptions
2424
fragments
25+
decisions/0001-service-entry-points
2526
xblock-tutorial/index
2627
xblock-utils/index

xblock/runtime.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,9 @@ class under the ``xblock.service.v1`` entry-point group::
454454
455455
Services that the runtime itself provides (via the ``services`` constructor
456456
argument or a ``service()`` override) always take precedence; entry points
457-
are only consulted when the runtime does not offer the requested service.
457+
are only consulted when the runtime has no entry for the requested name.
458+
A runtime entry explicitly set to None counts as provided (it means the
459+
runtime deliberately disabled the service) and is never overridden.
458460
459461
The provider class is instantiated per service request as
460462
``provider_class(runtime=runtime, xblock=block)``, mirroring
@@ -1132,8 +1134,9 @@ def service(self, block, service_name):
11321134
declaration = block.service_declaration(service_name)
11331135
if declaration is None:
11341136
raise NoSuchServiceError(f"Service {service_name!r} was not requested.")
1135-
service = self._services.get(service_name)
1136-
if service is None:
1137+
if service_name in self._services:
1138+
service = self._services[service_name]
1139+
else:
11371140
service = self._load_service_from_entry_point(block, service_name)
11381141
if service is None and declaration == "need":
11391142
raise NoSuchServiceError(f"Service {service_name!r} is not available.")

xblock/test/test_plugin_services.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,22 @@ def test_runtime_service_shadows_plugin_service():
6363
assert block.runtime.service(block, 'ai_extensions') is sentinel
6464

6565

66+
@ServiceProvider.register_temp_plugin(
67+
DummyAIService, identifier='ai_extensions', group='xblock.service.v1',
68+
)
69+
def test_runtime_none_service_disables_plugin_service():
70+
wants_block = make_block(
71+
WantsAIBlock, runtime=TestRuntime(services={'ai_extensions': None}),
72+
)
73+
assert wants_block.runtime.service(wants_block, 'ai_extensions') is None
74+
75+
needs_block = make_block(
76+
NeedsAIBlock, runtime=TestRuntime(services={'ai_extensions': None}),
77+
)
78+
with pytest.raises(NoSuchServiceError):
79+
needs_block.runtime.service(needs_block, 'ai_extensions')
80+
81+
6682
def test_missing_plugin_service_wanted_returns_none():
6783
block = make_block(WantsAIBlock)
6884
assert block.runtime.service(block, 'ai_extensions') is None

0 commit comments

Comments
 (0)