Skip to content
This repository has been archived by the owner on Aug 10, 2024. It is now read-only.

Commit

Permalink
Add @Injectable & local type hint support
Browse files Browse the repository at this point in the history
- Renaming @service to @Injectable
- Adding `factory_method` argument for @Injectable
- Use of locals for type hint resolution in @Inject, @wire, @Injectable
  • Loading branch information
Finistere committed Apr 26, 2022
1 parent 900417e commit 98aa25b
Show file tree
Hide file tree
Showing 79 changed files with 1,923 additions and 881 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.cache
.python-version
.tox
.tox-venv
.eggs
*.pyc
*.egg-info
Expand Down
22 changes: 11 additions & 11 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ Injection

.. code-block:: python
from antidote import inject, service
from antidote import inject, injectable
@service
@injectable
class Database:
pass
Expand Down Expand Up @@ -185,18 +185,18 @@ You can also retrieve the dependency by hand with :code:`world.get`:
world.get[Database](Database) # with type hint, enforced when possible
Service
-------
Injectable
----------

Services are classes for which Antidote provides an instance. It can be a singleton or not.
Scopes are also supported. Every method is injected by default, relying on annotated type
hints and markers such as :code:`inject.me()`:
Any class marked as `@injectable` can be provided by Antidote. It can be a singleton or not.
Scopes and a factory method are also supported. Every method is injected by default, relying on
annotated type hints and markers such as :code:`inject.me()`:

.. code-block:: python
from antidote import service, inject
from antidote import injectable, inject
@service(singleton=False)
@injectable(singleton=False)
class QueryBuilder:
# methods are also injected by default
def __init__(self, db: Database = inject.me()):
Expand Down Expand Up @@ -378,9 +378,9 @@ Testing and Debugging

.. code-block:: python
from antidote import service, inject
from antidote import injectable, inject
@service
@injectable
class Database:
pass
Expand Down
27 changes: 27 additions & 0 deletions bin/tox.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash

set -euo pipefail

project_dir="$(dirname "$(dirname "$(readlink -f "$0")")")"
cd "$project_dir"

