Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
98c91ff
fix: resolve postponed injection annotations
xelandernt Jul 12, 2026
fa0f380
fix: bind injected arguments by signature
xelandernt Jul 12, 2026
e0fdd6d
feat: selector context registration.
xelandernt Jul 13, 2026
e629cda
Merge remote-tracking branch 'origin/main' into selector-contexts
xelandernt Jul 14, 2026
9b7693c
Merge remote-tracking branch 'origin/main' into selector-contexts
xelandernt Jul 14, 2026
212b1c9
Merge remote-tracking branch 'origin/main' into selector-contexts
xelandernt Jul 17, 2026
39323c6
Merge remote-tracking branch 'origin/main' into selector-contexts
xelandernt Jul 27, 2026
f0afbe3
plan: plan for implementation.
xelandernt Jul 27, 2026
9da6b4b
feat: add generic provider resolution contract
xelandernt Jul 28, 2026
9461023
test: prove dynamic provider resolution contract
xelandernt Jul 28, 2026
987e5ac
test: lock down selector resolution semantics
xelandernt Jul 28, 2026
66d866b
test: cover dynamic injection surfaces
xelandernt Jul 28, 2026
282690c
test: assert selector public resolution contract
xelandernt Jul 28, 2026
6d9d874
docs: explain dynamic provider resolution lifecycle
xelandernt Jul 28, 2026
b9cdc68
refactor: remove obsolete context order cache
xelandernt Jul 28, 2026
ee1ed72
perf: preserve static injection fast path
xelandernt Jul 28, 2026
51d2ef1
docs: remove the main decisions change.
xelandernt Jul 28, 2026
05ebd18
docs: removed selector change.
xelandernt Jul 28, 2026
16287d8
misc: removed plan.md
xelandernt Jul 28, 2026
b94dfb8
docs: explain resolution dependency hooks
xelandernt Jul 28, 2026
5ac184c
docs: document provider context traversal
xelandernt Jul 28, 2026
b3ac2cd
perf: minimize static injection branching
xelandernt Jul 28, 2026
d011d56
misc: add site to gitignore
xelandernt Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ dist/
.venv
uv.lock
.agents
site
21 changes: 19 additions & 2 deletions tests/providers/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,25 @@ def test_register_with_mixed_items() -> None:
assert parent in child_1._children, "Expected child_1._children to contain parent"


def test_get_resolution_dependencies_registers_provider_arguments() -> None:
dependency = DummyProvider()
provider = Singleton(lambda value: value, dependency)

resolution_dependencies = provider.get_resolution_dependencies()

assert resolution_dependencies == frozenset({dependency})
assert isinstance(resolution_dependencies, frozenset)


async def test_default_resolution_contexts_have_no_runtime_dependencies() -> None:
provider = DummyProvider()

async with provider.resolution_context() as async_dependencies:
assert async_dependencies == ()
with provider.resolution_context_sync() as sync_dependencies:
assert sync_dependencies == ()


def test_invalidate_scope_init_order_handles_duplicate_descendants() -> None:
root = DummyProvider()
left = DummyProvider()
Expand All @@ -100,13 +119,11 @@ def test_invalidate_scope_init_order_handles_duplicate_descendants() -> None:
right.add_child_provider(shared)

for provider in (root, left, right, shared):
provider._scope_context_init_order = ()
provider._scope_init_order = ()

root._invalidate_scope_init_order()

for provider in (root, left, right, shared):
assert provider._scope_context_init_order is None
assert provider._scope_init_order is None


Expand Down
39 changes: 39 additions & 0 deletions tests/providers/test_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,45 @@ async def test_selector_with_provider_selector_async() -> None:
assert (await StringProviderSelectorContainer.selector.resolve()) == "Provider 1"


def test_selector_exposes_only_its_key_provider_as_static_dependency() -> None:
def _selector_key() -> typing.Iterator[str]: # pragma: no cover
yield "selected"

selector_key = providers.ContextResource(_selector_key)
selected = providers.Object("value")
selector = providers.Selector(selector_key, selected=selected)

assert selector.get_resolution_dependencies() == frozenset({selector_key})
assert selector.get_resolution_dependencies() == frozenset({selector_key})

selector._deregister_arguments()

assert selector.get_resolution_dependencies() == frozenset({selector_key})


def test_selector_resolution_context_exposes_and_pins_selected_provider() -> None:
expected_selection_count = 2
selected_key = "one"
selection_count = 0
one = providers.Object("one")
two = providers.Object("two")

def _select() -> str:
nonlocal selection_count
selection_count += 1
return selected_key

selector = providers.Selector(_select, one=one, two=two)

with selector.resolution_context_sync() as dependencies:
assert dependencies == (one,)
selected_key = "two"
assert selector.resolve_sync() == "one"

assert selector.resolve_sync() == "two"
assert selection_count == expected_selection_count


class InvalidSelectorContainer(BaseContainer):
selector = providers.Selector(
None, # type: ignore[arg-type]
Expand Down
Loading
Loading