inject

Python dependency injection

https://github.com/ivankorobkov/python-inject

Science Score: 26.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
  • Academic publication links
  • Committers with academic emails
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (10.9%) to scientific vocabulary
Last synced: 10 months ago · JSON representation

Repository

Python dependency injection

Basic Info
  • Host: GitHub
  • Owner: ivankorobkov
  • License: apache-2.0
  • Language: Python
  • Default Branch: master
  • Homepage:
  • Size: 464 KB
Statistics
  • Stars: 731
  • Watchers: 17
  • Forks: 84
  • Open Issues: 29
  • Releases: 0
Created over 16 years ago · Last pushed 11 months ago
Metadata Files
Readme Changelog License

README.md

python-inject Build Status

Dependency injection the python way, the good way.

Key features

  • Fast.
  • Thread-safe.
  • Simple to use.
  • Does not steal class constructors.
  • Does not try to manage your application object graph.
  • Transparently integrates into tests.
  • Autoparams leveraging type annotations.
  • Supports type hinting in Python 3.5+.
  • Supports Python 3.9+ (v5.*), 3.5-3.8 (v4.*) and Python 2.7–3.5 (v3.*).
  • Supports context managers.

Python Support

| Python | Inject Version | |---------|----------------| | 3.9+ | 5.0+ | | 3.6-3.8 | 4.1+, < 5.0 | | 3.5 | 4.0 | | < 3.5 | 3.* |

Installation

Use pip to install the lastest version:

bash pip install inject

Autoparams example

@inject.autoparams returns a decorator which automatically injects arguments into a function that uses type annotations. This is supported only in Python >= 3.5.

python @inject.autoparams def refresh_cache(cache: RedisCache, db: DbInterface): pass

There is an option to specify which arguments we want to inject without attempts of injecting everything:

python @inject.autoparams('cache', 'db') def sign_up(name, email, cache: RedisCache, db: DbInterface): pass

It is also acceptable to use explicit curly braces notation (@inject.autoparams()) for non-parameterized decorations — it will be treated the same as @inject.autoparams.

Step-by-step example

```python

Import the inject module.

import inject

inject.instance requests dependencies from the injector.

def foo(bar): cache = inject.instance(Cache) cache.save('bar', bar)

inject.params injects dependencies as keyword arguments or positional argument.

Also you can use @inject.autoparams in Python 3.5, see the example above.

@inject.params(cache=Cache, user=CurrentUser) def baz(foo, cache=None, user=None): cache.save('foo', foo, user)

this can be called in different ways:

with injected arguments

baz('foo')

with positional arguments

baz('foo', my_cache)

with keyword arguments

baz('foo', mycache, user=currentuser)

inject.param is deprecated, use inject.params instead.

@inject.param('cache', Cache) def bar(foo, cache=None): cache.save('foo', foo)

inject.attr creates properties (descriptors) which request dependencies on access.

class User(object): cache = inject.attr(Cache)

def __init__(self, id):
    self.id = id

def save(self):
    self.cache.save('users', self)

@classmethod
def load(cls, id):
    return cls.cache.load('users', id)

Create an optional configuration.

def my_config(binder): binder.bind(Cache, RedisCache('localhost:1234'))

Configure a shared injector.

inject.configure(my_config)

Instantiate User as a normal class. Its cache dependency is injected when accessed.

user = User(10) user.save()

Call the functions, the dependencies are automatically injected.

foo('Hello') bar('world') ```

Context managers

Binding a class to an instance of a context manager (through bind or bind_to_constructor) or to a function decorated as a context manager leads to the context manager to be used as is, not via with statement.

```python @contextlib.contextmanager def getfilesync(): obj = MockFile() yield obj obj.destroy()

@contextlib.asynccontextmanager async def getconnasync(): obj = MockConnection() yield obj obj.destroy()

def config(binder): binder.bindtoprovider(MockFile, getfilesync) binder.bind(int, 100) binder.bindtoprovider(str, lambda: "Hello") binder.bindtoprovider(MockConnection, getconnsync)

inject.configure(config)

@inject.autoparams() def example(conn: MockConnection, file: MockFile): # Connection and file will be automatically destroyed on exit. pass ```

Usage with Django

Django can load some modules multiple times which can lead to InjectorException: Injector is already configured. You can use configure(once=True) which is guaranteed to run only once when the injector is absent: python import inject inject.configure(my_config, once=True)

Testing

