Filtering
Filtering controls which registration is selected when multiple registrations match a service type.
from clean_ioc import Container, DependencySettings, Tag
from clean_ioc.registration_filters import has_tag, with_name
import clean_ioc.node_filters as nf
Visual model
Filtering happens during registration selection inside resolution. At a high level:
flowchart TD
A[Resolve service type] --> B[Collect registrations]
B --> C[Apply default or custom registration filter]
C --> D[Apply parent_node_filter checks]
D --> E[Take first match from registration order]
E --> F[Build instance and continue resolving children]
Default behavior
By default, resolve(...) uses unnamed registrations only.
container = Container()
container.register(int, instance=1)
container.register(int, instance=2, name="Two")
print(container.resolve(int)) # 1
print(container.resolve(int, filter=with_name("Two"))) # 2
Selection order is LIFO (last registration checked first):
flowchart LR
R3[Register named int Two] --> Q
R2[Register unnamed int one] --> Q
Q[Resolve int using default filter] --> U[Unnamed registrations only]
U --> Pick[Pick unnamed registration]
Name and tag filters
container = Container()
container.register(str, instance="prod", tags=[Tag("env", "prod")])
container.register(str, instance="dev", tags=[Tag("env", "dev")])
value = container.resolve(str, filter=has_tag("env", "dev"))
print(value) # dev
Top-down filtering with dependency_config
Top-down means the parent registration constrains which child registration is allowed for a given argument.
flowchart TD
G[Greeter registration] --> M[Dependency config for message]
M --> F1[Filter by name hello]
F1 --> S1[Select string registration named hello]
class Greeter:
def __init__(self, message: str):
self.message = message
container = Container()
container.register(str, instance="Hello", name="hello")
container.register(str, instance="Goodbye", name="bye")
container.register(
Greeter,
name="hello_greeter",
dependency_config={"message": DependencySettings(filter=with_name("hello"))},
)
container.register(
Greeter,
name="bye_greeter",
dependency_config={"message": DependencySettings(filter=with_name("bye"))},
)
print(container.resolve(Greeter, filter=with_name("hello_greeter")).message)
print(container.resolve(Greeter, filter=with_name("bye_greeter")).message)
Bottom-up filtering with parent_node_filter
Bottom-up means the child registration decides if it is eligible by inspecting the current parent node.
flowchart TD
P[Resolve NeedsB] --> AReq[Needs dependency A]
AReq --> RB[Registration mapping A to B with parent filter NeedsB]
AReq --> RC[Registration mapping A to C with parent filter NeedsC]
RB --> OK[Filter passes]
RC --> NO[Filter fails]
OK --> BInst[Build B instance]
class A:
pass
class B(A):
pass
class C(A):
pass
class NeedsB:
def __init__(self, a: A):
self.a = a
class NeedsC:
def __init__(self, a: A):
self.a = a
container = Container()
container.register(A, B, parent_node_filter=nf.implementation_type_is(NeedsB))
container.register(A, C, parent_node_filter=nf.implementation_type_is(NeedsC))
container.register(NeedsB)
container.register(NeedsC)
print(type(container.resolve(NeedsB).a).__name__) # B
print(type(container.resolve(NeedsC).a).__name__) # C