Dependency Injection Documentation¶
Warning
This Document Page Under Construction
Dependency Injection is a technique in which an object receives other objects that it depends on, called dependencies. Typically, the receiving object is called a client and the passed-in (‘injected’) object is called a service. The code that passes the service to the client is called the injector. Instead of the client specifying which service it will use, the injector tells the client what service to use. The ‘injection’ refers to the passing of a dependency (a service) into the client that uses it.
Overview¶
Warning
This Document Page Under Construction
Dependency Injection is a technique in which an object receives other objects that it depends on, called dependencies. Typically, the receiving object is called a client and the passed-in (‘injected’) object is called a service. The code that passes the service to the client is called the injector. Instead of the client specifying which service it will use, the injector tells the client what service to use. The ‘injection’ refers to the passing of a dependency (a service) into the client that uses it.
Dependency injection solves the following problems:
How can a class be independent of how the objects on which it depends are created?
How can the way objects are created be specified in separate configuration files?
How can an application support different configurations?
Creating objects directly within the class commits the class to particular implementations. This makes it difficult to change the instantiation at runtime, especially in compiled languages where changing the underlying objects can require re-compiling the source code.
Dependency injection separates the creation of a client’s dependencies from the client’s behavior, which promotes loosely coupled programs and the dependency inversion and single responsibility principles. Fundamentally, dependency injection is based on passing parameters to a method.
Dependency injection is an example of the more general concept of inversion of control.
Roles¶
Dependency injection involves four roles:
the service objects to be used
the client object, whose behavior depends on the services it uses
the interfaces that define how the client may use the services
the injector, which constructs the services and injects them into the client
Any object that may be used can be considered a service. Any object that uses other objects can be considered a client. The names relate only to the role the objects play in an injection.
The interfaces are the types the client expects its dependencies to be. The client should not know the specific implementation of its dependencies, only know the interface’s name and API. As a result, the client will not need to change even if what is behind the interface changes. Dependency injection can work with true interfaces or abstract classes, but also concrete services, though this would violate the dependency inversion principle and sacrifice the dynamic decoupling that enables testing. It is only required that the client never treats its interfaces as concrete by constructing or extending them. If the interface is refactored from a class to an interface type (or vice versa) the client will need to be recompiled. This is significant if the client and services are published separately.
The injector introduces services to the client. Often, it also constructs the client. An injector may connect a complex object graph by treating the same object as both a client at one point and as a service at another. The injector itself may actually be many objects working together, but may not be the client (as this would create a circular dependency). The injector may be referred to as an assembler, provider, container, factory, builder, spring, or construction code.
Pros¶
A basic benefit of dependency injection is decreased coupling between classes and their dependencies. By removing a client’s knowledge of how its dependencies are implemented, programs become more reusable, testable and maintainable.
This also results in increased flexibility: a client may act on anything that supports the intrinsic interface the client expects.
Many of dependency injection’s benefits are particularly relevant to unit-testing.
For example, dependency injection can be used to externalize a system’s configuration details into configuration files, allowing the system to be reconfigured without recompilation. Separate configurations can be written for different situations that require different implementations of components. This includes testing. Similarly, because dependency injection does not require any change in code behavior it can be applied to legacy code as a refactoring. The result is clients that are more independent and that are easier to unit test in isolation using stubs or mock objects that simulate other objects not under test. This ease of testing is often the first benefit noticed when using dependency injection.
More generally, dependency injection reduces boilerplate code, since all dependency creation is handled by a singular component.
Finally, dependency injection allows concurrent development. Two developers can independently develop classes that use each other, while only needing to know the interface the classes will communicate through. Plugins are often developed by third party shops that never even talk to the developers who created the product that uses the plugins.
Cons¶
Creates clients that demand configuration details, which can be onerous when obvious defaults are available.
Make code difficult to trace because it separates behavior from construction.
Is typically implemented with reflection or dynamic programming. This canhinder IDE automation.
Typically requires more upfront development effort.
Forces complexity out of classes and into the links between classes which might be harder to manage.
Encourage dependence on a framework.
Install¶
These instructions will install Dependency Injection package. Dependency Injection is a Python package that supports Python 3 on Linux, MacOS and Windows. We recommend using Python 3.6 or higher.
Linux and Mac OS¶
To install Dependency Injection package run:
python3 -m venv mediapills
source mediapills/bin/activate
pip install mediapills.dependency_injection
Windows (CMD/PowerShell)¶
To install Dependency Injection package on Windows (CMD/PowerShell)
To install Dependency Injection package run:
python3 -m venv mediapills
./mediapills/bin/activate
pip install mediapills.dependency_injection
Using the Container¶
Warning
This Document Page Under Construction
This documentation describes the API of the Container object itself.
__setitem__¶
You can set entries directly on the container:
>>> from mediapills.dependency_injection import Container
>>> di = Container()
>>> di['key'] = 'value'
>>> di['key']
'value'
__getitem__¶
You can get entries from the container:
>>> from mediapills.dependency_injection import Container
>>> di = Container({"key": "value"})
>>> di['key']
'value'
>>> di.get('key')
'value'
>>> di.get(None, 'default')
'default'
Definitions¶
Warning
This Document Page Under Construction
Lazy Loading¶
Lazy loading (also known as asynchronous loading) is a design pattern commonly used in computer programming and mostly in web design and development to defer initialization of an object until the point at which it is needed. It can contribute to efficiency in the program’s operation if properly and appropriately used.
Container
loads the definitions you have written and uses them like
instructions on how to create objects.
However those objects are only created when/if they are requested from the
Container
, for example through container.get(…) or when they need to
be injected in another object. That means you can have a large amount of
definitions, Container
will not create all the objects unless asked
to.
Definition types¶
This definition format is the most powerful of all. There are several kind of entries you can define:
arrays
Scalars¶
Scalars are simple Python values:
>>> from mediapills.dependency_injection import Container
>>> di = Container({
... 'database.driver': 'mysql',
... 'database.host': '127.0.0.1',
... 'database.port': 80,
... 'database.auth': False,
... 'database.user': 'root',
... })
>>> di['database.host']
'127.0.0.1'
You can also define object entries by creating them directly:
>>> from mediapills.dependency_injection import Container
>>> di = Container()
>>> di['key'] = 'value'
>>> di['key']
'value'
However this is not recommended as that object will be created for every entry invocation, even if not used (it will not be lazy loaded like explained at this section).
Generic Container Type Objects¶
Container
supports any object that holds an arbitrary number of other
objects. Examples of containers include tuple, list, set,
dict; these are the built-in containers.
>>> from mediapills.dependency_injection import Container
>>> di = Container()
>>> di['parameters'] = {
... 'database.host': '127.0.0.1',
... 'database.port': '80',
... 'database.user': 'root',
... }
>>> di['parameters']
{'database.host': '127.0.0.1', 'database.port': '80', 'database.user': 'root'}
Factories¶
Warning
This Page Section Under Construction
Objects¶
Services are defined by anonymous functions that return an instance of an object:
# define some services
container['session_storage'] = lambda di: (
SessionStorage('SESSION_ID')
)
container['session'] = lambda di: (
Session(di['session_storage'])
)
Notice that the anonymous function has access to the current container instance, allowing references to other services or parameters.
As objects are only created when you get them, the order of the definitions does not matter.
Using the defined services is also very easy:
# get the session object
session = injector['session']
# the above call is roughly equivalent to the following code:
# storage = SessionStorage('SESSION_ID')
# session = Session(storage)
Autowired Objects¶
Warning
This Page Section Under Construction
Aliases¶
You can alias an entry to another using the Container
:
# define arguments container
container['arguments'] = lambda _: sys.argv
# define arguments container alias with name properties
container['properties'] = lambda di: di['arguments']
Allows the interface of an existing location to be used as another name.
Environment Variables¶
You can get an environment variable’s value using the Container
:
>>> container['env'] = lambda _: os.environ
>>> di['env'].get("LANGUAGE")
'en_US'
String Expressions¶
Warning
This Page Section Under Construction
Wildcards¶
Warning
This Page Section Under Construction
How to contribute¶
There are lots of ways to contribute to the project. People with different expertise can help to improve the web content, the documentation, the code, and the tests. Even this README file is a result of collaboration of multiple people.
unit tests : A software project is as good as its tests are. Following this simple idea, we are trying to cover the integration capabilities with automated tests. If you’re passionate about the quality - please consider chiming in.
doc : Have you seen a project that doesn’t need to improve its documentation?!
website : We are using GH pages to build and manage the site’s content. If you’re interested in making it better, check-out gh-pages branch and dig in. If you are not familiar with the Github Pages - check it out, it’s pretty simple yet powerful!
giving feedback : Tell us how you use maediapills.dependency-injection, what was great and what was not so much. Also, what are you expecting from it and what would you like to see in the future? Opening an issue will grab our attention. Seriously, this is the great way to contribute!
Roles¶
Much like projects in ASF, Mediapills recognizes a few roles. Unlike ASF’s projects, our structure is a way simpler. There are only two types:
- A Contributor
is a user who contributes to a project in the form of code or documentation. Developers take extra steps to participate in a project,are active on the developer forums, participate in discussions, provide PRs (patches), documentation, suggestions, and criticism. Contributors are also known as developers.
- A Committer
is a developer that was given write access to the code repository. Not needing to depend on other people to commit their patches, they are actually making short-term decisions for the project. By submitting your code or other content to the project via PR or a patch, a Committer agrees to transfer the contribution rights to the Project. From time to time, the project’s committership will go through the list of contributions and make a decision to invite new developers to become a project committer.
RTC model¶
Mediapills supports Review-Then-Commit model of development. The following rules are used in the RTC process:
a developer should seek peer-review and/or feedback from other developers
through the PR mechanism (aka code review).
a developer should make a reasonable effort to test the changes before
submitting a PR for review.
any non-document PR is required to be opened for at least 24 hours for
community feedback before it gets committed unless it has an explicit +1 from a committer
any non-document PR needs to address all the comment and reach consensus
before it gets committed without a +1 from other committers
a committer can commit documentation patches without explicit review
process. However, we encourage you to seek the feedback.
Changelog¶
Warning
This Document Page Under Construction
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog and this project adheres to Semantic Versioning.
v0.1.0 (2021-08-23)¶
Minor release
Added¶
Added module
src.mediapills.dependency_injection.exceptions
classRecursionError
Added codecov integration
Added badges: requires.io, codecov, actions, py_versions, license, downloads, wheel and codeclimate
Added files:
LICENSE.md
andCONTRIBUTING.md
Added classifiers and project_urls sections in file
setup.cfg
Added py36, py37 and py39 into section envlist in
tox.ini
file
Other¶
Changed mypy, pytest-cov and build modules version
Changed
README.rst
Changed value in python_requires section in
setup.cfg
file from 3.8 to 3.5Changed
code-analysis.yml
workflow file
v0.0.2 (2021-08-22)¶
Patch release
Added¶
Created decorator
handle_unknown_identifier()
Created module
mediapills.dependency_injection
classContainer
methods:__getitem__()
,__setitem__()
,values()
,items()
,copy()
,update()
andprotect()
Created
TestInjector
unit test case
Other¶
Changed module
mediapills.dependency_injection
class name fromContainer
toInjector
Changed name from
TestContainer
toTestContainerBase
unit test case
v0.0.1 (2021-08-21)¶
Minor release
Added¶
Created
.coveragerc
file specifies python coverage configurationCreated
.gitignore
file specifies intentionally untracked filesCreated
.pre-commit-config.yaml
file specifies pre-commit configurationCreated Makefile the make utility
Created pyrightconfig.json the Pyright flexible configuration
Created python package builder
setup.py
andsetup.cfg
Created module
mediapills.dependency_injection
classContainer
Created module
src.mediapills.dependency_injection.exceptions
classes:BaseContainerException
,ExpectedInvokableException
,FrozenServiceException
,InvalidServiceIdentifierException
,UnknownIdentifierException
andRecursionInfiniteLoopError
Created unit tests case
TestContainer
Created virtualenv management file
tox.ini