ARCHIVED TRAINING / hynek/svcs
hynek__svcs-140
This is a preserved training example. Membership in the current training wave has not been verified.
Problem statement
`container.get` raises TypeError when factory returns a MagicMock
Calling `Container.get` raises a TypeError when the factory is configured to return a MagicMock. This makes it very inconventient to combine svcs with MagicMock in some cases.
This is demonstrated in the following example:
```python
from unittest.mock import MagicMock
import uuid
import svcs
registry = svcs.Registry()
registry.register_factory(uuid.UUID, lambda: uuid.uuid4())
container = svcs.Container(registry=registry)
def test_example():
registry.register_factory(uuid.UUID, lambda: MagicMock())
container.get(uuid.UUID)
# TypeError: Use `aget()` for async factories.
```
The registered factory for `uuid.UUID` is incorrectly interpreted as an `AbstractAsyncContextManager`, because `MagicMock` will always return `True` on `isinstance` checks.
Under normal circumstances I would be highly skeptical of explicitly checking if something is a `MagicMock` or `AsyncMock` in production code. However, given that the core premise of late-binding with `svcs` is to simplify testing, I think it may be warranted in this case.
In some cases, the error can be circumvented using `spec` on the `MagicMock`:
```
registry.register_factory(uuid.UUID, lambda: MagicMock(spec=uuid.UUID))
```
However, this does not work when the constructor of a class defines additional properties that would be accessible during normal operations. Below is a simplified example from my own code where I wanted per-instance caching for the result of a method call:
```python
class MyClass:
def __init__(self):
self.expensive_operation = cachetools.func.ttl_cache(ttl=60)(self._do_something)
def _expensive_operation(self): ...
registry.register_factory(MyClass, lambda: MagicMock(spec=MyClass))
my_class = container.get(MyClass)
my_class.expensive_operation()
# AttributeError: Mock object has no attribute 'expensive_operation'
```
The following amendment to `Container.get` in `_core.py` resolves the issue and does not break any of the existing tests:
```python
if not isinstance(svc, MagicMock) and (
iscoroutine(svc)
or isinstance(svc, AbstractAsyncContextManager)
):
msg = "Use `aget()` for async factories."
raise TypeError(msg)
```
## Hints:
Moortiii: Yes, this looks reasonable.
A quick, possibly related, question. Shouldn't re-registering a factory invalidate `self._instantiated` on the instance of `Container`? I was just verifying your fix again locally when I tried to do the following:
```python
from unittest.mock import MagicMock
import svcs
class Greeter:
def greet(self, name: str):
return f"Hello, {name}!"
def greeter_factory() -> Greeter:
return Greeter()
registry = svcs.Registry()
container = svcs.Container(registry=registry)
registry.register_factory(Greeter, factory=greeter_factory)
greeter = container.get(Greeter)
print("Without mock:", greeter.greet("Hynek"))
mock = MagicMock()
mock.greet.return_value = "I was mocked!"
registry.register_factory(Greeter, lambda: mock, enter=False)
greeter = container.get(Greeter)
print("With mock:", greeter.greet(name="irrelevant"))
```
I would've expected the result to be:
```
Without mock: Hello, Hynek!
With mock: I was mocked!
```
But the result is actually:
```
Without mock: Hello, Hynek!
With mock: Hello, irrelevant!
```
This is because the call to `.get` caches the return value for `Greeter`, even though we've overwritten the factory afterwards. Is this a bug or am I misunderstanding how `svcs` is intended to work?
Moortiii: Come to think of it, to facilitate temporary overrides in my tests, I initially wrote the following contextmanager:
```python
@contextmanager
def Override(registry: svcs.Registry, overrides: dict[type, Any]):
old_overrides = {}
for dependency_type, value in overrides.items():
if dependency_type in registry._services:
old_overrides[dependency_type] = registry.get_registered_service_for(dependency_type)
if callable(value):
registry.register_factory(dependency_type, value)
else:
registry.register_value(dependency_type, value)
try:
yield
finally:
for dependency_type, original_value in old_overrides.items():
registry._services[dependency_type] = original_value
```
Which would be used as such:
```python
class Greeter:
def greet(self, name):
return f"Hello, {name}!"
def test__override():
registry = svcs.Registry()
container = svcs.Container(registry=registry)
with Override(
registry=registry,
overrides={
Greeter: lambda: stub(greet = lambda name: "The mock says hello!")
}
):
greeter = container.get(Greeter)
print(greeter.greet("does-not-matter"))
```
I quickly ran into issues when I started to override the same type, i.e. `Greeter` across multiple tests with a global registry defined. In a debugger I could clearly see that I was resetting my dependencies after each test. Yet, magically, container would "revert" to the initially defined override for every subsequent test. I guess what was actually happening was that my new override was never ran, because the cached value was always returned directly.
hynek: > Ah, you're absolutely right.
Thanks Claude! ;)
Training membership
opd_e4b_hybrid_v3 opd_e4b_onpolicy_v3 opd_e4b_pure_v6b opd_e4b_pure_v7 opd_e4b_pure_v7b rft_e4b_aligned_v2 rft_v1 rft_v2
Task issues extracted from preserved verified_rft_messages training examples. This is not a verified current RL wave manifest.
Task and snapshot provenance
Snapshot captured 2026-11-12T17:06:59.476467+00:00 · Autoresearch-Arena/gemma-4-developer-agent
{
"datasets": [
"opd_e4b_hybrid_v3",
"opd_e4b_onpolicy_v3",
"opd_e4b_pure_v6b",
"opd_e4b_pure_v7",
"opd_e4b_pure_v7b",
"rft_e4b_aligned_v2",
"rft_v1",
"rft_v2"
],
"examples": 24,
"instance_id": "hynek__svcs-140",
"problem_statement": "`container.get` raises TypeError when factory returns a MagicMock\nCalling `Container.get` raises a TypeError when the factory is configured to return a MagicMock. This makes it very inconventient to combine svcs with MagicMock in some cases.\n\nThis is demonstrated in the following example:\n\n```python\nfrom unittest.mock import MagicMock\nimport uuid\nimport svcs\n\nregistry = svcs.Registry()\nregistry.register_factory(uuid.UUID, lambda: uuid.uuid4())\ncontainer = svcs.Container(registry=registry)\n\ndef test_example():\n registry.register_factory(uuid.UUID, lambda: MagicMock())\n container.get(uuid.UUID)\n # TypeError: Use `aget()` for async factories.\n```\n\nThe registered factory for `uuid.UUID` is incorrectly interpreted as an `AbstractAsyncContextManager`, because `MagicMock` will always return `True` on `isinstance` checks.\n\nUnder normal circumstances I would be highly skeptical of explicitly checking if something is a `MagicMock` or `AsyncMock` in production code. However, given that the core premise of late-binding with `svcs` is to simplify testing, I think it may be warranted in this case.\n\nIn some cases, the error can be circumvented using `spec` on the `MagicMock`:\n\n```\nregistry.register_factory(uuid.UUID, lambda: MagicMock(spec=uuid.UUID))\n```\n\nHowever, this does not work when the constructor of a class defines additional properties that would be accessible during normal operations. Below is a simplified example from my own code where I wanted per-instance caching for the result of a method call:\n\n```python\nclass MyClass:\n def __init__(self):\n self.expensive_operation = cachetools.func.ttl_cache(ttl=60)(self._do_something)\n\n def _expensive_operation(self): ...\n\nregistry.register_factory(MyClass, lambda: MagicMock(spec=MyClass))\n\nmy_class = container.get(MyClass)\nmy_class.expensive_operation()\n# AttributeError: Mock object has no attribute 'expensive_operation'\n```\n\nThe following amendment to `Container.get` in `_core.py` resolves the issue and does not break any of the existing tests:\n\n```python\nif not isinstance(svc, MagicMock) and (\n iscoroutine(svc)\n or isinstance(svc, AbstractAsyncContextManager)\n):\n msg = \"Use `aget()` for async factories.\"\n raise TypeError(msg)\n```\n\n\n## Hints:\nMoortiii: Yes, this looks reasonable.\r\n\r\nA quick, possibly related, question. Shouldn't re-registering a factory invalidate `self._instantiated` on the instance of `Container`? I was just verifying your fix again locally when I tried to do the following:\r\n\r\n```python\r\nfrom unittest.mock import MagicMock\r\nimport svcs\r\n\r\nclass Greeter:\r\n def greet(self, name: str):\r\n return f\"Hello, {name}!\"\r\n\r\ndef greeter_factory() -> Greeter:\r\n return Greeter()\r\n\r\n\r\nregistry = svcs.Registry()\r\ncontainer = svcs.Container(registry=registry)\r\n\r\nregistry.register_factory(Greeter, factory=greeter_factory)\r\ngreeter = container.get(Greeter)\r\nprint(\"Without mock:\", greeter.greet(\"Hynek\"))\r\n\r\nmock = MagicMock()\r\nmock.greet.return_value = \"I was mocked!\"\r\nregistry.register_factory(Greeter, lambda: mock, enter=False)\r\ngreeter = container.get(Greeter)\r\nprint(\"With mock:\", greeter.greet(name=\"irrelevant\"))\r\n``` \r\n\r\nI would've expected the result to be:\r\n\r\n```\r\nWithout mock: Hello, Hynek!\r\nWith mock: I was mocked!\r\n```\r\n\r\nBut the result is actually:\r\n\r\n```\r\nWithout mock: Hello, Hynek!\r\nWith mock: Hello, irrelevant!\r\n```\r\n\r\nThis is because the call to `.get` caches the return value for `Greeter`, even though we've overwritten the factory afterwards. Is this a bug or am I misunderstanding how `svcs` is intended to work?\nMoortiii: Come to think of it, to facilitate temporary overrides in my tests, I initially wrote the following contextmanager:\r\n\r\n```python\r\n@contextmanager\r\ndef Override(registry: svcs.Registry, overrides: dict[type, Any]):\r\n old_overrides = {}\r\n\r\n for dependency_type, value in overrides.items():\r\n if dependency_type in registry._services:\r\n old_overrides[dependency_type] = registry.get_registered_service_for(dependency_type)\r\n\r\n if callable(value):\r\n registry.register_factory(dependency_type, value)\r\n else:\r\n registry.register_value(dependency_type, value)\r\n\r\n try:\r\n yield\r\n finally:\r\n for dependency_type, original_value in old_overrides.items():\r\n registry._services[dependency_type] = original_value\r\n```\r\n\r\nWhich would be used as such:\r\n\r\n```python\r\nclass Greeter:\r\n def greet(self, name):\r\n return f\"Hello, {name}!\"\r\n\r\n\r\ndef test__override():\r\n registry = svcs.Registry()\r\n container = svcs.Container(registry=registry)\r\n\r\n with Override(\r\n registry=registry,\r\n overrides={\r\n Greeter: lambda: stub(greet = lambda name: \"The mock says hello!\")\r\n }\r\n ):\r\n greeter = container.get(Greeter)\r\n print(greeter.greet(\"does-not-matter\"))\r\n```\r\n\r\nI quickly ran into issues when I started to override the same type, i.e. `Greeter` across multiple tests with a global registry defined. In a debugger I could clearly see that I was resetting my dependencies after each test. Yet, magically, container would \"revert\" to the initially defined override for every subsequent test. I guess what was actually happening was that my new override was never ran, because the cached value was always returned directly.\nhynek: > Ah, you're absolutely right.\r\n\r\nThanks Claude! ;)",
"repo": "hynek/svcs",
"source": "v2/experiments/train_data/opd_e4b_hybrid_v3/verified_rft_messages.jsonl",
"source_sha256": "bd326303eb127e8e5e08f03daf97bb88d79d19709d59f3f93f348bc56686a8df"
}{
"captured_at": "2026-11-12T17:06:59.476467+00:00",
"membership": "archived training",
"membership_evidence": "Task issues extracted from preserved verified_rft_messages training examples. This is not a verified current RL wave manifest.",
"name": "Preserved post-training task archive",
"source": "Autoresearch-Arena/gemma-4-developer-agent",
"source_revision": "016496de222aaafca0d4ee4c18efc63ba616f76e",
"sources": [
{
"path": "v2/experiments/train_data/opd_e4b_cispo_v5/verified_rft_messages.jsonl",
"sha256": "a43610ad0dfb6ace95375cfae09000f41afc0338cc925af870a4ade7a9b20c4c"
},
{
"path": "v2/experiments/train_data/opd_e4b_hybrid_v3/verified_rft_messages.jsonl",
"sha256": "bd326303eb127e8e5e08f03daf97bb88d79d19709d59f3f93f348bc56686a8df"
},
{
"path": "v2/experiments/train_data/opd_e4b_onpolicy_v3/verified_rft_messages.jsonl",
"sha256": "960c680e797e823a0d3a22f4b1d5ef60a72971373d703b877340b0c9126aaf68"
},
{
"path": "v2/experiments/train_data/opd_e4b_pure_v6a/verified_rft_messages.jsonl",
"sha256": "f045b7ca5b3883a1e2eaee81e1c02a4a1b53dd7b7b36e98bacb2ba5823a15148"
},
{
"path": "v2/experiments/train_data/opd_e4b_pure_v6b/verified_rft_messages.jsonl",
"sha256": "0f62460a9706eea5e1605255896f191ab00cb5706c5f1f2e1c293e9a718218dc"
},
{
"path": "v2/experiments/train_data/opd_e4b_pure_v7/verified_rft_messages.jsonl",
"sha256": "e37f7bd675322f3f81500b73559d60d5f602ca0537bf19f5cb06332c728c7b72"
},
{
"path": "v2/experiments/train_data/opd_e4b_pure_v7b/verified_rft_messages.jsonl",
"sha256": "b864bdc8695183433eef6e31cb8d33fad44ba3aded2170c1ade4ad5b2944ce23"
},
{
"path": "v2/experiments/train_data/opd_e4b_self_audit_v4/verified_rft_messages.jsonl",
"sha256": "0026023745afb8f3dc1c176dc7f7171157b4ad5a66656b524af218e8e19ceee6"
},
{
"path": "v2/experiments/train_data/rft_e4b_aligned_v2/verified_rft_messages.jsonl",
"sha256": "92a4515fee9d152b0d4c510ee3ce4caaaffe2003ba7584469f8027f174aab18c"
},
{
"path": "v2/experiments/train_data/rft_v1/verified_rft_messages.jsonl",
"sha256": "a42453ccd7ac7680b0a03865e8e7080bc8bf74c8d9d193b440c441ed46f8512b"
},
{
"path": "v2/experiments/train_data/rft_v2/verified_rft_messages.jsonl",
"sha256": "18f4b3bd7e651f83c6acab479c9475e3fa50dc652a384f309e97146983ac85ba"
}
]
}