In tests use inject.configure(callable, clear=True) to create a new injector on setup, and optionally inject.clear() to clean up on tear down: ```python class MyTest(unittest.TestCase): def setUp(self): inject.configure(lambda binder: binder .bind(Cache, MockCache()) \ .bind(Validator, TestValidator()), clear=True)

def tearDown(self):
    inject.clear()

```

Composable configurations

You can reuse configurations and override already registered dependencies to fit the needs in different environments or specific tests. ```python def base_config(binder): # ... more dependencies registered here binder.bind(Validator, RealValidator()) binder.bind(Cache, RedisCache('localhost:1234'))

def tests_config(binder):
    # reuse existing configuration
    binder.install(base_config)

    # override only certain dependencies
    binder.bind(Validator, TestValidator())
    binder.bind(Cache, MockCache())

inject.configure(tests_config, allow_override=True, clear=True)

```

Thread-safety

After configuration the injector is thread-safe and can be safely reused by multiple threads.

Binding types

Instance bindings always return the same instance:

python redis = RedisCache(address='localhost:1234') def config(binder): binder.bind(Cache, redis)

Constructor bindings create a singleton on injection:

python def config(binder): # Creates a redis cache singleton on first injection. binder.bind_to_constructor(Cache, lambda: RedisCache(address='localhost:1234'))

Provider bindings call the provider on injection:

```python def getmythreadlocalcache(): pass

def config(binder): # Executes the provider on each injection. binder.bindtoprovider(Cache, getmythreadlocalcache) ```

Runtime bindings automatically create singletons on injection, require no configuration. For example, only the Config class binding is present, other bindings are runtime:

```python class Config(object): pass

class Cache(object): config = inject.attr(Config)

class Db(object): config = inject.attr(Config)

class User(object): cache = inject.attr(Cache) db = inject.attr(Db)

@classmethod
def load(cls, user_id):
    return cls.cache.load('users', user_id) or cls.db.load('users', user_id)

inject.configure(lambda binder: binder.bind(Config, loadconfigfile())) user = User.load(10) ```

Disabling runtime binding

Sometimes runtime binding leads to unexpected behaviour. Say if you forget to bind an instance to a class, inject will try to implicitly instantiate it.

If an instance is unintentionally created with default arguments it may lead to hard-to-debug bugs. To disable runtime binding and make sure that only explicitly bound instances are injected, pass bind_in_runtime=False to inject.configure.

In this case inject immediately raises InjectorException when the code tries to get an unbound instance.

Keys

It is possible to use any hashable object as a binding key. For example:

```python import inject

inject.configure(lambda binder: \ binder.bind('host', 'localhost') \ binder.bind('port', 1234)) ```

Why no scopes?

I've used Guice and Spring in Java for a lot of years, and I don't like their scopes. python-inject by default creates objects as singletons. It does not need a prototype scope as in Spring or NO_SCOPE as in Guice because python-inject does not steal your class constructors. Create instances the way you like and then inject dependencies into them.

Other scopes such as a request scope or a session scope are fragile, introduce high coupling, and are difficult to test. In python-inject write custom providers which can be thread-local, request-local, etc.

For example, a thread-local current user provider:

```python import inject import threading

Given a user class.

class User(object): pass

Create a thread-local current user storage.

_LOCAL = threading.local()

def getcurrentuser(): return getattr(_LOCAL, 'user', None)

def setcurrentuser(user): _LOCAL.user = user

Bind User to a custom provider.

inject.configure(lambda binder: binder.bindtoprovider(User, getcurrentuser))

Inject the current user.

@inject.params(user=User) def foo(user): pass ```

Links

  • Project: https://github.com/ivankorobkov/python-inject

License

Apache License 2.0

Contributors

Owner

  • Name: Ivan Korobkov
  • Login: ivankorobkov
  • Kind: user
  • Location: Bali, Indonesia

GitHub Events

Total
  • Create event: 5
  • Commit comment event: 1
  • Issues event: 6
  • Watch event: 42
  • Delete event: 1
  • Member event: 1
  • Issue comment event: 29
  • Push event: 15
  • Pull request review comment event: 13
  • Pull request review event: 14
  • Pull request event: 25
  • Fork event: 4
Last Year
  • Create event: 5
  • Commit comment event: 1
  • Issues event: 6
  • Watch event: 42
  • Delete event: 1
  • Member event: 1
  • Issue comment event: 29
  • Push event: 15
  • Pull request review comment event: 13
  • Pull request review event: 14
  • Pull request event: 25
  • Fork event: 4

