From 0bb778c79a2a39b81041b7e848fb1a7a35f3fd08 Mon Sep 17 00:00:00 2001 From: Josh Orr Date: Tue, 13 Dec 2022 12:31:55 -0700 Subject: [PATCH] fix: fixes a bug where if you activate a dependency before the thread-root context is created. --- README.md | 2 +- pyproject.toml | 2 +- tests/test_dependency.py | 21 +++++++++++++++++++++ xinject/context.py | 40 +++++++++++++++++++--------------------- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 6d88269..56e093c 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ in an easy to understand and self-documenting way. # Documentation -**[📄 Detailed Documentation](https://xyngular.github.io/py-xinject/latest/)** | **[🐍 PyPi](https://pypi.org/project/py-xinject/)** +**[📄 Detailed Documentation](https://xyngular.github.io/py-xinject/latest/)** | **[🐍 PyPi](https://pypi.org/project/xinject/)** # Install diff --git a/pyproject.toml b/pyproject.toml index 3e00bfc..0cfa3b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ description = "Lazy dependency injection." authors = ["Josh Orr "] packages = [{include = "xinject"}] readme = "README.md" -license = "MIT-0" +license = "MIT" repository = "https://github.com/xyngular/py-xinject" keywords = ["dependency", "injection", "lazy", "resource"] classifiers = [ diff --git a/tests/test_dependency.py b/tests/test_dependency.py index f9a6bf9..e54bc61 100644 --- a/tests/test_dependency.py +++ b/tests/test_dependency.py @@ -1,6 +1,9 @@ +import dataclasses + import pytest as pytest from xinject import CurrentDependencyProxy, XContext, Dependency +from xinject.context import _setup_blank_app_and_thread_root_contexts_globals from xinject.dependency import DependencyPerThread @@ -99,3 +102,21 @@ def thread_func(): # Check to see if the thread safe/unsafe dependencies worked correctly. assert thread_out_sharable == "3" assert thread_out_nonsharable == "a" + + +def test_dep_as_decorator(): + @dataclasses.dataclass + class MyDep(Dependency): + some_value: str = 'my-value' + + # This ensures we don't have any thread-root context's, + # which is important for testing to make sure a specific bug is fixed. + # (the pytest-plugin already creates a blank thread-root-context for us, so we clear it again here) + _setup_blank_app_and_thread_root_contexts_globals() + + @MyDep(some_value='new-value') + def my_func(): + return MyDep.grab().some_value + + assert my_func() == 'new-value' + diff --git a/xinject/context.py b/xinject/context.py index a689f7e..30e01c3 100644 --- a/xinject/context.py +++ b/xinject/context.py @@ -438,23 +438,21 @@ def _make_current_and_get_reset_token( Only returns None when this is True. """ - if is_thread_root_context: - # For debugging purposes, so you know which one was truly the thread-root context - # if there is another root-like-context made (for unit tests). - self._is_root_context_for_thread = True - assert self._parent is Default, "See my methods doc-comment for details." if is_app_root_context: self._is_root_context_for_app = True assert self._parent is _TreatAsRootParent, "See my methods doc-comment for details." + elif is_thread_root_context: + # For debugging purposes, so you know which one was truly the thread-root context + # if there is another root-like-context made (for unit tests). + self._is_root_context_for_thread = True + + # We set parent to use app-root-context if we are the thread-root-context. + self._parent = _app_root_context if self._parent is Default: # Side Note: This will be `None` if we are the first XContext on current thread. - self._parent = XContext._current_without_creating_thread_root() - - if self._parent is None: - # We set parent to use app-root-context if we are the thread-root-context. - self._parent = _app_root_context + self._parent = XContext.grab() elif self._parent is _TreatAsRootParent: # When you activate a context who should be treated as root, we have a None # parent and we set `_originally_passed_none_for_parent` to False @@ -522,17 +520,17 @@ def parent(self) -> Optional["XContext"]: if parent in (_TreatAsRootParent, None): return None - raise XInjectError( - f"Somehow we have a XContext that is not active " - f"(ie: ever activated via decorator `@` or via `with` or activating a " - f"`xinject.dependency.Dependency` via `@` or `with`) but has a specific parent " - f"(ie: not None or _TreatAsRootParent or Default). " - f"This indicates some sort of programming error or bug with XContext. " - f"A XContext should only have an explicit parent if they have " - f"been activated via `@` or `with` or activating a `xinject.dependency.Dependency` " - f"via `@` or `with` " - f"(side note: you can look at XContext._is_active for more internal details)." - ) + # raise XInjectError( + # f"Somehow we have a XContext that is not active " + # f"(ie: ever activated via decorator `@` or via `with` or activating a " + # f"`xinject.dependency.Dependency` via `@` or `with`) but has a specific parent " + # f"(ie: not None or _TreatAsRootParent or Default). " + # f"This indicates some sort of programming error or bug with XContext. " + # f"A XContext should only have an explicit parent if they have " + # f"been activated via `@` or `with` or activating a `xinject.dependency.Dependency` " + # f"via `@` or `with` " + # f"(side note: you can look at XContext._is_active for more internal details)." + # ) @property def name(self) -> str: