# python-inject [![Build Status](https://travis-ci.org/ivankorobkov/python-inject.svg?branch=master)](https://travis-ci.org/ivankorobkov/python-inject)
Dependency injection the python way, the good way. Not a port of Guice or Spring.
## 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.
* Supports Python 3.5+ (`v4.*`) and Python 2.7–3.5 (`v3.*`).
* Supports type hinting in Python 3.5+.
* Autoparams leveraging type annotations.
## Python Support
| Python | Inject Version |
|--------|----------------|
| 3.6+ | 4.1+ |
| 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
```
## 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', my_cache, user=current_user)
# `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.install(my_config2) # Add bindings from another config.
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')
```
## Usage with Django
Django can load some modules multiple times which can lead to
`InjectorException: Injector is already configured`. You can use `configure_once` which
is guaranteed to run only once when the injector is absent:
```python
import inject
inject.configure_once(my_config)
```
## Testing
In tests use `inject.clear_and_configure(callable)` 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.clear_and_configure(lambda binder: binder
.bind(Cache, Mock()) \
.bind(Validator, TestValidator()))
def tearDown(self):
inject.clear()
```
## 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 get_my_thread_local_cache():
pass
def config(binder):
# Executes the provider on each injection.
binder.bind_to_provider(Cache, get_my_thread_local_cache)
```
**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, load_config_file()))
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`, `inject.configure_once` or `inject.clear_and_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 get_current_user():
return getattr(_LOCAL, 'user', None)
def set_current_user(user):
_LOCAL.user = user
# Bind User to a custom provider.
inject.configure(lambda binder: binder.bind_to_provider(User, get_current_user))
# 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
* Ivan Korobkov [@ivankorobkov](https://github.com/ivankorobkov)
* Jaime Wyant [@jaimewyant](https://github.com/jaimewyant)
* Sebastian Buczyński [@Enforcer](https://github.com/Enforcer)
* Oleksandr Fedorov [@Fedorof](https://github.com/Fedorof)
* cselvaraj [@cselvaraj](https://github.com/cselvaraj)
* 陆雨晴 [@SixExtreme](https://github.com/SixExtreme)
* Andrew William Borba [@andrewborba10](https://github.com/andrewborba10)
* jdmeyer3 [@jdmeyer3](https://github.com/jdmeyer3)
* Alex Grover [@ajgrover](https://github.com/ajgrover)
* Harro van der Kroft [@wisepotato](https://github.com/wisepotato)
* Samiur Rahman [@samiur](https://github.com/samiur)
* 45deg [@45deg](https://github.com/45deg)
* Alexander Nicholas Costas [@ancostas](https://github.com/ancostas)
* Dmitry Balabka [@dbalabka](https://github.com/dbalabka)
Raw data
{
"_id": null,
"home_page": "https://github.com/ivankorobkov/python-inject",
"name": "Inject",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "",
"author": "Ivan Korobkov",
"author_email": "ivan.korobkov@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/23/e2/04673185baadf9f4e175de51212fd2966eed2fca3236fdcfb7e732e4d09e/Inject-4.3.1.tar.gz",
"platform": "",
"description": "# python-inject [![Build Status](https://travis-ci.org/ivankorobkov/python-inject.svg?branch=master)](https://travis-ci.org/ivankorobkov/python-inject)\nDependency injection the python way, the good way. Not a port of Guice or Spring.\n\n## Key features\n* Fast.\n* Thread-safe.\n* Simple to use.\n* Does not steal class constructors.\n* Does not try to manage your application object graph.\n* Transparently integrates into tests.\n* Supports Python 3.5+ (`v4.*`) and Python 2.7\u20133.5 (`v3.*`).\n* Supports type hinting in Python 3.5+.\n* Autoparams leveraging type annotations.\n\n## Python Support\n\n| Python | Inject Version |\n|--------|----------------|\n| 3.6+ | 4.1+ |\n| 3.5 | 4.0 |\n| < 3.5 | 3.* |\n\n\n## Installation\nUse pip to install the lastest version:\n\n```bash\npip install inject\n```\n\n## Autoparams example\n`@inject.autoparams` returns a decorator which automatically injects arguments into a function \nthat uses type annotations. This is supported only in Python >= 3.5.\n\n```python\n@inject.autoparams()\ndef refresh_cache(cache: RedisCache, db: DbInterface):\n pass\n```\n\nThere is an option to specify which arguments we want to inject without attempts of \ninjecting everything:\n\n```python\n@inject.autoparams('cache', 'db')\ndef sign_up(name, email, cache: RedisCache, db: DbInterface):\n pass\n```\n\n## Step-by-step example\n```python\n# Import the inject module.\nimport inject\n\n\n# `inject.instance` requests dependencies from the injector.\ndef foo(bar):\n cache = inject.instance(Cache)\n cache.save('bar', bar)\n\n\n# `inject.params` injects dependencies as keyword arguments or positional argument. \n# Also you can use @inject.autoparams in Python 3.5, see the example above.\n@inject.params(cache=Cache, user=CurrentUser)\ndef baz(foo, cache=None, user=None):\n cache.save('foo', foo, user)\n\n# this can be called in different ways:\n# with injected arguments\nbaz('foo')\n\n# with positional arguments\nbaz('foo', my_cache)\n\n# with keyword arguments\nbaz('foo', my_cache, user=current_user)\n\n\n# `inject.param` is deprecated, use `inject.params` instead.\n@inject.param('cache', Cache)\ndef bar(foo, cache=None):\n cache.save('foo', foo)\n\n\n# `inject.attr` creates properties (descriptors) which request dependencies on access.\nclass User(object):\n cache = inject.attr(Cache)\n \n def __init__(self, id):\n self.id = id\n\n def save(self):\n self.cache.save('users', self)\n \n @classmethod\n def load(cls, id):\n return cls.cache.load('users', id)\n\n\n# Create an optional configuration.\ndef my_config(binder):\n binder.install(my_config2) # Add bindings from another config.\n binder.bind(Cache, RedisCache('localhost:1234'))\n\n# Configure a shared injector.\ninject.configure(my_config)\n\n\n# Instantiate User as a normal class. Its `cache` dependency is injected when accessed.\nuser = User(10)\nuser.save()\n\n# Call the functions, the dependencies are automatically injected.\nfoo('Hello')\nbar('world')\n```\n\n\n## Usage with Django\nDjango can load some modules multiple times which can lead to \n`InjectorException: Injector is already configured`. You can use `configure_once` which\nis guaranteed to run only once when the injector is absent:\n```python\nimport inject\ninject.configure_once(my_config)\n```\n\n## Testing\nIn tests use `inject.clear_and_configure(callable)` to create a new injector on setup,\nand optionally `inject.clear()` to clean up on tear down:\n```python\nclass MyTest(unittest.TestCase):\n def setUp(self):\n inject.clear_and_configure(lambda binder: binder\n .bind(Cache, Mock()) \\\n .bind(Validator, TestValidator()))\n \n def tearDown(self):\n inject.clear()\n```\n\n## Thread-safety\nAfter configuration the injector is thread-safe and can be safely reused by multiple threads.\n\n## Binding types\n**Instance** bindings always return the same instance:\n\n```python\nredis = RedisCache(address='localhost:1234')\ndef config(binder):\n binder.bind(Cache, redis)\n```\n \n**Constructor** bindings create a singleton on injection:\n\n```python\ndef config(binder):\n # Creates a redis cache singleton on first injection.\n binder.bind_to_constructor(Cache, lambda: RedisCache(address='localhost:1234'))\n```\n\n**Provider** bindings call the provider on injection:\n\n```python\ndef get_my_thread_local_cache():\n pass\n\ndef config(binder):\n # Executes the provider on each injection.\n binder.bind_to_provider(Cache, get_my_thread_local_cache) \n```\n\n**Runtime** bindings automatically create singletons on injection, require no configuration.\nFor example, only the `Config` class binding is present, other bindings are runtime:\n\n```python\nclass Config(object):\n pass\n\nclass Cache(object):\n config = inject.attr(Config)\n\nclass Db(object):\n config = inject.attr(Config)\n\nclass User(object):\n cache = inject.attr(Cache)\n db = inject.attr(Db)\n \n @classmethod\n def load(cls, user_id):\n return cls.cache.load('users', user_id) or cls.db.load('users', user_id)\n \ninject.configure(lambda binder: binder.bind(Config, load_config_file()))\nuser = User.load(10)\n```\n## Disabling runtime binding\nSometimes runtime binding leads to unexpected behaviour. Say if you forget\nto bind an instance to a class, `inject` will try to implicitly instantiate it.\n\nIf an instance is unintentionally created with default arguments it may lead to\nhard-to-debug bugs. To disable runtime binding and make sure that only \nexplicitly bound instances are injected, pass `bind_in_runtime=False` \nto `inject.configure`, `inject.configure_once` or `inject.clear_and_configure`.\n\nIn this case `inject` immediately raises `InjectorException` when the code\ntries to get an unbound instance.\n\n## Keys\nIt is possible to use any hashable object as a binding key. For example:\n\n```python\nimport inject\n\ninject.configure(lambda binder: \\\n binder.bind('host', 'localhost') \\\n binder.bind('port', 1234))\n```\n\n## Why no scopes?\nI've used Guice and Spring in Java for a lot of years, and I don't like their scopes.\n`python-inject` by default creates objects as singletons. It does not need a prototype scope\nas in Spring or NO_SCOPE as in Guice because `python-inject` does not steal your class \nconstructors. Create instances the way you like and then inject dependencies into them.\n\nOther scopes such as a request scope or a session scope are fragile, introduce high coupling,\nand are difficult to test. In `python-inject` write custom providers which can be thread-local, \nrequest-local, etc.\n\nFor example, a thread-local current user provider:\n\n```python\nimport inject\nimport threading\n\n# Given a user class.\nclass User(object):\n pass\n\n# Create a thread-local current user storage.\n_LOCAL = threading.local()\n\ndef get_current_user():\n return getattr(_LOCAL, 'user', None)\n\ndef set_current_user(user):\n _LOCAL.user = user\n\n# Bind User to a custom provider.\ninject.configure(lambda binder: binder.bind_to_provider(User, get_current_user))\n\n# Inject the current user.\n@inject.params(user=User)\ndef foo(user):\n pass\n```\n\n## Links\n* Project: https://github.com/ivankorobkov/python-inject\n\n## License\nApache License 2.0\n\n## Contributors\n* Ivan Korobkov [@ivankorobkov](https://github.com/ivankorobkov)\n* Jaime Wyant [@jaimewyant](https://github.com/jaimewyant)\n* Sebastian Buczy\u0144ski [@Enforcer](https://github.com/Enforcer)\n* Oleksandr Fedorov [@Fedorof](https://github.com/Fedorof)\n* cselvaraj [@cselvaraj](https://github.com/cselvaraj)\n* \u9646\u96e8\u6674 [@SixExtreme](https://github.com/SixExtreme)\n* Andrew William Borba [@andrewborba10](https://github.com/andrewborba10)\n* jdmeyer3 [@jdmeyer3](https://github.com/jdmeyer3)\n* Alex Grover [@ajgrover](https://github.com/ajgrover)\n* Harro van der Kroft [@wisepotato](https://github.com/wisepotato)\n* Samiur Rahman [@samiur](https://github.com/samiur)\n* 45deg [@45deg](https://github.com/45deg)\n* Alexander Nicholas Costas [@ancostas](https://github.com/ancostas)\n* Dmitry Balabka [@dbalabka](https://github.com/dbalabka)",
"bugtrack_url": null,
"license": "Apache License 2.0",
"summary": "Python dependency injection framework",
"version": "4.3.1",
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "23e204673185baadf9f4e175de51212fd2966eed2fca3236fdcfb7e732e4d09e",
"md5": "a674cbdb5df1e1020a5ea30c692e4b5f",
"sha256": "7f996f2c9ed2082af776ddf6b528d97036898ac63040385feb1d12286a73c3cc"
},
"downloads": -1,
"filename": "Inject-4.3.1.tar.gz",
"has_sig": false,
"md5_digest": "a674cbdb5df1e1020a5ea30c692e4b5f",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 16560,
"upload_time": "2020-08-17T17:52:55",
"upload_time_iso_8601": "2020-08-17T17:52:55.100941Z",
"url": "https://files.pythonhosted.org/packages/23/e2/04673185baadf9f4e175de51212fd2966eed2fca3236fdcfb7e732e4d09e/Inject-4.3.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2020-08-17 17:52:55",
"github": true,
"gitlab": false,
"bitbucket": false,
"github_user": "ivankorobkov",
"github_project": "python-inject",
"travis_ci": true,
"coveralls": false,
"github_actions": false,
"lcname": "inject"
}