Committers

Last synced: over 2 years ago

All Time
  • Total Commits: 292
  • Total Committers: 28
  • Avg Commits per committer: 10.429
  • Development Distribution Score (DDS): 0.216
Past Year
  • Commits: 12
  • Committers: 3
  • Avg Commits per committer: 4.0
  • Development Distribution Score (DDS): 0.417
Top Committers
Name Email Commits
Ivan Korobkov i****v@g****m 229
Sebastian Buczyński n****a@g****m 9
Johan Westin j****n@g****m 7
Alexander Costas a****s@m****m 7
Dmitry Balabka d****a@g****m 4
Alexander Panko g****d@p****u 3
SixExtreme 9****9@q****m 3
Ivan Korobkov i****v@i****u 3
EspenAlbert a****n@g****m 3
Harro van der Kroft w****o 2
Oleksandr Fedorov a****f@g****m 2
jmeyer j****r@c****m 2
Michael Scharf (Github) g****b@s****r 2
Andrew William Borba a****a@f****m 2
Marcelo Avancini m****n@g****m 1
Michael Peick m****k@g****t 1
Didier Roche d****s@u****m 1
Chris Selvaraj c****j@g****m 1
Nick Harris n****s@c****m 1
Samiur Rahman s****r@u****m 1
Nikolay Tsvetanov n****v@g****m 1
Viktor Kerkez a****a@g****m 1
jaime p****y@g****m 1
Alex Grover h****o@a****e 1
hf-kklein k****n@h****e 1
Yuya Unno u****o@g****m 1
Karthikeyan Singaravelan t****i@g****m 1
45deg 4****g 1
Committer Domains (Top 20 + Academic)

Issues and Pull Requests

Last synced: 10 months ago

All Time
  • Total issues: 69
  • Total pull requests: 56
  • Average time to close issues: 3 months
  • Average time to close pull requests: 3 months
  • Total issue authors: 57
  • Total pull request authors: 36
  • Average comments per issue: 2.97
  • Average comments per pull request: 1.54
  • Merged pull requests: 42
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 6
  • Pull requests: 13
  • Average time to close issues: 2 days
  • Average time to close pull requests: 1 day
  • Issue authors: 6
  • Pull request authors: 3
  • Average comments per issue: 2.33
  • Average comments per pull request: 0.31
  • Merged pull requests: 6
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
  • ivankorobkov (5)
  • andrewborba10 (3)
  • dbalabka (3)
  • shtlrs (2)
  • nikordaris (2)
  • ancostas (2)
  • 1995chen (2)
  • Gr1N (1)
  • dimma837 (1)
  • suve (1)
  • scharf (1)
  • krcourville (1)
  • alex-grover (1)
  • mayesca (1)
  • dineshtrivedi (1)
Pull Request Authors
  • pyctrl (10)
  • dbalabka (4)
  • andrewborba10 (3)
  • Enforcer (3)
  • markhobson (2)
  • Sikerdebaard (2)
  • wushengjianzong (2)
  • fhdufhdu (2)
  • Sukonnik-Illia (2)
  • EspenAlbert (2)
  • PerchunPak (2)
  • juancerezo (2)
  • panki (2)
  • unnonouno (1)
  • fzyukio (1)
Top Labels
Issue Labels
Question (1)
Pull Request Labels

Packages

  • Total packages: 2
  • Total downloads:
    • pypi 1,466,883 last-month
  • Total docker downloads: 90
  • Total dependent packages: 16
    (may contain duplicates)
  • Total dependent repositories: 93
    (may contain duplicates)
  • Total versions: 29
  • Total maintainers: 1
pypi.org: inject

Python dependency injection framework.

  • Versions: 28
  • Dependent Packages: 16
  • Dependent Repositories: 93
  • Downloads: 1,466,883 Last month
  • Docker Downloads: 90
Rankings
Downloads: 0.3%
Dependent packages count: 0.7%
Dependent repos count: 1.5%
Average: 2.3%
Stargazers count: 2.6%
Docker downloads count: 3.9%
Forks count: 4.6%
Maintainers (1)
Last synced: 11 months ago
conda-forge.org: inject
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
Rankings
Stargazers count: 15.5%
Forks count: 20.5%
Average: 30.3%
Dependent repos count: 34.0%
Dependent packages count: 51.2%
Last synced: 11 months ago