From f9b10f9eda1b636b29cbba6add34cd9e6f7463d8 Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Thu, 27 Jul 2023 02:34:21 +0200 Subject: [PATCH 01/12] Pin dev dependency versions (#225) This will let us avoid mistakes with some of our dependencies transparently updating and breaking our CI. --- .github/workflows/ci.yml | 2 +- MANIFEST.in | 1 + requirements-dev.in | 14 ++++++++++++++ requirements-dev.txt | 23 ++++++++++++++++++----- 4 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 requirements-dev.in diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa60e78..57df427 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: 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 --git a/MANIFEST.in b/MANIFEST.in index e58eddb..78c6184 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ include *.py include *.toml +include requirements-dev.in include *.txt include CHANGES include COPYING diff --git a/requirements-dev.in b/requirements-dev.in new file mode 100644 index 0000000..dbe389d --- /dev/null +++ b/requirements-dev.in @@ -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 --git a/requirements-dev.txt b/requirements-dev.txt index f6ac27d..7e6b598 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,18 @@ -pytest -pytest-cov>=2.5.1 -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 From 80041f485d19b1385c137d31effbff3aa073ad09 Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Thu, 27 Jul 2023 02:44:06 +0200 Subject: [PATCH 02/12] Release version 0.21.0 (#226) --- CHANGES | 9 +++++++++ injector/__init__.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index f95fea1..e85fc96 100644 --- a/CHANGES +++ b/CHANGES @@ -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 --git a/injector/__init__.py b/injector/__init__.py index 90dc4bd..a331aef 100644 --- a/injector/__init__.py +++ b/injector/__init__.py @@ -61,7 +61,7 @@ __author__ = 'Alec Thomas ' -__version__ = '0.20.1' +__version__ = '0.21.0' __version_tag__ = '' log = logging.getLogger('injector') From d7a1a7a728d20151bd54b9b8b7adb94d25b2e848 Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Wed, 6 Sep 2023 01:45:39 +0200 Subject: [PATCH 03/12] Drop official Python 3.7 support (#230) It's really old now and it reached its end of life stage. Supporting the last four minor versions (3.8-3.11) should be good enough. --- .github/workflows/ci.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57df427..0105e73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "pypy3.7", "pypy3.8", "pypy3.9", "pypy3.10"] + python-version: [3.8, 3.9, "3.10", "3.11", "pypy3.8", "pypy3.9", "pypy3.10"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/README.md b/README.md index 7e76e81..68b03a6 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ The core values of Injector are: * Documentation: https://injector.readthedocs.org * Change log: https://injector.readthedocs.io/en/latest/changelog.html -Injector works with CPython 3.7+ and PyPy 3 implementing Python 3.7+. +Injector works with CPython 3.8+ and PyPy 3 implementing Python 3.8+. A Quick Example --------------- From 5610e2aefdb43ca64b2c115542af1f67df47671a Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Wed, 6 Sep 2023 01:48:40 +0200 Subject: [PATCH 04/12] Remove some no longer necessary code (#231) We no longer support Python versions old enough to warrant these workarounds. --- injector/__init__.py | 4 ---- injector_test.py | 16 ++++------------ 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/injector/__init__.py b/injector/__init__.py index a331aef..e144ff2 100644 --- a/injector/__init__.py +++ b/injector/__init__.py @@ -1119,10 +1119,6 @@ def get_bindings(callable: Callable) -> Dict[str, type]: >>> get_bindings(function3) {'a': } - >>> import sys, pytest - >>> if sys.version_info < (3, 7, 0): - ... pytest.skip('Python 3.7.0 required for sufficient Annotated support') - >>> # The simple case of no @inject but injection requested with Inject[...] >>> def function4(a: Inject[int], b: str) -> None: ... pass diff --git a/injector_test.py b/injector_test.py index 4b967ff..10087f2 100644 --- a/injector_test.py +++ b/injector_test.py @@ -1544,21 +1544,13 @@ def configure(binder): assert injector.get(UserID) == 123 -@pytest.mark.skipif(sys.version_info < (3, 6), reason="Requires Python 3.6+") def test_dataclass_integration_works(): import dataclasses - # Python 3.6+-only syntax below - exec( - """ -@inject -@dataclasses.dataclass -class Data: - name: str - """, - locals(), - globals(), - ) + @inject + @dataclasses.dataclass + class Data: + name: str def configure(binder): binder.bind(str, to='data') From db332ec765f24f6ff6f5b3ebfa976f4a20c75f0f Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Mon, 11 Sep 2023 23:11:50 +0200 Subject: [PATCH 05/12] Fix a bunch of typos (#232) --- injector/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/injector/__init__.py b/injector/__init__.py index e144ff2..f146db1 100644 --- a/injector/__init__.py +++ b/injector/__init__.py @@ -531,7 +531,7 @@ def install(self, module: _InstallableModuleType) -> None: In this context the module is one of the following: - * function taking the :class:`Binder` as it's only parameter + * function taking the :class:`Binder` as its only parameter :: @@ -540,7 +540,7 @@ def configure(binder): binder.install(configure) - * instance of :class:`Module` (instance of it's subclass counts) + * instance of :class:`Module` (instance of its subclass counts) :: @@ -1005,7 +1005,7 @@ def create_object(self, cls: Type[T], additional_kwargs: Any = None) -> T: def call_with_injection( self, callable: Callable[..., T], self_: Any = None, args: Any = (), kwargs: Any = {} ) -> T: - """Call a callable and provide it's dependencies if needed. + """Call a callable and provide its dependencies if needed. :param self_: Instance of a class callable belongs to if it's a method, None otherwise. From 395e7e8e53c63cb35ab70f53e1ecd6a3a8daef66 Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Mon, 11 Sep 2023 23:43:25 +0200 Subject: [PATCH 06/12] Expand call_with_injection() documentation (#233) I thought it could use some clarifications. --- injector/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/injector/__init__.py b/injector/__init__.py index f146db1..4136f8f 100644 --- a/injector/__init__.py +++ b/injector/__init__.py @@ -1007,6 +1007,14 @@ def call_with_injection( ) -> T: """Call a callable and provide its dependencies if needed. + Dependencies are provided when the callable is decorated with :func:`@inject ` + or some individual parameters are wrapped in :data:`Inject` – otherwise + ``call_with_injection()`` is equivalent to just calling the callable directly. + + If there is an overlap between arguments provided in ``args`` and ``kwargs`` + and injectable dependencies the provided values take precedence and no dependency + injection process will take place for the corresponding parameters. + :param self_: Instance of a class callable belongs to if it's a method, None otherwise. :param args: Arguments to pass to callable. From 82929b6eca69eddf70a4d5c078947012a3ce31df Mon Sep 17 00:00:00 2001 From: Pavlo Morozov <6754248+pavlomorozov@users.noreply.github.com> Date: Mon, 2 Oct 2023 10:38:48 +0200 Subject: [PATCH 07/12] Test example update (#236) * Test example update because of @with_injector gone according to https://github.com/python-injector/injector/issues/146 Reported also on https://github.com/python-injector/injector/issues/220 * Cleanup --- docs/testing.rst | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/testing.rst b/docs/testing.rst index 568cbc2..c5cca33 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -1,24 +1,23 @@ Testing with Injector ===================== -When you use unit test framework such as `unittest2` or `nose` you can also profit from `injector`. However, manually creating injectors and test classes can be quite annoying. There is, however, `with_injector` method decorator which has parameters just as `Injector` construtor and installes configured injector into class instance on the time of method call:: +When you use unit test framework such as `unittest2` or `nose` you can also profit from `injector`. :: import unittest - from injector import Module, with_injector, inject + from injector import Injector, Module + class UsernameModule(Module): def configure(self, binder): binder.bind(str, 'Maria') + class TestSomethingClass(unittest.TestCase): - @with_injector(UsernameModule()) + def setUp(self): - pass + self.__injector = Injector(UsernameModule()) - @inject - def test_username(self, username: str): + def test_username(self): + username = self.__injector.get(str) self.assertEqual(username, 'Maria') -**Each** method call re-initializes :class:`~injector.Injector` - if you want to you can also put :func:`~injector.with_injector` decorator on class constructor. - -After such call all :func:`~injector.inject`-decorated methods will work just as you'd expect them to work. From c7d1f6ebf43820822c2b62388ffbf48dba1b31e7 Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Wed, 13 Dec 2023 22:48:09 +0100 Subject: [PATCH 08/12] Provide ReadTheDocs configuration (#275) (#240) RTD has been requiring a configuration file for a while, this change should resolve the currently ongoing documentation build failures. --- .readthedocs.yaml | 22 ++++++++++++++++++++++ MANIFEST.in | 1 + 2 files changed, 23 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..31ea7c5 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,22 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.12" + +sphinx: + configuration: docs/conf.py + # TODO: Enable this when we get rid of the existing warnings + # fail_on_warning: true + +python: + install: + - method: pip + path: . +# TODO: pin the development dependency versions +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +# - requirements: docs/requirements.txt diff --git a/MANIFEST.in b/MANIFEST.in index 78c6184..7277df6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -11,3 +11,4 @@ recursive-include docs *.html recursive-include docs *.py recursive-include docs *.rst recursive-include docs Makefile +exclude .readthedocs.yaml From 4b96e21b7c80a92805d70ef979a7d800df20d1cb Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Wed, 13 Dec 2023 22:53:26 +0100 Subject: [PATCH 09/12] Reduce repetitive CI builds when pushing to the main repository (#241) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I like to push branches to the upstream repository and create PRs from that. With the previous configuration all jobs were triggered twice – once for the push event, once for the pull_request event. That was wasting resources and creating noise in the PRs. --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0105e73..ff9e31a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,10 @@ name: CI -on: [push, pull_request] +on: + push: + branches: + - master + pull_request: jobs: build: From 9821040969a4a5bf3f71c3535059cedf259d199e Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Wed, 13 Dec 2023 23:09:11 +0100 Subject: [PATCH 10/12] Update the development dependencies to the latest versions (#242) Just keeping them up to date. --- requirements-dev.txt | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 7e6b598..a4d8209 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,18 +1,28 @@ +aiohttp==3.9.1 +aiosignal==1.3.1 +async-timeout==4.0.3 +attrs==23.1.0 black==23.3.0;implementation_name=="cpython" -build==0.10.0 +build==1.0.3 check-manifest==0.49 -click==8.1.3 -coverage==7.2.7 -exceptiongroup==1.1.1 +click==8.1.7 +coverage==7.3.2 +exceptiongroup==1.2.0 +frozenlist==1.4.0 +idna==3.6 +importlib-metadata==7.0.0 iniconfig==2.0.0 -mypy==1.4.1;implementation_name=="cpython" +multidict==6.0.4 +mypy==1.7.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 +packaging==23.2 +pathspec==0.12.1 +platformdirs==4.1.0 +pluggy==1.3.0 +pyproject-hooks==1.0.0 +pytest==7.4.3 pytest-cov==4.1.0 tomli==2.0.1 -typing_extensions==4.7.0 +typing-extensions==4.9.0 +yarl==1.9.4 +zipp==3.17.0 From dc5081d5f4832268bba722b249efedee474d2aeb Mon Sep 17 00:00:00 2001 From: Jakub Stasiak Date: Wed, 13 Dec 2023 23:18:08 +0100 Subject: [PATCH 11/12] Pin the documentation dependencies' versions (#243) This should make the documentation process more reliable and reproducible across environments. --- .readthedocs.yaml | 4 +--- MANIFEST.in | 2 +- requirements-docs.in | 8 ++++++++ requirements-docs.txt | 24 ++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 requirements-docs.in create mode 100644 requirements-docs.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 31ea7c5..ce3037d 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -17,6 +17,4 @@ python: install: - method: pip path: . -# TODO: pin the development dependency versions -# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html -# - requirements: docs/requirements.txt + - requirements: requirements-docs.txt diff --git a/MANIFEST.in b/MANIFEST.in index 7277df6..33315f6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,6 @@ include *.py include *.toml -include requirements-dev.in +include requirements-*.in include *.txt include CHANGES include COPYING diff --git a/requirements-docs.in b/requirements-docs.in new file mode 100644 index 0000000..fa046b6 --- /dev/null +++ b/requirements-docs.in @@ -0,0 +1,8 @@ +# The documentation-specific development dependencies. +# +# We generate requirements-dev.txt from this file by running +# +# pip install -r requirements-docs.in && pip freeze > requirements-docs.txt +# +# and then modifying the file manually to restrict black and mypy to CPython +sphinx diff --git a/requirements-docs.txt b/requirements-docs.txt new file mode 100644 index 0000000..ae4767a --- /dev/null +++ b/requirements-docs.txt @@ -0,0 +1,24 @@ +alabaster==0.7.13 +Babel==2.14.0 +certifi==2023.11.17 +charset-normalizer==3.3.2 +docutils==0.20.1 +idna==3.6 +imagesize==1.4.1 +importlib-metadata==7.0.0 +Jinja2==3.1.2 +MarkupSafe==2.1.3 +packaging==23.2 +pygments==2.17.2 +pytz==2023.3.post1 +requests==2.31.0 +snowballstemmer==2.2.0 +sphinx==7.1.2 +sphinxcontrib-applehelp==1.0.4 +sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-htmlhelp==2.0.1 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-serializinghtml==1.1.5 +urllib3==2.1.0 +zipp==3.17.0 From 44ac4e0b09daeb5788eb0944cb32e7e815bcd092 Mon Sep 17 00:00:00 2001 From: np-kyokyo <119554975+np-kyokyo@users.noreply.github.com> Date: Thu, 25 Jan 2024 00:21:13 +0900 Subject: [PATCH 12/12] Make example in documentation runnable (#247) With all required imports and `NewType` declarations. --- docs/terminology.rst | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/terminology.rst b/docs/terminology.rst index 26bc8ed..6e34535 100644 --- a/docs/terminology.rst +++ b/docs/terminology.rst @@ -78,7 +78,12 @@ Injection is the process of providing an instance of a type, to a method that us Here is an example of injection on a module provider method, and on the constructor of a normal class:: - from injector import inject + from typing import NewType + + from injector import Binder, Module, inject, provider + + Name = NewType("Name", str) + Description = NewType("Description", str) class User: @inject @@ -86,14 +91,12 @@ Here is an example of injection on a module provider method, and on the construc self.name = name self.description = description - class UserModule(Module): - def configure(self, binder): + def configure(self, binder: Binder): binder.bind(User) - class UserAttributeModule(Module): - def configure(self, binder): + def configure(self, binder: Binder): binder.bind(Name, to='Sherlock') @provider