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 2022-09-29 18:13:53
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-injector (Old)
and /work/SRC/openSUSE:Factory/.python-injector.new.2275 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-injector"
Thu Sep 29 18:13:53 2022 rev:9 rq:1006876 version:0.20.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-injector/python-injector.changes
2022-01-10 23:54:23.136845479 +0100
+++
/work/SRC/openSUSE:Factory/.python-injector.new.2275/python-injector.changes
2022-09-29 18:14:53.899436112 +0200
@@ -1,0 +2,15 @@
+Thu Sep 29 02:41:09 UTC 2022 - Yogalakshmi Arunachalam <[email protected]>
+
+- Update to 0.20.1
+ - Added support for PEP 604 union types (Python 3.10+), thanks to David
P??rsson
+ - Fixed building with pypandoc 1.8+, thanks to S??ren Fuglede J??rgensen
+
+- Update to 0.20.0
+ - Fixed handling of Union combined with Annotated, thanks to Tobias Nilsson
+ - Fixed AssitedBuilder/child Injector interaction, thanks to Erik Cederberg
+ - Made get_bindings() and injections work even if a injectee's return type
+ annotation is a forward reference that can't be resolved
+ Backwards incompatible:
+ - Dropped Python 3.6 support
+
+-------------------------------------------------------------------
Old:
----
0.19.0.tar.gz
New:
----
0.20.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-injector.spec ++++++
--- /var/tmp/diff_new_pack.2Y5nQR/_old 2022-09-29 18:14:54.503437296 +0200
+++ /var/tmp/diff_new_pack.2Y5nQR/_new 2022-09-29 18:14:54.511437312 +0200
@@ -19,7 +19,7 @@
%define skip_python2 1
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-injector
-Version: 0.19.0
+Version: 0.20.1
Release: 0
Summary: Python dependency injection framework, inspired by Guice
License: BSD-3-Clause
++++++ 0.19.0.tar.gz -> 0.20.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.19.0/.github/workflows/ci.yml
new/injector-0.20.1/.github/workflows/ci.yml
--- old/injector-0.19.0/.github/workflows/ci.yml 2021-12-20
23:18:57.000000000 +0100
+++ new/injector-0.20.1/.github/workflows/ci.yml 2022-08-17
01:03:10.000000000 +0200
@@ -8,11 +8,11 @@
strategy:
matrix:
os: [ubuntu-latest]
- python-version: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3]
+ python-version: [3.7, 3.8, 3.9, "3.10", "pypy3.7", "pypy3.8",
"pypy3.9"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.19.0/CHANGES new/injector-0.20.1/CHANGES
--- old/injector-0.19.0/CHANGES 2021-12-20 23:18:57.000000000 +0100
+++ new/injector-0.20.1/CHANGES 2022-08-17 01:03:10.000000000 +0200
@@ -1,6 +1,24 @@
Injector Change Log
===================
+0.20.1
+------
+
+- Added support for PEP 604 union types (Python 3.10+), thanks to David
P??rsson
+- Fixed building with pypandoc 1.8+, thanks to S??ren Fuglede J??rgensen
+
+0.20.0
+------
+
+- Fixed handling of Union combined with Annotated, thanks to Tobias Nilsson
+- Fixed AssitedBuilder/child Injector interaction, thanks to Erik Cederberg
+- Made get_bindings() and injections work even if a injectee's return type
+ annotation is a forward reference that can't be resolved
+
+Backwards incompatible:
+
+- Dropped Python 3.6 support
+
0.19.0
------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.19.0/README.md
new/injector-0.20.1/README.md
--- old/injector-0.19.0/README.md 2021-12-20 23:18:57.000000000 +0100
+++ new/injector-0.20.1/README.md 2022-08-17 01:03:10.000000000 +0200
@@ -72,7 +72,7 @@
* Documentation: https://injector.readthedocs.org
* Change log: https://injector.readthedocs.io/en/latest/changelog.html
-Injector works with CPython 3.6+ and PyPy 3 implementing Python 3.6+.
+Injector works with CPython 3.7+ and PyPy 3 implementing Python 3.7+.
A Quick Example
---------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.19.0/injector/__init__.py
new/injector-0.20.1/injector/__init__.py
--- old/injector-0.19.0/injector/__init__.py 2021-12-20 23:18:57.000000000
+0100
+++ new/injector-0.20.1/injector/__init__.py 2022-08-17 01:03:10.000000000
+0200
@@ -46,40 +46,22 @@
except ImportError:
from typing_extensions import NoReturn
-HAVE_ANNOTATED = sys.version_info >= (3, 7, 0)
-
# This is a messy, type-wise, because we not only have two potentially
conflicting imports here
-# but we also define our own versions in the else block in case we operate on
Python 3.6
-# which didn't get Annotated support in get_type_hints(). The easiest way to
make mypy
-# happy here is to tell it the versions from typing_extensions are canonical.
Since this
-# typing_extensions import is only for mypy it'll work even without
typing_extensions actually
-# installed so all's good.
+# The easiest way to make mypy happy here is to tell it the versions from
typing_extensions are
+# canonical. Since this typing_extensions import is only for mypy it'll work
even without
+# typing_extensions actually installed so all's good.
if TYPE_CHECKING:
from typing_extensions import _AnnotatedAlias, Annotated, get_type_hints
-elif HAVE_ANNOTATED:
+else:
# Ignoring errors here as typing_extensions stub doesn't know about those
things yet
try:
from typing import _AnnotatedAlias, Annotated, get_type_hints
except ImportError:
from typing_extensions import _AnnotatedAlias, Annotated,
get_type_hints
-else:
-
- class Annotated:
- pass
-
- from typing import get_type_hints as _get_type_hints
-
- def get_type_hints(
- obj: Callable[..., Any],
- globalns: Optional[Dict[str, Any]] = None,
- localns: Optional[Dict[str, Any]] = None,
- include_extras: bool = False,
- ) -> Dict[str, Any]:
- return _get_type_hints(obj, globalns, localns)
__author__ = 'Alec Thomas <[email protected]>'
-__version__ = '0.19.0'
+__version__ = '0.20.1'
__version_tag__ = ''
log = logging.getLogger('injector')
@@ -119,86 +101,85 @@
_inject_marker = object()
_noinject_marker = object()
-if HAVE_ANNOTATED:
- InjectT = TypeVar('InjectT')
- Inject = Annotated[InjectT, _inject_marker]
- """An experimental way to declare injectable dependencies utilizing a `PEP
593`_ implementation
- in Python 3.9 and backported to Python 3.7+ in `typing_extensions`.
+InjectT = TypeVar('InjectT')
+Inject = Annotated[InjectT, _inject_marker]
+"""An experimental way to declare injectable dependencies utilizing a `PEP
593`_ implementation
+in Python 3.9 and backported to Python 3.7+ in `typing_extensions`.
- Those two declarations are equivalent::
+Those two declarations are equivalent::
- @inject
- def fun(t: SomeType) -> None:
- pass
+ @inject
+ def fun(t: SomeType) -> None:
+ pass
- def fun(t: Inject[SomeType]) -> None:
- pass
+ def fun(t: Inject[SomeType]) -> None:
+ pass
- The advantage over using :func:`inject` is that if you have some
noninjectable parameters
- it may be easier to spot what are they. Those two are equivalent::
+The advantage over using :func:`inject` is that if you have some noninjectable
parameters
+it may be easier to spot what are they. Those two are equivalent::
- @inject
- @noninjectable('s')
- def fun(t: SomeType, s: SomeOtherType) -> None:
- pass
+ @inject
+ @noninjectable('s')
+ def fun(t: SomeType, s: SomeOtherType) -> None:
+ pass
- def fun(t: Inject[SomeType], s: SomeOtherType) -> None:
- pass
+ def fun(t: Inject[SomeType], s: SomeOtherType) -> None:
+ pass
- .. seealso::
+.. seealso::
- Function :func:`get_bindings`
- A way to inspect how various injection declarations interact with
each other.
+ Function :func:`get_bindings`
+ A way to inspect how various injection declarations interact with each
other.
- .. versionadded:: 0.18.0
- .. note:: Requires Python 3.7+.
- .. note::
+.. versionadded:: 0.18.0
+.. note:: Requires Python 3.7+.
+.. note::
- If you're using mypy you need the version 0.750 or newer to fully
type-check code using this
- construct.
+ If you're using mypy you need the version 0.750 or newer to fully
type-check code using this
+ construct.
- .. _PEP 593: https://www.python.org/dev/peps/pep-0593/
- .. _typing_extensions: https://pypi.org/project/typing-extensions/
- """
+.. _PEP 593: https://www.python.org/dev/peps/pep-0593/
+.. _typing_extensions: https://pypi.org/project/typing-extensions/
+"""
- NoInject = Annotated[InjectT, _noinject_marker]
- """An experimental way to declare noninjectable dependencies utilizing a
`PEP 593`_ implementation
- in Python 3.9 and backported to Python 3.7+ in `typing_extensions`.
+NoInject = Annotated[InjectT, _noinject_marker]
+"""An experimental way to declare noninjectable dependencies utilizing a `PEP
593`_ implementation
+in Python 3.9 and backported to Python 3.7+ in `typing_extensions`.
- Since :func:`inject` declares all function's parameters to be injectable
there needs to be a way
- to opt out of it. This has been provided by :func:`noninjectable` but
`noninjectable` suffers from
- two issues:
+Since :func:`inject` declares all function's parameters to be injectable there
needs to be a way
+to opt out of it. This has been provided by :func:`noninjectable` but
`noninjectable` suffers from
+two issues:
- * You need to repeat the parameter name
- * The declaration may be relatively distance in space from the actual
parameter declaration, thus
- hindering readability
+* You need to repeat the parameter name
+* The declaration may be relatively distance in space from the actual
parameter declaration, thus
+ hindering readability
- `NoInject` solves both of those concerns, for example (those two
declarations are equivalent)::
+`NoInject` solves both of those concerns, for example (those two declarations
are equivalent)::
- @inject
- @noninjectable('b')
- def fun(a: TypeA, b: TypeB) -> None:
- pass
+ @inject
+ @noninjectable('b')
+ def fun(a: TypeA, b: TypeB) -> None:
+ pass
- @inject
- def fun(a: TypeA, b: NoInject[TypeB]) -> None:
- pass
+ @inject
+ def fun(a: TypeA, b: NoInject[TypeB]) -> None:
+ pass
- .. seealso::
+.. seealso::
- Function :func:`get_bindings`
- A way to inspect how various injection declarations interact with
each other.
+ Function :func:`get_bindings`
+ A way to inspect how various injection declarations interact with each
other.
- .. versionadded:: 0.18.0
- .. note:: Requires Python 3.7+.
- .. note::
+.. versionadded:: 0.18.0
+.. note:: Requires Python 3.7+.
+.. note::
- If you're using mypy you need the version 0.750 or newer to fully
type-check code using this
- construct.
+ If you're using mypy you need the version 0.750 or newer to fully
type-check code using this
+ construct.
- .. _PEP 593: https://www.python.org/dev/peps/pep-0593/
- .. _typing_extensions: https://pypi.org/project/typing-extensions/
- """
+.. _PEP 593: https://www.python.org/dev/peps/pep-0593/
+.. _typing_extensions: https://pypi.org/project/typing-extensions/
+"""
def reraise(original: Exception, exception: Exception, maximum_frames: int =
1) -> NoReturn:
@@ -651,8 +632,9 @@
def get_binding(self, interface: type) -> Tuple[Binding, 'Binder']:
is_scope = isinstance(interface, type) and issubclass(interface, Scope)
+ is_assisted_builder = _is_specialization(interface, AssistedBuilder)
try:
- return self._get_binding(interface, only_this_binder=is_scope)
+ return self._get_binding(interface, only_this_binder=is_scope or
is_assisted_builder)
except (KeyError, UnsatisfiedRequirement):
if is_scope:
scope = interface
@@ -683,7 +665,7 @@
# We need to special-case Annotated as its __origin__ behaves differently
than
# other typing generic classes. See
https://github.com/python/typing/pull/635
# for some details.
- if HAVE_ANNOTATED and generic_class is Annotated and isinstance(cls,
_AnnotatedAlias):
+ if generic_class is Annotated and isinstance(cls, _AnnotatedAlias):
return True
if not hasattr(cls, '__origin__'):
@@ -1166,10 +1148,37 @@
pass
+# See a comment in _infer_injected_bindings() for why this is useful.
+class _NoReturnAnnotationProxy:
+ def __init__(self, callable: Callable) -> None:
+ self.callable = callable
+
+ def __getattribute__(self, name: str) -> Any:
+ # get_type_hints() uses quite complex logic to determine the
namespaces using which
+ # any forward references should be resolved. Instead of mirroring this
logic here
+ # let's just take the easy way out and forward all attribute access to
the original
+ # callable except for the annotations ??? we want to filter them.
+ callable = object.__getattribute__(self, 'callable')
+ if name == '__annotations__':
+ annotations = callable.__annotations__
+ return {name: value for (name, value) in annotations.items() if
name != 'return'}
+ return getattr(callable, name)
+
+
def _infer_injected_bindings(callable: Callable, only_explicit_bindings: bool)
-> Dict[str, type]:
+ def _is_new_union_type(instance: Any) -> bool:
+ new_union_type = getattr(types, 'UnionType', None)
+ return new_union_type is not None and isinstance(instance,
new_union_type)
+
spec = inspect.getfullargspec(callable)
+
try:
- bindings = get_type_hints(callable, include_extras=True)
+ # Return types don't matter for the purpose of dependency injection so
instead of
+ # obtaining type hints of the callable directly let's wrap it in
_NoReturnAnnotationProxy.
+ # The proxy removes the return type annotation (if present) from the
annotations so that
+ # get_type_hints() works even if the return type is a forward
reference that can't be
+ # resolved.
+ bindings = get_type_hints(cast(Callable,
_NoReturnAnnotationProxy(callable)), include_extras=True)
except NameError as e:
raise _BindingNotYetAvailable(e)
@@ -1199,7 +1208,7 @@
if only_explicit_bindings and _inject_marker not in metadata or
_noinject_marker in metadata:
del bindings[k]
- elif _is_specialization(v, Union):
+ elif _is_specialization(v, Union) or _is_new_union_type(v):
# We don't treat Optional parameters in any special way at the
moment.
union_members = v.__args__
new_members = tuple(set(union_members) - {type(None)})
@@ -1209,7 +1218,20 @@
# mypy complains about this construct:
# error: The type alias is invalid in runtime context
# See: https://github.com/python/mypy/issues/5354
- bindings[k] = new_union # type: ignore
+ union_metadata = {
+ metadata
+ for member in new_members
+ for metadata in getattr(member, '__metadata__', tuple())
+ if _is_specialization(member, Annotated)
+ }
+ if (
+ only_explicit_bindings
+ and _inject_marker not in union_metadata
+ or _noinject_marker in union_metadata
+ ):
+ del bindings[k]
+ else:
+ bindings[k] = new_union # type: ignore
return bindings
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.19.0/injector_test.py
new/injector-0.20.1/injector_test.py
--- old/injector-0.19.0/injector_test.py 2021-12-20 23:18:57.000000000
+0100
+++ new/injector-0.20.1/injector_test.py 2022-08-17 01:03:10.000000000
+0200
@@ -11,7 +11,7 @@
"""Functional tests for the "Injector" dependency injection framework."""
from contextlib import contextmanager
-from typing import Any, NewType
+from typing import Any, NewType, Optional, Union
import abc
import sys
import threading
@@ -25,7 +25,9 @@
from injector import (
Binder,
CallError,
+ Inject,
Injector,
+ NoInject,
Scope,
InstanceProvider,
ClassProvider,
@@ -46,12 +48,8 @@
ClassAssistedBuilder,
Error,
UnknownArgument,
- HAVE_ANNOTATED,
)
-if HAVE_ANNOTATED:
- from injector import Inject, NoInject
-
class EmptyClass:
pass
@@ -807,6 +805,19 @@
assert (b1._injector, b2._injector) == (i1, i2)
+def test_assisted_builder_injection_is_safe_to_use_with_child_injectors():
+ class X:
+ @inject
+ def __init__(self, builder: AssistedBuilder[NeedsAssistance]):
+ self.builder = builder
+
+ i1 = Injector()
+ i2 = i1.create_child_injector()
+ b1 = i1.get(X).builder
+ b2 = i2.get(X).builder
+ assert (b1._injector, b2._injector) == (i1, i2)
+
+
class TestThreadSafety:
def setup(self):
self.event = threading.Event()
@@ -1449,38 +1460,75 @@
assert get_bindings(function3b) == {'a': int}
- if HAVE_ANNOTATED:
- # The simple case of no @inject but injection requested with
Inject[...]
- def function4(a: Inject[int], b: str) -> None:
- pass
+ # The simple case of no @inject but injection requested with Inject[...]
+ def function4(a: Inject[int], b: str) -> None:
+ pass
- assert get_bindings(function4) == {'a': int}
+ assert get_bindings(function4) == {'a': int}
- # Using @inject with Inject is redundant but it should not break
anything
- @inject
- def function5(a: Inject[int], b: str) -> None:
- pass
+ # Using @inject with Inject is redundant but it should not break anything
+ @inject
+ def function5(a: Inject[int], b: str) -> None:
+ pass
- assert get_bindings(function5) == {'a': int, 'b': str}
+ assert get_bindings(function5) == {'a': int, 'b': str}
- # We need to be able to exclude a parameter from injection with
NoInject
- @inject
- def function6(a: int, b: NoInject[str]) -> None:
- pass
+ # We need to be able to exclude a parameter from injection with NoInject
+ @inject
+ def function6(a: int, b: NoInject[str]) -> None:
+ pass
- assert get_bindings(function6) == {'a': int}
+ assert get_bindings(function6) == {'a': int}
- # The presence of NoInject should not trigger anything on its own
- def function7(a: int, b: NoInject[str]) -> None:
- pass
+ # The presence of NoInject should not trigger anything on its own
+ def function7(a: int, b: NoInject[str]) -> None:
+ pass
- assert get_bindings(function7) == {}
+ assert get_bindings(function7) == {}
- # There was a bug where in case of multiple NoInject-decorated
parameters only the first one was
- # actually made noninjectable and we tried to inject something we
couldn't possibly provide
- # into the second one.
- @inject
- def function8(a: NoInject[int], b: NoInject[int]) -> None:
- pass
+ # There was a bug where in case of multiple NoInject-decorated parameters
only the first one was
+ # actually made noninjectable and we tried to inject something we couldn't
possibly provide
+ # into the second one.
+ @inject
+ def function8(a: NoInject[int], b: NoInject[int]) -> None:
+ pass
+
+ assert get_bindings(function8) == {}
+
+ # Default arguments to NoInject annotations should behave the same as
noninjectable decorator w.r.t 'None'
+ @inject
+ @noninjectable('b')
+ def function9(self, a: int, b: Optional[str] = None):
+ pass
+
+ @inject
+ def function10(self, a: int, b: NoInject[Optional[str]] = None):
+ # b:s type is Union[NoInject[Union[str, None]], None]
+ pass
+
+ assert get_bindings(function9) == {'a': int} == get_bindings(function10)
+
+ # If there's a return type annottion that contains an a forward reference
that can't be
+ # resolved (for whatever reason) we don't want that to break things for us
??? return types
+ # don't matter for the purpose of dependency injection.
+ @inject
+ def function11(a: int) -> 'InvalidForwardReference':
+ pass
+
+ assert get_bindings(function11) == {'a': int}
+
+
+# Tests https://github.com/alecthomas/injector/issues/202
[email protected](sys.version_info < (3, 10), reason="Requires Python 3.10+")
+def test_get_bindings_for_pep_604():
+ @inject
+ def function1(a: int | None) -> None:
+ pass
+
+ assert get_bindings(function1) == {'a': int}
+
+ @inject
+ def function1(a: int | str) -> None:
+ pass
- assert get_bindings(function8) == {}
+ assert get_bindings(function1) == {'a': Union[int, str]}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.19.0/setup.py new/injector-0.20.1/setup.py
--- old/injector-0.19.0/setup.py 2021-12-20 23:18:57.000000000 +0100
+++ new/injector-0.20.1/setup.py 2022-08-17 01:03:10.000000000 +0200
@@ -49,7 +49,7 @@
try:
import pypandoc
- long_description = pypandoc.convert('README.md', 'rst')
+ long_description = pypandoc.convert_file('README.md', 'rst')
except ImportError:
warnings.warn('Could not locate pandoc, using Markdown long_description.',
ImportWarning)
with open('README.md') as f: