Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-injector for openSUSE:Factory
checked in at 2023-12-08 22:31:57
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-injector (Old)
and /work/SRC/openSUSE:Factory/.python-injector.new.25432 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-injector"
Fri Dec 8 22:31:57 2023 rev:10 rq:1131714 version:0.21.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-injector/python-injector.changes
2022-09-29 18:14:53.899436112 +0200
+++
/work/SRC/openSUSE:Factory/.python-injector.new.25432/python-injector.changes
2023-12-08 22:32:13.136365269 +0100
@@ -1,0 +2,12 @@
+Thu Dec 7 22:09:22 UTC 2023 - Dirk Müller <[email protected]>
+
+- update to 0.21.0:
+ * Improved the documentation, thanks to jonathanmach and Jakub
+ Wilk
+ * Fixed a thread-safety regression
+ * Improved the type annotations, thanks to David Pärsson
+ * Fixed singleton scope behavior with parent/child injectors,
+ thanks to David Pärsson
+ * Stopped using a deprecated test function, thanks to ljnsn
+
+-------------------------------------------------------------------
Old:
----
0.20.1.tar.gz
New:
----
0.21.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-injector.spec ++++++
--- /var/tmp/diff_new_pack.c9WQQK/_old 2023-12-08 22:32:13.720386757 +0100
+++ /var/tmp/diff_new_pack.c9WQQK/_new 2023-12-08 22:32:13.720386757 +0100
@@ -1,7 +1,7 @@
#
# spec file for package python-injector
#
-# Copyright (c) 2022 SUSE LLC
+# Copyright (c) 2023 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -19,7 +19,7 @@
%define skip_python2 1
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-injector
-Version: 0.20.1
+Version: 0.21.0
Release: 0
Summary: Python dependency injection framework, inspired by Guice
License: BSD-3-Clause
++++++ 0.20.1.tar.gz -> 0.21.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.20.1/.github/workflows/ci.yml
new/injector-0.21.0/.github/workflows/ci.yml
--- old/injector-0.20.1/.github/workflows/ci.yml 2022-08-17
01:03:10.000000000 +0200
+++ new/injector-0.21.0/.github/workflows/ci.yml 2023-07-27
02:44:06.000000000 +0200
@@ -8,7 +8,7 @@
strategy:
matrix:
os: [ubuntu-latest]
- python-version: [3.7, 3.8, 3.9, "3.10", "pypy3.7", "pypy3.8",
"pypy3.9"]
+ python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "pypy3.7", "pypy3.8",
"pypy3.9", "pypy3.10"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
@@ -17,7 +17,7 @@
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
- pip install --upgrade -r requirements.txt -r requirements-dev.txt
+ pip install --upgrade -r requirements-dev.txt
pip install .
- name: Run tests
run: |
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.20.1/.gitignore
new/injector-0.21.0/.gitignore
--- old/injector-0.20.1/.gitignore 2022-08-17 01:03:10.000000000 +0200
+++ new/injector-0.21.0/.gitignore 2023-07-27 02:44:06.000000000 +0200
@@ -1,3 +1,8 @@
+/.*
+
+!/.gitignore
+!/.github
+
.cache/
__pycache__/
docs/_build/
@@ -9,4 +14,3 @@
coverage.xml
/dist/
/injector.egg-info/
-/.coverage
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.20.1/CHANGES new/injector-0.21.0/CHANGES
--- old/injector-0.20.1/CHANGES 2022-08-17 01:03:10.000000000 +0200
+++ new/injector-0.21.0/CHANGES 2023-07-27 02:44:06.000000000 +0200
@@ -1,6 +1,15 @@
Injector Change Log
===================
+0.21.0
+------
+
+- Improved the documentation, thanks to jonathanmach and Jakub Wilk
+- Fixed a thread-safety regression
+- Improved the type annotations, thanks to David Pärsson
+- Fixed singleton scope behavior with parent/child injectors, thanks to David
Pärsson
+- Stopped using a deprecated test function, thanks to ljnsn
+
0.20.1
------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.20.1/MANIFEST.in
new/injector-0.21.0/MANIFEST.in
--- old/injector-0.20.1/MANIFEST.in 2022-08-17 01:03:10.000000000 +0200
+++ new/injector-0.21.0/MANIFEST.in 2023-07-27 02:44:06.000000000 +0200
@@ -1,5 +1,6 @@
include *.py
include *.toml
+include requirements-dev.in
include *.txt
include CHANGES
include COPYING
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.20.1/README.md
new/injector-0.21.0/README.md
--- old/injector-0.20.1/README.md 2022-08-17 01:03:10.000000000 +0200
+++ new/injector-0.21.0/README.md 2023-07-27 02:44:06.000000000 +0200
@@ -7,7 +7,7 @@
Introduction
------------
-While dependency injection is easy to do in Python due to its support for
keyword arguments, the ease with which objects can be mocked and its dynamic
nature, a framework for assisting in this process can remove a lot of
boiler-plate from larger applications. That's where Injector can help. It
automatically and transitively provides dependencies for you. As an added
benefit, Injector encourages nicely compartmentalised code through the use of
:ref:`modules <module>`.
+While dependency injection is easy to do in Python due to its support for
keyword arguments, the ease with which objects can be mocked and its dynamic
nature, a framework for assisting in this process can remove a lot of
boiler-plate from larger applications. That's where Injector can help. It
automatically and transitively provides dependencies for you. As an added
benefit, Injector encourages nicely compartmentalised code through the use of
modules.
If you're not sure what dependency injection is or you'd like to learn more
about it see:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.20.1/docs/index.rst
new/injector-0.21.0/docs/index.rst
--- old/injector-0.20.1/docs/index.rst 2022-08-17 01:03:10.000000000 +0200
+++ new/injector-0.21.0/docs/index.rst 2023-07-27 02:44:06.000000000 +0200
@@ -50,6 +50,8 @@
a different configuration and each with different objects in different
scopes. Code like this
won't work for this very reason::
+ # This will NOT work:
+
class MyClass:
@inject
def __init__(self, t: SomeType):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.20.1/docs/scopes.rst
new/injector-0.21.0/docs/scopes.rst
--- old/injector-0.20.1/docs/scopes.rst 2022-08-17 01:03:10.000000000 +0200
+++ new/injector-0.21.0/docs/scopes.rst 2023-07-27 02:44:06.000000000 +0200
@@ -24,6 +24,8 @@
def provide_thing(self) -> Thing:
return Thing()
+If using hierarchies of injectors, classes decorated with `@singleton` will be
created by and bound to the parent/ancestor injector closest to the root that
can provide all of its dependencies.
+
Implementing new Scopes
```````````````````````
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.20.1/injector/__init__.py
new/injector-0.21.0/injector/__init__.py
--- old/injector-0.20.1/injector/__init__.py 2022-08-17 01:03:10.000000000
+0200
+++ new/injector-0.21.0/injector/__init__.py 2023-07-27 02:44:06.000000000
+0200
@@ -61,7 +61,7 @@
__author__ = 'Alec Thomas <[email protected]>'
-__version__ = '0.20.1'
+__version__ = '0.21.0'
__version_tag__ = ''
log = logging.getLogger('injector')
@@ -254,7 +254,7 @@
raise NotImplementedError # pragma: no cover
-class ClassProvider(Provider):
+class ClassProvider(Provider, Generic[T]):
"""Provides instances from a given class, created using an Injector."""
def __init__(self, cls: Type[T]) -> None:
@@ -264,7 +264,7 @@
return injector.create_object(self._cls)
-class CallableProvider(Provider):
+class CallableProvider(Provider, Generic[T]):
"""Provides something using a callable.
The callable is called every time new value is requested from the provider.
@@ -305,7 +305,7 @@
return '%s(%r)' % (type(self).__name__, self._callable)
-class InstanceProvider(Provider):
+class InstanceProvider(Provider, Generic[T]):
"""Provide a specific instance.
::
@@ -379,6 +379,13 @@
return _get_origin(_punch_through_alias(self.interface)) in {dict,
list}
+@private
+class ImplicitBinding(Binding):
+ """A binding that was created implicitly by auto-binding."""
+
+ pass
+
+
_InstallableModuleType = Union[Callable[['Binder'], None], 'Module',
Type['Module']]
@@ -392,7 +399,9 @@
_bindings: Dict[type, Binding]
@private
- def __init__(self, injector: 'Injector', auto_bind: bool = True, parent:
'Binder' = None) -> None:
+ def __init__(
+ self, injector: 'Injector', auto_bind: bool = True, parent:
Optional['Binder'] = None
+ ) -> None:
"""Create a new Binder.
:param injector: Injector we are binding for.
@@ -460,7 +469,7 @@
self,
interface: Type[List[T]],
to: Union[List[T], Callable[..., List[T]], Provider[List[T]]],
- scope: Union[Type['Scope'], 'ScopeDecorator'] = None,
+ scope: Union[Type['Scope'], 'ScopeDecorator', None] = None,
) -> None: # pragma: no cover
pass
@@ -469,12 +478,12 @@
self,
interface: Type[Dict[K, V]],
to: Union[Dict[K, V], Callable[..., Dict[K, V]], Provider[Dict[K, V]]],
- scope: Union[Type['Scope'], 'ScopeDecorator'] = None,
+ scope: Union[Type['Scope'], 'ScopeDecorator', None] = None,
) -> None: # pragma: no cover
pass
def multibind(
- self, interface: type, to: Any, scope: Union['ScopeDecorator',
Type['Scope']] = None
+ self, interface: type, to: Any, scope: Union['ScopeDecorator',
Type['Scope'], None] = None
) -> None:
"""Creates or extends a multi-binding.
@@ -555,7 +564,7 @@
instance(self)
def create_binding(
- self, interface: type, to: Any = None, scope: Union['ScopeDecorator',
Type['Scope']] = None
+ self, interface: type, to: Any = None, scope: Union['ScopeDecorator',
Type['Scope'], None] = None
) -> Binding:
provider = self.provider_for(interface, to)
scope = scope or getattr(to or interface, '__scope__', NoScope)
@@ -643,12 +652,18 @@
# The special interface is added here so that requesting a special
# interface with auto_bind disabled works
if self._auto_bind or self._is_special_interface(interface):
- binding = self.create_binding(interface)
+ binding = ImplicitBinding(*self.create_binding(interface))
self._bindings[interface] = binding
return binding, self
raise UnsatisfiedRequirement(None, interface)
+ def has_binding_for(self, interface: type) -> bool:
+ return interface in self._bindings
+
+ def has_explicit_binding_for(self, interface: type) -> bool:
+ return self.has_binding_for(interface) and not
isinstance(self._bindings[interface], ImplicitBinding)
+
def _is_special_interface(self, interface: type) -> bool:
# "Special" interfaces are ones that you cannot bind yourself but
# you can request them (for example you cannot bind
ProviderOf(SomeClass)
@@ -782,10 +797,25 @@
try:
return self._context[key]
except KeyError:
- provider = InstanceProvider(provider.get(self.injector))
+ instance = self._get_instance(key, provider, self.injector)
+ provider = InstanceProvider(instance)
self._context[key] = provider
return provider
+ def _get_instance(self, key: Type[T], provider: Provider[T], injector:
'Injector') -> T:
+ if injector.parent and not
injector.binder.has_explicit_binding_for(key):
+ try:
+ return self._get_instance_from_parent(key, provider,
injector.parent)
+ except (CallError, UnsatisfiedRequirement):
+ pass
+ return provider.get(injector)
+
+ def _get_instance_from_parent(self, key: Type[T], provider: Provider[T],
parent: 'Injector') -> T:
+ singleton_scope_binding, _ = parent.binder.get_binding(type(self))
+ singleton_scope = singleton_scope_binding.provider.get(parent)
+ provider = singleton_scope.get(key, provider)
+ return provider.get(parent)
+
singleton = ScopeDecorator(SingletonScope)
@@ -829,7 +859,7 @@
% (function.__name__, type(self), e)
) from e
return_type = annotations['return']
- binding = function.__func__.__binding__ = Binding(
+ binding = cast(Any, function.__func__).__binding__ =
Binding(
interface=return_type, provider=binding.provider,
scope=binding.scope
)
bind_method = binder.multibind if binding.is_multibinding()
else binder.bind
@@ -864,9 +894,9 @@
def __init__(
self,
- modules: Union[_InstallableModuleType,
Iterable[_InstallableModuleType]] = None,
+ modules: Union[_InstallableModuleType,
Iterable[_InstallableModuleType], None] = None,
auto_bind: bool = True,
- parent: 'Injector' = None,
+ parent: Optional['Injector'] = None,
) -> None:
# Stack of keys currently being injected. Used to detect circular
# dependencies.
@@ -896,7 +926,8 @@
def _log_prefix(self) -> str:
return '>' * (len(self._stack) + 1) + ' '
- def get(self, interface: Type[T], scope: Union[ScopeDecorator,
Type[Scope]] = None) -> T:
+ @synchronized(lock)
+ def get(self, interface: Type[T], scope: Union[ScopeDecorator,
Type[Scope], None] = None) -> T:
"""Get an instance of the given interface.
.. note::
@@ -940,7 +971,8 @@
log.debug(
'%sInjector.get(%r, scope=%r) using %r', self._log_prefix,
interface, scope, binding.provider
)
- result = scope_instance.get(interface, binding.provider).get(self)
+ provider_instance = scope_instance.get(interface, binding.provider)
+ result = provider_instance.get(self)
log.debug('%s -> %r', self._log_prefix, result)
return result
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.20.1/injector_test.py
new/injector-0.21.0/injector_test.py
--- old/injector-0.20.1/injector_test.py 2022-08-17 01:03:10.000000000
+0200
+++ new/injector-0.21.0/injector_test.py 2023-07-27 02:44:06.000000000
+0200
@@ -294,6 +294,141 @@
a1 = injector1.get(A)
a2 = injector1.get(A)
assert a1.b is a2.b
+ assert a1 is not a2
+
+
+def test_injecting_an_auto_bound_decorated_singleton_class():
+ class A:
+ @inject
+ def __init__(self, b: SingletonB):
+ self.b = b
+
+ injector1 = Injector()
+ a1 = injector1.get(A)
+ a2 = injector1.get(A)
+ assert a1.b is a2.b
+ assert a1 is not a2
+
+
+def
test_a_decorated_singleton_is_shared_between_parent_and_child_injectors_when_parent_creates_it_first():
+ parent_injector = Injector()
+
+ child_injector = parent_injector.create_child_injector()
+
+ assert parent_injector.get(SingletonB) is child_injector.get(SingletonB)
+
+
+def
test_a_decorated_singleton_is_shared_between_parent_and_child_injectors_when_child_creates_it_first():
+ parent_injector = Injector()
+
+ child_injector = parent_injector.create_child_injector()
+
+ assert child_injector.get(SingletonB) is parent_injector.get(SingletonB)
+
+
+# Test for https://github.com/python-injector/injector/issues/207
+def test_a_decorated_singleton_is_shared_among_child_injectors():
+ parent_injector = Injector()
+
+ child_injector_1 = parent_injector.create_child_injector()
+ child_injector_2 = parent_injector.create_child_injector()
+
+ assert child_injector_1.get(SingletonB) is child_injector_2.get(SingletonB)
+
+
+def test_a_decorated_singleton_should_not_override_explicit_binds():
+ parent_injector = Injector()
+
+ child_injector = parent_injector.create_child_injector()
+ grand_child_injector = child_injector.create_child_injector()
+
+ bound_singleton = SingletonB()
+ child_injector.binder.bind(SingletonB, to=bound_singleton)
+
+ assert parent_injector.get(SingletonB) is not bound_singleton
+ assert child_injector.get(SingletonB) is bound_singleton
+ assert grand_child_injector.get(SingletonB) is bound_singleton
+
+
+def
test_binding_a_singleton_to_a_child_injector_does_not_affect_the_parent_injector():
+ parent_injector = Injector()
+
+ child_injector = parent_injector.create_child_injector()
+ child_injector.binder.bind(EmptyClass, scope=singleton)
+
+ assert child_injector.get(EmptyClass) is child_injector.get(EmptyClass)
+ assert child_injector.get(EmptyClass) is not
parent_injector.get(EmptyClass)
+ assert parent_injector.get(EmptyClass) is not
parent_injector.get(EmptyClass)
+
+
+def test_a_decorated_singleton_should_not_override_a_child_provider():
+ parent_injector = Injector()
+
+ provided_instance = SingletonB()
+
+ class MyModule(Module):
+ @provider
+ def provide_name(self) -> SingletonB:
+ return provided_instance
+
+ child_injector = parent_injector.create_child_injector([MyModule])
+
+ assert child_injector.get(SingletonB) is provided_instance
+ assert parent_injector.get(SingletonB) is not provided_instance
+ assert parent_injector.get(SingletonB) is parent_injector.get(SingletonB)
+
+
+# Test for https://github.com/python-injector/injector/issues/207
+def
test_a_decorated_singleton_is_created_as_close_to_the_root_where_dependencies_fulfilled():
+ class NonInjectableD:
+ @inject
+ def __init__(self, required) -> None:
+ self.required = required
+
+ @singleton
+ class SingletonC:
+ @inject
+ def __init__(self, d: NonInjectableD):
+ self.d = d
+
+ parent_injector = Injector()
+
+ child_injector_1 = parent_injector.create_child_injector()
+
+ child_injector_2 = parent_injector.create_child_injector()
+ child_injector_2_1 = child_injector_2.create_child_injector()
+
+ provided_d = NonInjectableD(required=True)
+ child_injector_2.binder.bind(NonInjectableD, to=provided_d)
+
+ assert child_injector_2_1.get(SingletonC) is
child_injector_2.get(SingletonC)
+ assert child_injector_2.get(SingletonC).d is provided_d
+
+ with pytest.raises(CallError):
+ parent_injector.get(SingletonC)
+
+ with pytest.raises(CallError):
+ child_injector_1.get(SingletonC)
+
+
+def
test_a_bound_decorated_singleton_is_created_as_close_to_the_root_where_it_exists_when_auto_bind_is_disabled():
+ parent_injector = Injector(auto_bind=False)
+
+ child_injector_1 = parent_injector.create_child_injector(auto_bind=False)
+
+ child_injector_2 = parent_injector.create_child_injector(auto_bind=False)
+ child_injector_2_1 =
child_injector_2.create_child_injector(auto_bind=False)
+
+ child_injector_2.binder.bind(SingletonB)
+
+ assert child_injector_2_1.get(SingletonB) is
child_injector_2_1.get(SingletonB)
+ assert child_injector_2_1.get(SingletonB) is
child_injector_2.get(SingletonB)
+
+ with pytest.raises(UnsatisfiedRequirement):
+ parent_injector.get(SingletonB)
+
+ with pytest.raises(UnsatisfiedRequirement):
+ child_injector_1.get(SingletonB)
def test_threadlocal():
@@ -627,7 +762,6 @@
def test_custom_scope():
-
injector = Injector([RequestModule()], auto_bind=False)
with pytest.raises(UnsatisfiedRequirement):
@@ -819,7 +953,7 @@
class TestThreadSafety:
- def setup(self):
+ def setup_method(self):
self.event = threading.Event()
def configure(binder):
@@ -1433,6 +1567,30 @@
assert injector.get(Data).name == 'data'
+def test_binder_does_not_have_a_binding_for_an_unbound_type():
+ injector = Injector()
+ assert not injector.binder.has_binding_for(int)
+ assert not injector.binder.has_explicit_binding_for(int)
+
+
+def test_binder_has_binding_for_explicitly_bound_type():
+ def configure(binder):
+ binder.bind(int, to=123)
+
+ injector = Injector([configure])
+ assert injector.binder.has_binding_for(int)
+ assert injector.binder.has_explicit_binding_for(int)
+
+
+def test_binder_has_implicit_binding_for_implicitly_bound_type():
+ injector = Injector()
+
+ injector.get(int)
+
+ assert injector.binder.has_binding_for(int)
+ assert not injector.binder.has_explicit_binding_for(int)
+
+
def test_get_bindings():
def function1(a: int) -> None:
pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.20.1/requirements-dev.in
new/injector-0.21.0/requirements-dev.in
--- old/injector-0.20.1/requirements-dev.in 1970-01-01 01:00:00.000000000
+0100
+++ new/injector-0.21.0/requirements-dev.in 2023-07-27 02:44:06.000000000
+0200
@@ -0,0 +1,14 @@
+# Our direct dependencies used in development/CI.
+#
+# We generate requirements-dev.txt from this file by running
+#
+# pip install -r requirements-dev.in && pip freeze > requirements-dev.txt
+#
+# and then modifying the file manually to restrict black and mypy to CPython
+
+pytest
+pytest-cov>=2.5.1
+mypy;implementation_name=="cpython"
+black;implementation_name=="cpython"
+check-manifest
+typing_extensions>=3.7.4;python_version<"3.9"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.20.1/requirements-dev.txt
new/injector-0.21.0/requirements-dev.txt
--- old/injector-0.20.1/requirements-dev.txt 2022-08-17 01:03:10.000000000
+0200
+++ new/injector-0.21.0/requirements-dev.txt 2023-07-27 02:44:06.000000000
+0200
@@ -1,6 +1,18 @@
-pytest
-pytest-cov>=2.5.1
-dataclasses;python_version<"3.7"
-mypy;implementation_name=="cpython"
-black;implementation_name=="cpython"
-check-manifest
+black==23.3.0;implementation_name=="cpython"
+build==0.10.0
+check-manifest==0.49
+click==8.1.3
+coverage==7.2.7
+exceptiongroup==1.1.1
+iniconfig==2.0.0
+mypy==1.4.1;implementation_name=="cpython"
+mypy-extensions==1.0.0
+packaging==23.1
+pathspec==0.11.1
+platformdirs==3.8.0
+pluggy==1.2.0
+pyproject_hooks==1.0.0
+pytest==7.4.0
+pytest-cov==4.1.0
+tomli==2.0.1
+typing_extensions==4.7.0