if [[ "${INSIDE_DOCKER:-}" == "yes" ]]; then
venv_dir="$project_dir/.tox-venv"
if [[ ! -d "$venv_dir" ]]; then
python3.10 -m venv "$venv_dir"
source "$venv_dir/bin/activate"
pip install -r "$project_dir/requirements/dev.txt"
else
source "$venv_dir/bin/activate"
fi
tox "$@"
else
docker run \
--user "$(id -u):$(id -g)" \
-v "$project_dir:/antidote" \
-w /antidote \
-it \
-e INSIDE_DOCKER=yes \
quay.io/pypa/manylinux2014_x86_64:latest \
bash -c "/antidote/bin/tox.sh $*"
fi
86 changes: 86 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,92 @@ Most, if not all, the API is annotated with decorators such as :code:`@API.publi
the given functionality can be relied upon.


1.3.0 (2022-04-26)
==================


Deprecation
-----------

- :py:func:`.service` is deprecated in favor of :py:func:`.injectable` which is a drop-in
replacement.
- :py:func:`.inject` used to raise a :py:exc:`RuntimeError` when specifying
:code:`ignore_type_hints=True` and no injections were found. It now raises
:py:exc:`.NoInjectionsFoundError`
- :py:meth:`.Wiring.wire` used to return the wired class, it won't be the case anymore.


Features
--------

- Add local type hint support with :code:`type_hints_locals` argument for :py:func:`.inject`,
:py:func:`.injectable`, :py:class:`.implements` and :py:func:`.wire`. The default behavior can
be configured globally with :py:obj:`.config`. Auto-detection is done through :py:mod:`inspect`
and frame manipulation. It's mostly helpful inside tests.

.. code-block:: python
from __future__ import annotations
from antidote import config, inject, injectable, world
def function() -> None:
@injectable
class Dummy:
pass
@inject(type_hints_locals='auto')
def f(dummy: Dummy = inject.me()) -> Dummy:
return dummy
assert f() is world.get(Dummy)
function()
config.auto_detect_type_hints_locals = True
def function2() -> None:
@injectable
class Dummy:
pass
@inject
def f(dummy: Dummy = inject.me()) -> Dummy:
return dummy
assert f() is world.get(Dummy)
function2()
- Add :code:`factory_method` to :py:func:`.injectable` (previous :py:func:`.service`)

.. code-block:: python
from __future__ import annotations
from antidote import injectable
@injectable(factory_method='build')
class Dummy:
@classmethod
def build(cls) -> Dummy:
return cls()
- Added :code:`ignore_type_hints` argument to :py:class:`.Wiring` and :py:func:`.wire`.
- Added :code:`type_hints_locals` and :code:`class_in_localns` argument to :py:class:`.Wiring.wire`.


Bug fix
-------

- Fix :code:`Optional` detection in predicate constraints.



1.2.0 (2022-04-19)
==================
Expand Down
14 changes: 7 additions & 7 deletions docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ and where it should be injected:

.. testcode:: why_dependency_injection

from antidote import service, inject
from antidote import injectable, inject

@service
@injectable
class Database:
def query(self, sql: str) -> Any:
pass
Expand All @@ -84,7 +84,7 @@ so you want to avoid it if possible. A simple way to do

.. testcode:: why_dependency_injection

# services.py
# injectables.py
from typing import Optional

__db: Optional[Database] = None
Expand Down Expand Up @@ -136,13 +136,13 @@ to do all that wiring properly. Here is the same example with Antidote:

.. testcode:: why_dependency_injection

from antidote import service, inject, Constants, const
from antidote import injectable, inject, Constants, const

class Config(Constants):
DB_HOST = const('localhost')
DB_PORT = const(5432)

@service
@injectable
class Database:
def __init__(self,
host: str = Config.DB_HOST,
Expand Down Expand Up @@ -267,9 +267,9 @@ Let's see how the same example looks with Antidote:

# my_service.py
# Antidote
from antidote import service
from antidote import injectable

@service
@injectable
class MyService:
pass

Expand Down
12 changes: 6 additions & 6 deletions docs/how_to.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ existing dependency:

.. testcode:: how_to_annotated_type_hints

from antidote import service, inject, Inject
from antidote import injectable, inject, Inject

@service
@injectable
class Database:
pass

Expand Down Expand Up @@ -105,9 +105,9 @@ arguments:

.. testcode:: how_to_test

from antidote import inject, service
from antidote import inject, injectable

@service
@injectable
class Database:
pass

Expand Down Expand Up @@ -200,9 +200,9 @@ tree with :py:func:`.world.debug`:

.. testcode:: how_to_debug

from antidote import world, service, inject
from antidote import world, injectable, inject

@service
@injectable
class MyService:
pass

Expand Down
8 changes: 4 additions & 4 deletions docs/recipes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,9 @@ Lazily calling a method requires the class to be :py:class:`.Service`.

.. testcode:: recipes_lazy

from antidote import LazyMethodCall, service
from antidote import LazyMethodCall, injectable

@service
@injectable
class ExampleCom:
def get(url):
return requests.get(f"https://example.com{url}")
Expand Down Expand Up @@ -489,8 +489,8 @@ To use the newly created scope, use :code:`scope` parameters:

.. doctest:: recipes_scope

>>> from antidote import service
>>> @service(scope=REQUEST_SCOPE)
>>> from antidote import injectable
>>> @injectable(scope=REQUEST_SCOPE)
... class Dummy:
... pass

Expand Down
34 changes: 27 additions & 7 deletions docs/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ world

.. doctest:: world_get

>>> from antidote import world, service
>>> @service
>>> from antidote import world, injectable
>>> @injectable
... class Dummy:
... pass
>>> world.get(Dummy)
Expand Down Expand Up @@ -57,19 +57,34 @@ world.test.override
:members:



Utilities
=========


.. automodule:: antidote._config
:members:

.. py:data:: config
:type: antidote._config.Config

Singleton instance of :py:class:`.Config`


.. automodule:: antidote.utils
:members: is_compiled, validated_scope, validate_injection



Lib
===


Service
-------
Injectable
----------

.. automodule:: antidote.lib.injectable
:members:

.. automodule:: antidote.service
:members: service
Expand Down Expand Up @@ -151,13 +166,18 @@ Inject

.. py:function:: inject
Singleton instance of :py:class:`~.core.injection.Inject`
Singleton instance of :py:class:`~.core.injection.Injector`

.. autoclass:: antidote.core.injection.Inject
.. autoclass:: antidote.core.injection.Injector

.. automethod:: __call__
.. automethod:: me
.. automethod:: get
.. py:attribute:: get
:type: antidote.core.getter.DependencyGetter

:py:class:`.DependencyGetter` to explicit state the dependencies to be retrieved.
It follows the same API as :py:obj:`.world.get`.


.. autoclass:: antidote.core.getter.DependencyGetter
:members: __call__, __getitem__
Expand Down
Loading

0 comments on commit 98aa25b

Please sign in to comment.