diff --git a/CHANGELOG.md b/CHANGELOG.md index 49c9c62..99e9215 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ You can find our backwards-compatibility policy [here](https://github.com/hynek/ - Python 3.13 support. +- `svcs.Registry` now implements an `__iter__` method that allows to iterate over its registered services. + [#106](https://github.com/hynek/svcs/pull/106) + ### Removed diff --git a/docs/core-concepts.md b/docs/core-concepts.md index 4203681..9402215 100644 --- a/docs/core-concepts.md +++ b/docs/core-concepts.md @@ -363,7 +363,7 @@ You can see that the datetime factory and the str value have both been registere .. module:: svcs .. autoclass:: Registry() - :members: register_factory, register_value, close, aclose, __contains__ + :members: register_factory, register_value, close, aclose, __contains__, __iter__ .. autoclass:: Container() :members: get, aget, get_abstract, aget_abstract, register_local_factory, register_local_value, close, aclose, get_pings, __contains__ @@ -371,5 +371,7 @@ You can see that the datetime factory and the str value have both been registere .. autoclass:: ServicePing() :members: name, ping, aping, is_async +.. autoclass:: RegisteredService() + .. autoclass:: svcs.exceptions.ServiceNotFoundError ``` diff --git a/src/svcs/_core.py b/src/svcs/_core.py index 4ed8a51..200ada9 100644 --- a/src/svcs/_core.py +++ b/src/svcs/_core.py @@ -8,7 +8,7 @@ import logging import warnings -from collections.abc import Awaitable, Callable +from collections.abc import Awaitable, Callable, Iterator from contextlib import ( AbstractAsyncContextManager, AbstractContextManager, @@ -48,6 +48,26 @@ def _full_name(obj: object) -> str: @attrs.frozen class RegisteredService: + """ + A recipe for creating a service. + + .. warning:: + + Strictly read-only. + + Attributes: + svc_type: The type under which the type has been registered. + + factory: Callable that creates the service. + + takes_container: + Whether the factory takes a container as its first argument. + + enter: Whether context managers returned by the factory are entered. + + ping: See :ref:`health`. + """ + svc_type: type factory: Callable = attrs.field(hash=False) takes_container: bool @@ -161,6 +181,15 @@ def __contains__(self, svc_type: type) -> bool: """ return svc_type in self._services + def __iter__(self) -> Iterator[RegisteredService]: + """ + Returns: + An iterator over registered services. + + .. versionadded:: 24.2.0 + """ + return iter(self._services.values()) + def __enter__(self) -> Registry: return self diff --git a/tests/test_registry.py b/tests/test_registry.py index bc6d01c..a11c266 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -281,6 +281,20 @@ def test_contains(self, registry): assert Service in registry assert AnotherService not in registry + def test_iterate(self, registry): + """ + It's possible to iterate over the registered services. + """ + registry.register_factory(Service, Service) + registry.register_factory(AnotherService, AnotherService) + + assert { + svcs.RegisteredService(Service, Service, False, True, None), + svcs.RegisteredService( + AnotherService, AnotherService, False, True, None + ), + } == {rs for rs in registry} # noqa: C416 -- explicit on purpose + def test_gc_warning(self, recwarn): """ If a registry is gc'ed with pending cleanups, a warning is raised.