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-01-03 10:49:24
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-injector (Old)
and /work/SRC/openSUSE:Factory/.python-injector.new.1896 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-injector"
Mon Jan 3 10:49:24 2022 rev:7 rq:943322 version:0.19.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-injector/python-injector.changes
2021-03-08 15:18:22.981988154 +0100
+++
/work/SRC/openSUSE:Factory/.python-injector.new.1896/python-injector.changes
2022-01-03 10:49:55.123591918 +0100
@@ -1,0 +2,17 @@
+Fri Dec 31 13:01:20 UTC 2021 - Matej Cepl <[email protected]>
+
+- Update to 0.19.0:
+ - Added the license to the source distribution, thanks to
+ Joshua Adelman
+ - Added Python 3.9 and 3.10 support, this includes fixing
+ Python 3.10 compatibility, thanks to Torge Matthies
+ - Improved the documentation, thanks to Takahiro Kojima
+ - Improved the source distribution so that it can be used to
+ build and install wheels, thanks to Janusz Skonieczny
+ - Added requirements files for easier development, thanks to
+ Greg Eremeev
+ - Removed Python 3.5 support
+ - Fixed a bug where only one of multiple NoInject annotations
+ was interpreted
+
+-------------------------------------------------------------------
Old:
----
0.18.3.tar.gz
New:
----
0.19.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-injector.spec ++++++
--- /var/tmp/diff_new_pack.jPPdmI/_old 2022-01-03 10:49:56.199592330 +0100
+++ /var/tmp/diff_new_pack.jPPdmI/_new 2022-01-03 10:49:56.203592332 +0100
@@ -19,7 +19,7 @@
%define skip_python2 1
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-injector
-Version: 0.18.3
+Version: 0.19.0
Release: 0
Summary: Python dependency injection framework, inspired by Guice
License: BSD-3-Clause
@@ -33,7 +33,7 @@
BuildRequires: python-rpm-macros
BuildRequires: ((python3-dataclasses and python3-base < 3.7) or
(python36-dataclasses and python36-base))
Requires: python
-Requires: python-typing_extensions >= 3.7.4
+Requires: (python-typing_extensions if python-base < 3.8)
BuildArch: noarch
%python_subpackages
++++++ 0.18.3.tar.gz -> 0.19.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.18.3/.github/workflows/ci.yml
new/injector-0.19.0/.github/workflows/ci.yml
--- old/injector-0.18.3/.github/workflows/ci.yml 1970-01-01
01:00:00.000000000 +0100
+++ new/injector-0.19.0/.github/workflows/ci.yml 2021-12-20
23:18:57.000000000 +0100
@@ -0,0 +1,29 @@
+name: CI
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ python-version: [3.6, 3.7, 3.8, 3.9, "3.10", pypy3]
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies
+ run: |
+ pip install --upgrade -r requirements.txt -r requirements-dev.txt
+ pip install .
+ - name: Run tests
+ run: |
+ py.test -vv --cov=injector --cov-branch --cov-report html --cov-report
term
+ if which mypy; then mypy injector ; fi
+ if which black; then black --check . ; fi
+ check-manifest
+ - name: Report coverage to Codecov
+ uses: codecov/codecov-action@v1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.18.3/.gitignore
new/injector-0.19.0/.gitignore
--- old/injector-0.18.3/.gitignore 2020-02-03 15:35:27.000000000 +0100
+++ new/injector-0.19.0/.gitignore 2021-12-20 23:18:57.000000000 +0100
@@ -7,3 +7,6 @@
.mypy_cache/
.pytest_cache/
coverage.xml
+/dist/
+/injector.egg-info/
+/.coverage
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.18.3/.travis.yml
new/injector-0.19.0/.travis.yml
--- old/injector-0.18.3/.travis.yml 2020-02-03 15:35:27.000000000 +0100
+++ new/injector-0.19.0/.travis.yml 1970-01-01 01:00:00.000000000 +0100
@@ -1,29 +0,0 @@
-sudo: false
-language: python
-cache: pip
-python:
- # There were significant typing-related changes in Python 3.5.4, let's make
sure we test with the
- # older versions as well
- - "3.5.3"
- - "3.5"
- - "3.6"
- - "3.7"
- - "3.8"
- - "nightly"
- - "pypy3.5"
- - "pypy3"
-matrix:
- allow_failures:
- - python: "nightly"
-install:
- - pip install --upgrade coveralls pytest "pytest-cov>=2.5.1" dataclasses
typing_extensions
- # mypy can't be installed on pypy
- - if [[ "${TRAVIS_PYTHON_VERSION}" != "pypy"* ]] ; then pip install mypy ; fi
- # Black is Python 3.6+-only
- - if [[ "${TRAVIS_PYTHON_VERSION}" != "pypy"* && "${TRAVIS_PYTHON_VERSION}"
!= "3.5"* ]] ; then pip install black ; fi
-script:
- - py.test -vv --cov=injector --cov-branch --cov-report html --cov-report term
- - if [[ "${TRAVIS_PYTHON_VERSION}" != "pypy"* ]] ; then mypy injector ; fi
- - if [[ "${TRAVIS_PYTHON_VERSION}" != "pypy"* && "${TRAVIS_PYTHON_VERSION}"
!= "3.5"* ]] ; then black --check . ; fi
-after_success:
- - coveralls
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.18.3/CHANGES new/injector-0.19.0/CHANGES
--- old/injector-0.18.3/CHANGES 2020-02-03 15:35:27.000000000 +0100
+++ new/injector-0.19.0/CHANGES 2021-12-20 23:18:57.000000000 +0100
@@ -1,6 +1,24 @@
Injector Change Log
===================
+0.19.0
+------
+
+- Added the license to the source distribution, thanks to Joshua Adelman
+- Added Python 3.9 and 3.10 support, this includes fixing Python 3.10
compatibility, thanks to Torge Matthies
+- Improved the documentation, thanks to Takahiro Kojima
+- Improved the source distribution so that it can be used to build and install
wheels, thanks to Janusz Skonieczny
+- Added requirements files for easier development, thanks to Greg Eremeev
+
+Backwards incompatible:
+
+- Removed Python 3.5 support
+
+0.18.4
+------
+
+- Fixed a bug where only one of multiple NoInject annotations was interpreted
+
0.18.3
------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.18.3/MANIFEST.in
new/injector-0.19.0/MANIFEST.in
--- old/injector-0.18.3/MANIFEST.in 2020-02-03 15:35:27.000000000 +0100
+++ new/injector-0.19.0/MANIFEST.in 2021-12-20 23:18:57.000000000 +0100
@@ -1 +1,12 @@
+include *.py
+include *.toml
+include *.txt
+include CHANGES
+include COPYING
include README.md
+include mypy.ini
+include pytest.ini
+recursive-include docs *.html
+recursive-include docs *.py
+recursive-include docs *.rst
+recursive-include docs Makefile
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.18.3/README.md
new/injector-0.19.0/README.md
--- old/injector-0.18.3/README.md 2020-02-03 15:35:27.000000000 +0100
+++ new/injector-0.19.0/README.md 2021-12-20 23:18:57.000000000 +0100
@@ -1,8 +1,8 @@
Injector - Python dependency injection framework, inspired by Guice
===================================================================
-[](https://travis-ci.org/alecthomas/injector)
-[](https://coveralls.io/github/alecthomas/injector?branch=master)
+[](https://github.com/alecthomas/injector/actions?query=workflow%3ACI+branch%3Amaster)
+[](https://codecov.io/gh/alecthomas/injector)
Introduction
------------
@@ -51,21 +51,28 @@
is typed such that `injector.get(SomeType)` is statically declared to return
an instance of
`SomeType`, therefore making it possible for tools such as
[mypy](https://github.com/python/mypy) to
type-check correctly the code using it.
+
+* The client code only knows about dependency injection to the extent it needs
?????
+
[`inject`](https://injector.readthedocs.io/en/latest/api.html#injector.inject),
+
[`Inject`](https://injector.readthedocs.io/en/latest/api.html#injector.Inject)
and
+
[`NoInject`](https://injector.readthedocs.io/en/latest/api.html#injector.NoInject)
are simple markers
+ that don't really do anything on their own and your code can run just fine
without Injector
+ orchestrating things.
### How to get Injector?
* GitHub (code repository, issues): https://github.com/alecthomas/injector
-* PyPI (installable, stable distributions):
https://pypi.python.org/pypi/injector. You can install it using pip:
+* PyPI (installable, stable distributions):
https://pypi.org/project/injector/. You can install it using pip:
```bash
pip install injector
```
-* Documentation: http://injector.readthedocs.org
-* Change log: http://injector.readthedocs.io/en/latest/changelog.html
+* Documentation: https://injector.readthedocs.org
+* Change log: https://injector.readthedocs.io/en/latest/changelog.html
-Injector works with CPython 3.5+ and PyPy 3 implementing Python 3.5+.
+Injector works with CPython 3.6+ and PyPy 3 implementing Python 3.6+.
A Quick Example
---------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.18.3/docs/_templates/sidebar.html
new/injector-0.19.0/docs/_templates/sidebar.html
--- old/injector-0.18.3/docs/_templates/sidebar.html 2020-02-03
15:35:27.000000000 +0100
+++ new/injector-0.19.0/docs/_templates/sidebar.html 2021-12-20
23:18:57.000000000 +0100
@@ -2,6 +2,6 @@
<p>Dependency Injection Framework</p>
<ul>
<li><a href="https://github.com/alecthomas/injector">Injector at
GitHub</a></li>
- <li><a href="https://pypi.python.org/pypi/injector">Injector at
PyPI</a></li>
- <li><a href="http://injector.readthedocs.org/en/latest/">Injector
documentation</a></li>
+ <li><a href="https://pypi.org/project/injector/">Injector at
PyPI</a></li>
+ <li><a href="https://injector.readthedocs.org/en/latest/">Injector
documentation</a></li>
</ul>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.18.3/docs/index.rst
new/injector-0.19.0/docs/index.rst
--- old/injector-0.18.3/docs/index.rst 2020-02-03 15:35:27.000000000 +0100
+++ new/injector-0.19.0/docs/index.rst 2021-12-20 23:18:57.000000000 +0100
@@ -6,13 +6,14 @@
Welcome to Injector's documentation!
====================================
-.. image:: https://travis-ci.org/alecthomas/injector.png?branch=master
+.. image:: https://github.com/alecthomas/injector/workflows/CI/badge.svg
:alt: Build status
- :target: https://travis-ci.org/alecthomas/injector
+ :target:
https://github.com/alecthomas/injector/actions?query=workflow%3ACI+branch%3Amaster
-.. image::
https://coveralls.io/repos/github/alecthomas/injector/badge.svg?branch=master
+.. image::
https://codecov.io/gh/alecthomas/injector/branch/master/graph/badge.svg
:alt: Covergage status
- :target: https://coveralls.io/github/alecthomas/injector?branch=master
+ :target: https://codecov.io/gh/alecthomas/injector
+
GitHub (code repository, issues): https://github.com/alecthomas/injector
@@ -20,7 +21,7 @@
pip install injector
-Injector works with CPython 3.5+ and PyPy 3 implementing Python 3.5+.
+Injector works with CPython 3.6+ and PyPy 3 implementing Python 3.6+.
Introduction
------------
@@ -51,7 +52,7 @@
class MyClass:
@inject
- def __init__(t: SomeType):
+ def __init__(self, t: SomeType):
# ...
MyClass()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.18.3/docs/practices.rst
new/injector-0.19.0/docs/practices.rst
--- old/injector-0.18.3/docs/practices.rst 2020-02-03 15:35:27.000000000
+0100
+++ new/injector-0.19.0/docs/practices.rst 2021-12-20 23:18:57.000000000
+0100
@@ -126,11 +126,11 @@
from threading import Thread
from time import sleep
- from injector import inject, Injector, Key, Module, provider
+ from injector import inject, Injector, Module, provider
- SubA = Key('SubA')
- A = Key('A')
- B = Key('B')
+ class A: pass
+ class SubA(A): pass
+ class B: pass
class BadModule(Module):
@@ -146,11 +146,11 @@
sleep(1)
# This never executes
- return 'suba'
+ return SubA()
@provider
def provide_b(self) -> B:
- return 'b'
+ return B()
injector = Injector([BadModule])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.18.3/docs/terminology.rst
new/injector-0.19.0/docs/terminology.rst
--- old/injector-0.18.3/docs/terminology.rst 2020-02-03 15:35:27.000000000
+0100
+++ new/injector-0.19.0/docs/terminology.rst 2021-12-20 23:18:57.000000000
+0100
@@ -29,15 +29,6 @@
.. seealso:: :ref:`scopes`
-Keys
-````
-
-`Key` may be used to create unique types as necessary::
-
- >>> from injector import Key
- >>> Name = Key('name')
- >>> Description = Key('description')
-
Binding
```````
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.18.3/injector/__init__.py
new/injector-0.19.0/injector/__init__.py
--- old/injector-0.18.3/injector/__init__.py 2020-02-03 15:35:27.000000000
+0100
+++ new/injector-0.19.0/injector/__init__.py 2021-12-20 23:18:57.000000000
+0100
@@ -33,22 +33,38 @@
List,
Optional,
overload,
+ Set,
Tuple,
Type,
TypeVar,
+ TYPE_CHECKING,
Union,
)
-from typing_extensions import NoReturn
+try:
+ from typing import NoReturn
+except ImportError:
+ from typing_extensions import NoReturn
HAVE_ANNOTATED = sys.version_info >= (3, 7, 0)
-if HAVE_ANNOTATED:
+# 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.
+if TYPE_CHECKING:
+ from typing_extensions import _AnnotatedAlias, Annotated, get_type_hints
+elif HAVE_ANNOTATED:
# Ignoring errors here as typing_extensions stub doesn't know about those
things yet
- from typing_extensions import _AnnotatedAlias, Annotated, get_type_hints
# type: ignore
+ try:
+ from typing import _AnnotatedAlias, Annotated, get_type_hints
+ except ImportError:
+ from typing_extensions import _AnnotatedAlias, Annotated,
get_type_hints
else:
- class Annotated: # type: ignore
+ class Annotated:
pass
from typing import get_type_hints as _get_type_hints
@@ -62,11 +78,8 @@
return _get_type_hints(obj, globalns, localns)
-TYPING353 = hasattr(Union[str, int], '__origin__')
-
-
__author__ = 'Alec Thomas <[email protected]>'
-__version__ = '0.18.3'
+__version__ = '0.19.0'
__version_tag__ = ''
log = logging.getLogger('injector')
@@ -110,7 +123,7 @@
InjectT = TypeVar('InjectT')
Inject = Annotated[InjectT, _inject_marker]
"""An experimental way to declare injectable dependencies utilizing a `PEP
593`_ implementation
- in `typing_extensions`.
+ in Python 3.9 and backported to Python 3.7+ in `typing_extensions`.
Those two declarations are equivalent::
@@ -150,7 +163,7 @@
NoInject = Annotated[InjectT, _noinject_marker]
"""An experimental way to declare noninjectable dependencies utilizing a
`PEP 593`_ implementation
- in `typing_extensions`.
+ 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
@@ -221,15 +234,9 @@
return self.args[0]
instance, method, args, kwargs, original_error, stack = self.args
- if hasattr(method, 'im_class'):
- instance = method.__self__
- method_name = method.__func__.__name__
- else:
- method_name = method.__name__
-
cls = instance.__class__.__name__ if instance is not None else ''
- full_method = '.'.join((cls, method_name)).strip('.')
+ full_method = '.'.join((cls, method.__name__)).strip('.')
parameters = ', '.join(
itertools.chain(
@@ -305,7 +312,7 @@
providing
providing
False
- """
+ """
def __init__(self, callable: Callable[..., T]):
self._callable = callable
@@ -350,8 +357,10 @@
class ListOfProviders(Provider, Generic[T]):
"""Provide a list of instances via other Providers."""
+ _providers: List[Provider[T]]
+
def __init__(self) -> None:
- self._providers = [] # type: List[Provider[T]]
+ self._providers = []
def append(self, provider: Provider[T]) -> None:
self._providers.append(provider)
@@ -372,7 +381,7 @@
"""A provider for map bindings."""
def get(self, injector: 'Injector') -> Dict[str, T]:
- map = {} # type: Dict[str, T]
+ map: Dict[str, T] = {}
for provider in self._providers:
map.update(provider.get(injector))
return map
@@ -399,6 +408,8 @@
to instantiate it on your own.
"""
+ _bindings: Dict[type, Binding]
+
@private
def __init__(self, injector: 'Injector', auto_bind: bool = True, parent:
'Binder' = None) -> None:
"""Create a new Binder.
@@ -409,7 +420,7 @@
"""
self.injector = injector
self._auto_bind = auto_bind
- self._bindings = {} # type: Dict[type, Binding]
+ self._bindings = {}
self.parent = parent
def bind(
@@ -507,13 +518,14 @@
:param scope: Optional Scope in which to bind.
"""
if interface not in self._bindings:
+ provider: ListOfProviders
if (
isinstance(interface, dict)
or isinstance(interface, type)
and issubclass(interface, dict)
or _get_origin(_punch_through_alias(interface)) is dict
):
- provider = MapBindProvider() # type: ListOfProviders
+ provider = MapBindProvider()
else:
provider = MultiBindProvider()
binding = self.create_binding(interface, provider, scope)
@@ -662,47 +674,45 @@
return any(_is_specialization(interface, cls) for cls in
[AssistedBuilder, ProviderOf])
-if TYPING353:
-
- def _is_specialization(cls: type, generic_class: Any) -> bool:
- # Starting with typing 3.5.3/Python 3.6 it is no longer necessarily
true that
- # issubclass(SomeGeneric[X], SomeGeneric) so we need some other way to
- # determine whether a particular object is a generic class with type
parameters
- # provided. Fortunately there seems to be __origin__ attribute that's
useful here.
-
- # 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):
- return True
-
- if not hasattr(cls, '__origin__'):
- return False
- origin = cast(Any, cls).__origin__
- if not inspect.isclass(generic_class):
- generic_class = type(generic_class)
- if not inspect.isclass(origin):
- origin = type(origin)
- # __origin__ is generic_class is a special case to handle Union as
- # Union cannot be used in issubclass() check (it raises an exception
- # by design).
- return origin is generic_class or issubclass(origin, generic_class)
-
-
-else:
- # To maintain compatibility we fall back to an issubclass check.
- def _is_specialization(cls: type, generic_class: Any) -> bool:
- return isinstance(cls, type) and cls is not Any and issubclass(cls,
generic_class)
+def _is_specialization(cls: type, generic_class: Any) -> bool:
+ # Starting with typing 3.5.3/Python 3.6 it is no longer necessarily true
that
+ # issubclass(SomeGeneric[X], SomeGeneric) so we need some other way to
+ # determine whether a particular object is a generic class with type
parameters
+ # provided. Fortunately there seems to be __origin__ attribute that's
useful here.
+
+ # 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):
+ return True
+
+ if not hasattr(cls, '__origin__'):
+ return False
+ origin = cast(Any, cls).__origin__
+ if not inspect.isclass(generic_class):
+ generic_class = type(generic_class)
+ if not inspect.isclass(origin):
+ origin = type(origin)
+ # __origin__ is generic_class is a special case to handle Union as
+ # Union cannot be used in issubclass() check (it raises an exception
+ # by design).
+ return origin is generic_class or issubclass(origin, generic_class)
def _punch_through_alias(type_: Any) -> type:
- if getattr(type_, '__qualname__', '') == 'NewType.<locals>.new_type':
+ if (
+ sys.version_info < (3, 10)
+ and getattr(type_, '__qualname__', '') == 'NewType.<locals>.new_type'
+ or sys.version_info >= (3, 10)
+ and type(type_).__module__ == 'typing'
+ and type(type_).__name__ == 'NewType'
+ ):
return type_.__supertype__
else:
return type_
-def _get_origin(type_: type) -> type:
+def _get_origin(type_: type) -> Optional[type]:
origin = getattr(type_, '__origin__', None)
# Older typing behaves differently there and stores Dict and List as
origin, we need to be flexible.
if origin is List:
@@ -780,8 +790,10 @@
True
"""
+ _context: Dict[type, Provider]
+
def configure(self) -> None:
- self._context = {} # type: Dict[type, Provider]
+ self._context = {}
@synchronized(lock)
def get(self, key: Type[T], provider: Provider[T]) -> Provider[T]:
@@ -865,6 +877,9 @@
``use_annotations`` parameter is removed
"""
+ _stack: Tuple[Tuple[object, Callable, Tuple[Tuple[str, type], ...]], ...]
+ binder: Binder
+
def __init__(
self,
modules: Union[_InstallableModuleType,
Iterable[_InstallableModuleType]] = None,
@@ -873,14 +888,12 @@
) -> None:
# Stack of keys currently being injected. Used to detect circular
# dependencies.
- self._stack = () # type: Tuple[Tuple[object, Callable,
Tuple[Tuple[str, type], ...]], ...]
+ self._stack = ()
self.parent = parent
# Binder
- self.binder = Binder(
- self, auto_bind=auto_bind, parent=parent.binder if parent is not
None else None
- ) # type: Binder
+ self.binder = Binder(self, auto_bind=auto_bind, parent=parent.binder
if parent is not None else None)
if not modules:
modules = []
@@ -966,15 +979,13 @@
CallError(cls, getattr(cls.__new__, '__func__', cls.__new__),
(), {}, e, self._stack),
maximum_frames=2,
)
+ init = cls.__init__
try:
- # On Python 3.5.3 calling call_with_injection() with
object.__init__ would fail further
- # down the line as get_type_hints(object.__init__) raises an
exception until Python 3.5.4.
- # And since object.__init__ doesn't do anything useful and can't
have any injectable
- # arguments we can skip calling it altogether. See GH-135 for more
information.
- if cls.__init__ is not object.__init__:
- self.call_with_injection(cls.__init__, self_=instance,
kwargs=additional_kwargs)
+ self.call_with_injection(init, self_=instance,
kwargs=additional_kwargs)
except TypeError as e:
- reraise(e, CallError(instance, instance.__init__.__func__, (),
additional_kwargs, e, self._stack))
+ # Mypy says "Cannot access "__init__" directly"
+ init_function = instance.__init__.__func__ # type: ignore
+ reraise(e, CallError(instance, init_function, (),
additional_kwargs, e, self._stack))
return instance
def call_with_injection(
@@ -1051,7 +1062,7 @@
try:
for arg, interface in bindings.items():
try:
- instance = self.get(interface) # type: Any
+ instance: Any = self.get(interface)
except UnsatisfiedRequirement as e:
if not e.owner:
e = UnsatisfiedRequirement(owner_key, e.interface)
@@ -1147,7 +1158,7 @@
read_and_store_bindings(
callable, _infer_injected_bindings(callable,
only_explicit_bindings=look_for_explicit_bindings)
)
- noninjectables = getattr(callable, '__noninjectables__', set())
+ noninjectables: Set[str] = getattr(callable, '__noninjectables__', set())
return {k: v for k, v in cast(Any, callable).__bindings__.items() if k not
in noninjectables}
@@ -1188,14 +1199,9 @@
if only_explicit_bindings and _inject_marker not in metadata or
_noinject_marker in metadata:
del bindings[k]
- break
-
- if _is_specialization(v, Union):
+ elif _is_specialization(v, Union):
# We don't treat Optional parameters in any special way at the
moment.
- if TYPING353:
- union_members = v.__args__
- else:
- union_members = v.__union_params__
+ union_members = v.__args__
new_members = tuple(set(union_members) - {type(None)})
# mypy stared complaining about this line for some reason:
# error: Variable "new_members" is not valid as a type
@@ -1397,7 +1403,7 @@
if arg not in argspec.args and arg not in argspec.kwonlyargs:
raise UnknownArgument('Unable to mark unknown argument %s '
'as non-injectable.' % arg)
- existing = getattr(function, '__noninjectables__', set())
+ existing: Set[str] = getattr(function, '__noninjectables__', set())
merged = existing | set(args)
cast(Any, function).__noninjectables__ = merged
return function
@@ -1481,19 +1487,19 @@
class ProviderOf(Generic[T]):
"""Can be used to get a provider of an interface, for example:
- >>> def provide_int():
- ... print('providing')
- ... return 123
- >>>
- >>> def configure(binder):
- ... binder.bind(int, to=provide_int)
- >>>
- >>> injector = Injector(configure)
- >>> provider = injector.get(ProviderOf[int])
- >>> value = provider.get()
- providing
- >>> value
- 123
+ >>> def provide_int():
+ ... print('providing')
+ ... return 123
+ >>>
+ >>> def configure(binder):
+ ... binder.bind(int, to=provide_int)
+ >>>
+ >>> injector = Injector(configure)
+ >>> provider = injector.get(ProviderOf[int])
+ >>> value = provider.get()
+ providing
+ >>> value
+ 123
"""
def __init__(self, injector: Injector, interface: Type[T]):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.18.3/injector_test.py
new/injector-0.19.0/injector_test.py
--- old/injector-0.18.3/injector_test.py 2020-02-03 15:35:27.000000000
+0100
+++ new/injector-0.19.0/injector_test.py 2021-12-20 23:18:57.000000000
+0100
@@ -53,17 +53,15 @@
from injector import Inject, NoInject
-def prepare_basic_injection():
- class B:
- pass
+class EmptyClass:
+ pass
- class A:
- @inject
- def __init__(self, b: B):
- """Construct a new A."""
- self.b = b
- return A, B
+class DependsOnEmptyClass:
+ @inject
+ def __init__(self, b: EmptyClass):
+ """Construct a new DependsOnEmptyClass."""
+ self.b = b
def prepare_nested_injectors():
@@ -130,11 +128,9 @@
def test_get_default_injected_instances():
- A, B = prepare_basic_injection()
-
def configure(binder):
- binder.bind(A)
- binder.bind(B)
+ binder.bind(DependsOnEmptyClass)
+ binder.bind(EmptyClass)
injector = Injector(configure)
assert injector.get(Injector) is injector
@@ -142,15 +138,13 @@
def test_instantiate_injected_method():
- A, _ = prepare_basic_injection()
- a = A('Bob')
+ a = DependsOnEmptyClass('Bob')
assert a.b == 'Bob'
def test_method_decorator_is_wrapped():
- A, _ = prepare_basic_injection()
- assert A.__init__.__doc__ == 'Construct a new A.'
- assert A.__init__.__name__ == '__init__'
+ assert DependsOnEmptyClass.__init__.__doc__ == 'Construct a new
DependsOnEmptyClass.'
+ assert DependsOnEmptyClass.__init__.__name__ == '__init__'
def test_decorator_works_for_function_with_no_args():
@@ -178,121 +172,104 @@
def test_inject_direct():
- A, B = prepare_basic_injection()
-
def configure(binder):
- binder.bind(A)
- binder.bind(B)
+ binder.bind(DependsOnEmptyClass)
+ binder.bind(EmptyClass)
injector = Injector(configure)
- a = injector.get(A)
- assert isinstance(a, A)
- assert isinstance(a.b, B)
+ a = injector.get(DependsOnEmptyClass)
+ assert isinstance(a, DependsOnEmptyClass)
+ assert isinstance(a.b, EmptyClass)
def test_configure_multiple_modules():
- A, B = prepare_basic_injection()
-
def configure_a(binder):
- binder.bind(A)
+ binder.bind(DependsOnEmptyClass)
def configure_b(binder):
- binder.bind(B)
+ binder.bind(EmptyClass)
injector = Injector([configure_a, configure_b])
- a = injector.get(A)
- assert isinstance(a, A)
- assert isinstance(a.b, B)
+ a = injector.get(DependsOnEmptyClass)
+ assert isinstance(a, DependsOnEmptyClass)
+ assert isinstance(a.b, EmptyClass)
def test_inject_with_missing_dependency():
- A, _ = prepare_basic_injection()
-
def configure(binder):
- binder.bind(A)
+ binder.bind(DependsOnEmptyClass)
injector = Injector(configure, auto_bind=False)
with pytest.raises(UnsatisfiedRequirement):
- injector.get(A)
+ injector.get(EmptyClass)
def test_inject_named_interface():
- class B:
- pass
-
class A:
@inject
- def __init__(self, b: B):
+ def __init__(self, b: EmptyClass):
self.b = b
def configure(binder):
binder.bind(A)
- binder.bind(B)
+ binder.bind(EmptyClass)
injector = Injector(configure)
a = injector.get(A)
assert isinstance(a, A)
- assert isinstance(a.b, B)
+ assert isinstance(a.b, EmptyClass)
-def prepare_transitive_injection():
- class C:
- pass
+class TransitiveC:
+ pass
- class B:
- @inject
- def __init__(self, c: C):
- self.c = c
- class A:
- @inject
- def __init__(self, b: B):
- self.b = b
+class TransitiveB:
+ @inject
+ def __init__(self, c: TransitiveC):
+ self.c = c
- return A, B, C
+class TransitiveA:
+ @inject
+ def __init__(self, b: TransitiveB):
+ self.b = b
-def test_transitive_injection():
- A, B, C = prepare_transitive_injection()
+def test_transitive_injection():
def configure(binder):
- binder.bind(A)
- binder.bind(B)
- binder.bind(C)
+ binder.bind(TransitiveA)
+ binder.bind(TransitiveB)
+ binder.bind(TransitiveC)
injector = Injector(configure)
- a = injector.get(A)
- assert isinstance(a, A)
- assert isinstance(a.b, B)
- assert isinstance(a.b.c, C)
+ a = injector.get(TransitiveA)
+ assert isinstance(a, TransitiveA)
+ assert isinstance(a.b, TransitiveB)
+ assert isinstance(a.b.c, TransitiveC)
def test_transitive_injection_with_missing_dependency():
- A, B, _ = prepare_transitive_injection()
-
def configure(binder):
- binder.bind(A)
- binder.bind(B)
+ binder.bind(TransitiveA)
+ binder.bind(TransitiveB)
injector = Injector(configure, auto_bind=False)
with pytest.raises(UnsatisfiedRequirement):
- injector.get(A)
+ injector.get(TransitiveA)
with pytest.raises(UnsatisfiedRequirement):
- injector.get(B)
+ injector.get(TransitiveB)
def test_inject_singleton():
- class B:
- pass
-
class A:
@inject
- def __init__(self, b: B):
+ def __init__(self, b: EmptyClass):
self.b = b
def configure(binder):
binder.bind(A)
- binder.bind(B, scope=SingletonScope)
+ binder.bind(EmptyClass, scope=SingletonScope)
injector1 = Injector(configure)
a1 = injector1.get(A)
@@ -300,19 +277,20 @@
assert a1.b is a2.b
-def test_inject_decorated_singleton_class():
- @singleton
- class B:
- pass
+@singleton
+class SingletonB:
+ pass
+
+def test_inject_decorated_singleton_class():
class A:
@inject
- def __init__(self, b: B):
+ def __init__(self, b: SingletonB):
self.b = b
def configure(binder):
binder.bind(A)
- binder.bind(B)
+ binder.bind(SingletonB)
injector1 = Injector(configure)
a1 = injector1.get(A)
@@ -348,97 +326,105 @@
assert a2 is not a3[0] and a3[0] is not None
-def test_injecting_interface_implementation():
- class Interface:
- pass
+class Interface2:
+ pass
+
+def test_injecting_interface_implementation():
class Implementation:
pass
class A:
@inject
- def __init__(self, i: Interface):
+ def __init__(self, i: Interface2):
self.i = i
def configure(binder):
binder.bind(A)
- binder.bind(Interface, to=Implementation)
+ binder.bind(Interface2, to=Implementation)
injector = Injector(configure)
a = injector.get(A)
assert isinstance(a.i, Implementation)
-def test_cyclic_dependencies():
- class Interface:
- pass
+class CyclicInterface:
+ pass
- class A:
- @inject
- def __init__(self, i: Interface):
- self.i = i
- class B:
- @inject
- def __init__(self, a: A):
- self.a = a
+class CyclicA:
+ @inject
+ def __init__(self, i: CyclicInterface):
+ self.i = i
+
+
+class CyclicB:
+ @inject
+ def __init__(self, a: CyclicA):
+ self.a = a
+
+def test_cyclic_dependencies():
def configure(binder):
- binder.bind(Interface, to=B)
- binder.bind(A)
+ binder.bind(CyclicInterface, to=CyclicB)
+ binder.bind(CyclicA)
injector = Injector(configure)
with pytest.raises(CircularDependency):
- injector.get(A)
+ injector.get(CyclicA)
-def test_dependency_cycle_can_be_worked_broken_by_assisted_building():
- class Interface:
- pass
+class CyclicInterface2:
+ pass
- class A:
- @inject
- def __init__(self, i: Interface):
- self.i = i
- class B:
- @inject
- def __init__(self, a_builder: AssistedBuilder[A]):
- self.a = a_builder.build(i=self)
+class CyclicA2:
+ @inject
+ def __init__(self, i: CyclicInterface2):
+ self.i = i
+
+class CyclicB2:
+ @inject
+ def __init__(self, a_builder: AssistedBuilder[CyclicA2]):
+ self.a = a_builder.build(i=self)
+
+
+def test_dependency_cycle_can_be_worked_broken_by_assisted_building():
def configure(binder):
- binder.bind(Interface, to=B)
- binder.bind(A)
+ binder.bind(CyclicInterface2, to=CyclicB2)
+ binder.bind(CyclicA2)
injector = Injector(configure)
# Previously it'd detect a circular dependency here:
- # 1. Constructing A requires Interface (bound to B)
- # 2. Constructing B requires assisted build of A
- # 3. Constructing A triggers circular dependency check
- assert isinstance(injector.get(A), A)
+ # 1. Constructing CyclicA2 requires CyclicInterface2 (bound to CyclicB2)
+ # 2. Constructing CyclicB2 requires assisted build of CyclicA2
+ # 3. Constructing CyclicA2 triggers circular dependency check
+ assert isinstance(injector.get(CyclicA2), CyclicA2)
-def test_that_injection_is_lazy():
- class Interface:
- constructed = False
+class Interface5:
+ constructed = False
+
+ def __init__(self):
+ Interface5.constructed = True
- def __init__(self):
- Interface.constructed = True
+def test_that_injection_is_lazy():
class A:
@inject
- def __init__(self, i: Interface):
+ def __init__(self, i: Interface5):
self.i = i
def configure(binder):
- binder.bind(Interface)
+ binder.bind(Interface5)
binder.bind(A)
injector = Injector(configure)
- assert not (Interface.constructed)
+ assert not (Interface5.constructed)
injector.get(A)
- assert Interface.constructed
+ assert Interface5.constructed
def test_module_provider():
@@ -482,10 +468,11 @@
assert Injector(MyModule()).get(str) == 'Bob is 25 and weighs 50.0kg'
-def test_multibind():
- Names = NewType('Names', List[str])
- Passwords = NewType('Ages', Dict[str, str])
+Names = NewType('Names', List[str])
+Passwords = NewType('Ages', Dict[str, str])
+
+def test_multibind():
# First let's have some explicit multibindings
def configure(binder):
binder.multibind(List[str], to=['not a name'])
@@ -597,45 +584,51 @@
assert isinstance(injector.get(AliasOfA), A)
-def test_custom_scope():
- class RequestScope(Scope):
- def configure(self):
- self.context = None
-
- @contextmanager
- def __call__(self, request):
- assert self.context is None
- self.context = {}
- binder = self.injector.get(Binder)
- binder.bind(Request, to=request, scope=RequestScope)
- yield
- self.context = None
-
- def get(self, key, provider):
- if self.context is None:
- raise UnsatisfiedRequirement(None, key)
- try:
- return self.context[key]
- except KeyError:
- provider = InstanceProvider(provider.get(self.injector))
- self.context[key] = provider
- return provider
+class Request:
+ pass
- request = ScopeDecorator(RequestScope)
- class Request:
- pass
+class RequestScope(Scope):
+ def configure(self):
+ self.context = None
- @request
- class Handler:
- def __init__(self, request):
- self.request = request
+ @contextmanager
+ def __call__(self, request):
+ assert self.context is None
+ self.context = {}
+ binder = self.injector.get(Binder)
+ binder.bind(Request, to=request, scope=RequestScope)
+ yield
+ self.context = None
- class RequestModule(Module):
- @provider
- @inject
- def handler(self, request: Request) -> Handler:
- return Handler(request)
+ def get(self, key, provider):
+ if self.context is None:
+ raise UnsatisfiedRequirement(None, key)
+ try:
+ return self.context[key]
+ except KeyError:
+ provider = InstanceProvider(provider.get(self.injector))
+ self.context[key] = provider
+ return provider
+
+
+request = ScopeDecorator(RequestScope)
+
+
+@request
+class Handler:
+ def __init__(self, request):
+ self.request = request
+
+
+class RequestModule(Module):
+ @provider
+ @inject
+ def handler(self, request: Request) -> Handler:
+ return Handler(request)
+
+
+def test_custom_scope():
injector = Injector([RequestModule()], auto_bind=False)
@@ -716,16 +709,18 @@
assert isinstance(binder.provider_for(A, None).get(injector), A)
-def
test_injecting_undecorated_class_with_missing_dependencies_raises_the_right_error():
- class ClassA:
- def __init__(self, parameter):
- pass
+class ClassA:
+ def __init__(self, parameter):
+ pass
+
+
+class ClassB:
+ @inject
+ def __init__(self, a: ClassA):
+ pass
- class ClassB:
- @inject
- def __init__(self, a: ClassA):
- pass
+def
test_injecting_undecorated_class_with_missing_dependencies_raises_the_right_error():
injector = Injector()
try:
injector.get(ClassB)
@@ -773,10 +768,11 @@
assert (x.obj.a, x.obj.b) == (str(), 234)
-def test_assisted_builder_uses_bindings():
- class Interface:
- b = 0
+class Interface:
+ b = 0
+
+def test_assisted_builder_uses_bindings():
def configure(binder):
binder.bind(Interface, to=NeedsAssistance)
@@ -884,10 +880,11 @@
injector.get(X)
-def test_callable_provider_injection():
- Name = NewType("Name", str)
- Message = NewType("Message", str)
+Name = NewType("Name", str)
+Message = NewType("Message", str)
+
+def test_callable_provider_injection():
@inject
def create_message(name: Name):
return "Hello, " + name
@@ -991,47 +988,48 @@
assert injector.get(bytes) == text
-def test_class_assisted_builder_of_partially_injected_class_old():
- class A:
- pass
+class PartialB:
+ @inject
+ def __init__(self, a: EmptyClass, b: str):
+ self.a = a
+ self.b = b
- class B:
- @inject
- def __init__(self, a: A, b: str):
- self.a = a
- self.b = b
+def test_class_assisted_builder_of_partially_injected_class_old():
class C:
@inject
- def __init__(self, a: A, builder: ClassAssistedBuilder[B]):
+ def __init__(self, a: EmptyClass, builder:
ClassAssistedBuilder[PartialB]):
self.a = a
self.b = builder.build(b='C')
c = Injector().get(C)
assert isinstance(c, C)
- assert isinstance(c.b, B)
- assert isinstance(c.b.a, A)
+ assert isinstance(c.b, PartialB)
+ assert isinstance(c.b.a, EmptyClass)
-def test_implicit_injection_for_python3():
- class A:
- pass
+class ImplicitA:
+ pass
- class B:
- @inject
- def __init__(self, a: A):
- self.a = a
- class C:
- @inject
- def __init__(self, b: B):
- self.b = b
+class ImplicitB:
+ @inject
+ def __init__(self, a: ImplicitA):
+ self.a = a
+
+
+class ImplicitC:
+ @inject
+ def __init__(self, b: ImplicitB):
+ self.b = b
+
+def test_implicit_injection_for_python3():
injector = Injector()
- c = injector.get(C)
- assert isinstance(c, C)
- assert isinstance(c.b, B)
- assert isinstance(c.b.a, A)
+ c = injector.get(ImplicitC)
+ assert isinstance(c, ImplicitC)
+ assert isinstance(c.b, ImplicitB)
+ assert isinstance(c.b.a, ImplicitA)
def test_annotation_based_injection_works_in_provider_methods():
@@ -1053,21 +1051,23 @@
assert injector.get(object) is injector.get(object)
-def test_assisted_building_is_supported():
- class Fetcher:
- def fetch(self, user_id):
- assert user_id == 333
- return {'name': 'John'}
-
- class Processor:
- @noninjectable('provider_id')
- @inject
- @noninjectable('user_id')
- def __init__(self, fetcher: Fetcher, user_id: int, provider_id: str):
- assert provider_id == 'not injected'
- data = fetcher.fetch(user_id)
- self.name = data['name']
+class Fetcher:
+ def fetch(self, user_id):
+ assert user_id == 333
+ return {'name': 'John'}
+
+class Processor:
+ @noninjectable('provider_id')
+ @inject
+ @noninjectable('user_id')
+ def __init__(self, fetcher: Fetcher, user_id: int, provider_id: str):
+ assert provider_id == 'not injected'
+ data = fetcher.fetch(user_id)
+ self.name = data['name']
+
+
+def test_assisted_building_is_supported():
def configure(binder):
binder.bind(int, to=897)
binder.bind(str, to='injected')
@@ -1294,26 +1294,24 @@
del X
-def test_class_assisted_builder_of_partially_injected_class():
- class A:
- pass
+class AssistedB:
+ @inject
+ def __init__(self, a: EmptyClass, b: str):
+ self.a = a
+ self.b = b
- class B:
- @inject
- def __init__(self, a: A, b: str):
- self.a = a
- self.b = b
+def test_class_assisted_builder_of_partially_injected_class():
class C:
@inject
- def __init__(self, a: A, builder: ClassAssistedBuilder[B]):
+ def __init__(self, a: EmptyClass, builder:
ClassAssistedBuilder[AssistedB]):
self.a = a
self.b = builder.build(b='C')
c = Injector().get(C)
assert isinstance(c, C)
- assert isinstance(c.b, B)
- assert isinstance(c.b.a, A)
+ assert isinstance(c.b, AssistedB)
+ assert isinstance(c.b.a, EmptyClass)
# The test taken from Alec Thomas' pull request:
https://github.com/alecthomas/injector/pull/73
@@ -1380,18 +1378,15 @@
def test_using_an_assisted_builder_with_a_provider_raises_an_injector_error():
- class A:
- pass
-
class MyModule(Module):
@provider
- def provide_a(self, builder: AssistedBuilder[A]) -> A:
+ def provide_a(self, builder: AssistedBuilder[EmptyClass]) ->
EmptyClass:
return builder.build()
injector = Injector(MyModule)
with pytest.raises(Error):
- injector.get(A)
+ injector.get(EmptyClass)
def test_newtype_integration_works():
@@ -1446,6 +1441,14 @@
assert get_bindings(function3) == {'a': int}
+ # Let's verify that the inject/noninjectable ordering doesn't matter
+ @noninjectable('b')
+ @inject
+ def function3b(a: int, b: str) -> None:
+ pass
+
+ 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:
@@ -1472,3 +1475,12 @@
pass
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
+
+ assert get_bindings(function8) == {}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.18.3/requirements-dev.txt
new/injector-0.19.0/requirements-dev.txt
--- old/injector-0.18.3/requirements-dev.txt 1970-01-01 01:00:00.000000000
+0100
+++ new/injector-0.19.0/requirements-dev.txt 2021-12-20 23:18:57.000000000
+0100
@@ -0,0 +1,6 @@
+pytest
+pytest-cov>=2.5.1
+dataclasses;python_version<"3.7"
+mypy;implementation_name=="cpython"
+black;implementation_name=="cpython"
+check-manifest
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.18.3/requirements.txt
new/injector-0.19.0/requirements.txt
--- old/injector-0.18.3/requirements.txt 1970-01-01 01:00:00.000000000
+0100
+++ new/injector-0.19.0/requirements.txt 2021-12-20 23:18:57.000000000
+0100
@@ -0,0 +1 @@
+typing_extensions>=3.7.4;python_version<"3.9"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/injector-0.18.3/setup.py new/injector-0.19.0/setup.py
--- old/injector-0.18.3/setup.py 2020-02-03 15:35:27.000000000 +0100
+++ new/injector-0.19.0/setup.py 2021-12-20 23:18:57.000000000 +0100
@@ -6,6 +6,13 @@
warnings.filterwarnings("always", module=__name__)
+def obtain_requirements(file_name):
+ with open(file_name) as fd_in:
+ for line in fd_in:
+ if '#' not in line:
+ yield line.strip()
+
+
class PyTest(Command):
user_options = []
@@ -34,6 +41,11 @@
version = read_injector_variable('__version__')
version_tag = read_injector_variable('__version_tag__')
+
+requirements = list(obtain_requirements('requirements.txt'))
+requirements_dev = list(obtain_requirements('requirements-dev.txt'))
+
+
try:
import pypandoc
@@ -48,8 +60,8 @@
setup(
name='injector',
- url='http://github.com/alecthomas/injector',
- download_url='http://pypi.python.org/pypi/injector',
+ url='https://github.com/alecthomas/injector',
+ download_url='https://pypi.org/project/injector/',
version=version,
options=dict(egg_info=dict(tag_build=version_tag)),
description=description,
@@ -61,6 +73,7 @@
author='Alec Thomas',
author_email='[email protected]',
cmdclass={'test': PyTest},
+ extras_require={'dev': requirements_dev},
keywords=[
'Dependency Injection',
'DI',
@@ -69,5 +82,5 @@
'IoC',
'Inversion of Control container',
],
- install_requires=['typing_extensions>=3.7.4'],
+ install_requires=requirements,
)