From 359d33b362466680ceda4fc66a7c0a444fa6b13d Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 20 Jun 2026 17:33:44 +0300 Subject: [PATCH 1/6] chore: enrich PyPI metadata (keywords, classifiers, project urls) Co-Authored-By: Claude Opus 4.8 (1M context) --- pyproject.toml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0cee046..acf5ac9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,17 @@ [project] name = "that-depends" -description = "Simple Dependency Injection framework" +description = "Simple, typed dependency-injection framework for Python" authors = [ { name = "Artur Shiriev", email = "me@shiriev.ru" }, ] readme = "README.md" requires-python = ">=3.10,<4" license = "MIT" -keywords = ["di", "dependency injector", "ioc-container", "mocks", "python"] +keywords = ["dependency-injection", "di", "ioc-container", "scopes", "mocks", "python"] classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -28,8 +31,11 @@ faststream = [ ] [project.urls] -repository = "https://github.com/modern-python/that-depends" -docs = "https://that-depends.modern-python.org" +Homepage = "https://that-depends.modern-python.org" +Documentation = "https://that-depends.modern-python.org" +Repository = "https://github.com/modern-python/that-depends" +Issues = "https://github.com/modern-python/that-depends/issues" +Changelog = "https://github.com/modern-python/that-depends/releases" [dependency-groups] dev = [ From 9989ca3b05c56e7c9015667d11b0b4ec8b99c752 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 20 Jun 2026 17:38:43 +0300 Subject: [PATCH 2/6] chore: drop deprecated License classifier (SPDX license key already set) Co-Authored-By: Claude Opus 4.8 (1M context) --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index acf5ac9..53926cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,6 @@ keywords = ["dependency-injection", "di", "ioc-container", "scopes", "mocks", "p classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", From d0cc497872fe60bb62512435be1da2a630ab598d Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 20 Jun 2026 17:49:59 +0300 Subject: [PATCH 3/6] docs: sharpen README headline (simple, typed) Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a1ce16..2ec5d06 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ [![Context7](https://img.shields.io/badge/Context7-docs-blue)](https://context7.com/modern-python/that-depends) [![llms.txt](https://img.shields.io/badge/llms.txt-green)](https://that-depends.modern-python.org/llms.txt) -Dependency injection framework for Python. +Simple, typed dependency injection framework for Python. It is production-ready and gives you the following: - Simple async-first DI framework with IOC-container. From c99191cf85b6c082db22f69f530028f5bf111f49 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 20 Jun 2026 18:04:22 +0300 Subject: [PATCH 4/6] fix: pyrefly type error in _invalidate_scope_init_order; de-flake thread-local test - annotate stack as list[AbstractProvider[Any]] so .extend(provider._children) (a set[AbstractProvider[Any]]) type-checks under pyrefly - test_thread_local_singleton_different_threads used random.randint(1,100) as the factory, so two of ten threads could draw the same value and fail spuriously; use threading.get_ident (unique per thread) and assert all results are distinct Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/providers/test_local_singleton.py | 16 +++++++++++----- that_depends/providers/base.py | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/providers/test_local_singleton.py b/tests/providers/test_local_singleton.py index 4f92dda..237f360 100644 --- a/tests/providers/test_local_singleton.py +++ b/tests/providers/test_local_singleton.py @@ -71,11 +71,17 @@ async def test_thread_local_singleton_reuses_instance_created_while_waiting_on_l def test_thread_local_singleton_different_threads() -> None: """Test that different threads receive different instances.""" - provider = ThreadLocalSingleton(_factory) - results = [] + # Use the thread identity as the factory so each thread produces a value that is + # unique by construction; a random factory could collide across threads and make + # this assertion flaky even when thread-local isolation works correctly. + provider = ThreadLocalSingleton(threading.get_ident) + results: list[int] = [] + results_lock = threading.Lock() def resolve_in_thread() -> None: - results.append(provider.resolve_sync()) + value = provider.resolve_sync() + with results_lock: + results.append(value) number_of_threads = 10 @@ -86,8 +92,8 @@ def resolve_in_thread() -> None: for thread in threads: thread.join() - assert len(results) == number_of_threads, "Test failed: Expected results from two threads." - assert results[0] != results[1], "Thread-local failed: Instances across threads should differ." + assert len(results) == number_of_threads, "Expected one result per thread." + assert len(set(results)) == number_of_threads, "Thread-local failed: each thread should get its own instance." def test_thread_local_singleton_override() -> None: diff --git a/that_depends/providers/base.py b/that_depends/providers/base.py index 7adc02d..31f5982 100644 --- a/that_depends/providers/base.py +++ b/that_depends/providers/base.py @@ -158,7 +158,7 @@ def _deregister(self, candidates: typing.Iterable[typing.Any]) -> None: self._invalidate_scope_init_order() def _invalidate_scope_init_order(self) -> None: - stack = [self] + stack: list[AbstractProvider[typing.Any]] = [self] visited: set[AbstractProvider[typing.Any]] = set() while stack: From 274768132a39757fab85fad728b6937cec9780fb Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 20 Jun 2026 18:16:10 +0300 Subject: [PATCH 5/6] ci: retrigger checks (stale PR merge ref) Co-Authored-By: Claude Opus 4.8 (1M context) From cad9cf54f1ebed6b23b8b9a4af7964360373e96c Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 20 Jun 2026 18:23:25 +0300 Subject: [PATCH 6/6] test: make thread-local test deterministic via itertools.count threading.get_ident is unreliable as the factory: thread ids are recycled once a thread exits, so on a fast runner where the 10 threads run near-sequentially every thread observes the same recycled id and set(results) collapses to 1. Use an atomic itertools.count so each per-thread factory call yields a distinct value regardless of scheduling. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/providers/test_local_singleton.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tests/providers/test_local_singleton.py b/tests/providers/test_local_singleton.py index 237f360..af9384d 100644 --- a/tests/providers/test_local_singleton.py +++ b/tests/providers/test_local_singleton.py @@ -1,4 +1,5 @@ import asyncio +import itertools import random import threading import time @@ -71,10 +72,18 @@ async def test_thread_local_singleton_reuses_instance_created_while_waiting_on_l def test_thread_local_singleton_different_threads() -> None: """Test that different threads receive different instances.""" - # Use the thread identity as the factory so each thread produces a value that is - # unique by construction; a random factory could collide across threads and make - # this assertion flaky even when thread-local isolation works correctly. - provider = ThreadLocalSingleton(threading.get_ident) + # Each factory call returns a distinct value from an atomic counter, so the test is + # deterministic: the ThreadLocalSingleton calls the factory once per thread, yielding + # one unique value per thread. A random factory could collide and a thread-id factory + # is unreliable because thread ids are recycled once a thread exits; itertools.count + # avoids both. The sleep keeps the threads overlapping so this exercises real concurrency. + counter = itertools.count() + + def factory() -> int: + time.sleep(0.01) + return next(counter) + + provider = ThreadLocalSingleton(factory) results: list[int] = [] results_lock = threading.Lock()