|
1 | | -# chuk_tool_processor/registry/provider.py |
2 | 1 | """ |
3 | | -Global access to *the* tool-registry instance. |
| 2 | +Global access to *the* tool registry instance. |
| 3 | +
|
| 4 | +There are two public faces: |
| 5 | +
|
| 6 | +1. **Module helpers** |
| 7 | + • `get_registry()` lazily instantiates a default `InMemoryToolRegistry` |
| 8 | + and memoises it in the module-level variable ``_REGISTRY``. |
| 9 | + • `set_registry()` lets callers replace or reset that singleton. |
| 10 | +
|
| 11 | +2. **`ToolRegistryProvider` shim** |
| 12 | + Earlier versions exposed a static wrapper. Tests rely on being able to |
| 13 | + monkey-patch the *module-level* factory and to clear the cached instance |
| 14 | + by setting `ToolRegistryProvider._registry = None`. We therefore keep a |
| 15 | + **separate class-level cache** (`_registry`) and call the *current* |
| 16 | + module-level `get_registry()` **only when the cache is empty**. |
| 17 | +
|
| 18 | +The contract verified by the test-suite is: |
| 19 | +
|
| 20 | +* The module-level factory is invoked **exactly once** per fresh cache. |
| 21 | +* `ToolRegistryProvider.set_registry(obj)` overrides subsequent retrievals. |
| 22 | +* `ToolRegistryProvider.set_registry(None)` resets the cache so the next |
| 23 | + `get_registry()` call invokes (and honours any monkey-patched) factory. |
4 | 24 | """ |
5 | 25 | from __future__ import annotations |
6 | 26 |
|
7 | 27 | from typing import Optional |
8 | 28 |
|
9 | 29 | from .interface import ToolRegistryInterface |
10 | | -from .providers.memory import InMemoryToolRegistry # default impl |
| 30 | +from .providers.memory import InMemoryToolRegistry |
11 | 31 |
|
12 | | -# ─────────────────────────────────────────────────────────────────────────── |
| 32 | +# --------------------------------------------------------------------------- # |
| 33 | +# Module-level singleton used by the helper functions |
| 34 | +# --------------------------------------------------------------------------- # |
13 | 35 | _REGISTRY: Optional[ToolRegistryInterface] = None |
14 | | -# ─────────────────────────────────────────────────────────────────────────── |
| 36 | +# --------------------------------------------------------------------------- # |
| 37 | + |
| 38 | + |
| 39 | +def _default_registry() -> ToolRegistryInterface: |
| 40 | + """Create the default in-memory registry.""" |
| 41 | + return InMemoryToolRegistry() |
15 | 42 |
|
16 | 43 |
|
17 | 44 | def get_registry() -> ToolRegistryInterface: |
18 | | - """Return the single, process-wide registry instance.""" |
| 45 | + """ |
| 46 | + Return the process-wide registry, creating it on first use. |
| 47 | +
|
| 48 | + This function *may* be monkey-patched in tests; call it via |
| 49 | + ``globals()["get_registry"]()`` if you need the latest binding. |
| 50 | + """ |
19 | 51 | global _REGISTRY |
20 | 52 | if _REGISTRY is None: |
21 | | - _REGISTRY = InMemoryToolRegistry() |
| 53 | + _REGISTRY = _default_registry() |
22 | 54 | return _REGISTRY |
23 | 55 |
|
24 | 56 |
|
25 | | -def set_registry(registry: ToolRegistryInterface) -> None: |
26 | | - """Swap in another implementation (tests, multi-process, …).""" |
| 57 | +def set_registry(registry: ToolRegistryInterface | None) -> None: |
| 58 | + """ |
| 59 | + Replace or clear the global registry. |
| 60 | +
|
| 61 | + Passing ``None`` resets the singleton so that the next `get_registry()` |
| 62 | + call recreates it (useful in tests). |
| 63 | + """ |
27 | 64 | global _REGISTRY |
28 | 65 | _REGISTRY = registry |
29 | 66 |
|
30 | 67 |
|
31 | | -# ------------------------------------------------------------------------- # |
32 | | -# 🔌 backward-compat shim – lets old `from … import ToolRegistryProvider` |
33 | | -# statements keep working without changes. |
34 | | -# ------------------------------------------------------------------------- # |
35 | | -class ToolRegistryProvider: # noqa: D401 |
36 | | - """Compatibility wrapper around the new helpers.""" |
| 68 | +# --------------------------------------------------------------------------- # |
| 69 | +# Back-compat shim used by legacy import paths and the test-suite |
| 70 | +# --------------------------------------------------------------------------- # |
| 71 | +class ToolRegistryProvider: # noqa: D401 |
| 72 | + """Legacy static wrapper retaining historical semantics.""" |
37 | 73 |
|
| 74 | + # The test-suite directly mutates this attribute, so we keep it. |
| 75 | + _registry: Optional[ToolRegistryInterface] = None |
| 76 | + |
| 77 | + # ------------------------ public API ------------------------ # |
38 | 78 | @staticmethod |
39 | | - def get_registry() -> ToolRegistryInterface: # same signature |
40 | | - return get_registry() |
| 79 | + def get_registry() -> ToolRegistryInterface: |
| 80 | + """ |
| 81 | + Return the cached instance or, if absent, call the *current* |
| 82 | + module-level `get_registry()` exactly once to populate it. |
| 83 | + """ |
| 84 | + if ToolRegistryProvider._registry is None: |
| 85 | + # Honour any runtime monkey-patching of the factory. |
| 86 | + ToolRegistryProvider._registry = globals()["get_registry"]() |
| 87 | + return ToolRegistryProvider._registry |
41 | 88 |
|
42 | 89 | @staticmethod |
43 | | - def set_registry(registry: ToolRegistryInterface) -> None: |
44 | | - set_registry(registry) |
| 90 | + def set_registry(registry: ToolRegistryInterface | None) -> None: |
| 91 | + """ |
| 92 | + Override the cached registry. |
| 93 | +
|
| 94 | + * If ``registry`` is an object, all subsequent `get_registry()` |
| 95 | + calls return it without touching the factory. |
| 96 | + * If ``registry`` is ``None``, the cache is cleared so the next |
| 97 | + `get_registry()` call invokes the (possibly patched) factory. |
| 98 | + """ |
| 99 | + ToolRegistryProvider._registry = registry |
0 commit comments