mirror of
https://github.com/ets-labs/python-dependency-injector.git
synced 2024-11-22 09:36:48 +03:00
Readme update (#263)
* Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Add files via upload * Update README.rst * Rename Blank Diagram (1).svg to di-map.svg * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Add files via upload * Rename Blank Diagram (2).svg to di-map2.svg * Update README.rst * Update README.rst * Update README.rst * Update README.rst * Add files via upload * Add files via upload * Rename README.svg to di-map3.svg * Update README.rst * Add files via upload * Rename README - Page 3.svg to di-map4.svg * Update README.rst * Add files via upload * Rename README - Copy of Page 3.svg to di-map5.svg * Update README.rst * Delete di-map.svg * Delete di-map2.svg * Delete di-map3.svg * Delete di-map4.svg * Update README.rst * Update README.rst * Add Github Navigator - Flask application * Do more refactoring for ghnav-flask * More refactoring * Update README * Add tests * Update readme * Add Flask extension * Add Factory.provides attribute * Add Flask extension module * User flask extension in githubnavigator example * Add README for ghnav-flask * Update ghnav-flask README * Update ghnav-flask README * Update README with ghnav container example * Move ghnav-flask to miniapps/ folder * Fix auth token reading from env for ghnav-flask * Update readme * Fix ghnav-flask linter errors * Add downloads and wheel badge * Add tests for flask extension * Fix flask tests * Add requirements-ext.txt installation to tox.ini * Add API docs for ext.flask module * Update setup.py * Add Flask to the list of keywords * Update badges on docs README * Update docs README title * Fix ext.flask tests * Fix syntax of ext.flask for Python 2.7, 3.4, 3.5 * Fix syntax of ext.flask for Python 2.7, 3.4, 3.5 * Fix imports in ext.flask for Python 2.7, 3.4, 3.5 * Update ghfnav-flask README * Update ghfnav-flask README * Remove setting of empty github token * Add flask extras * Update requirements * Update requirements * Add flask extra to python 3.4 tox.ini * Update changelog * Update changelog
This commit is contained in:
parent
f4c713fc22
commit
fa469618dd
458
README.rst
458
README.rst
|
@ -1,28 +1,48 @@
|
||||||
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/logo.svg
|
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/logo.svg
|
||||||
|
:target: https://github.com/ets-labs/python-dependency-injector
|
||||||
|
|
||||||
|
|
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/v/dependency_injector.svg
|
.. image:: https://img.shields.io/pypi/v/dependency_injector.svg
|
||||||
:target: https://pypi.org/project/dependency-injector/
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
:alt: Latest Version
|
:alt: Latest Version
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/l/dependency_injector.svg
|
.. image:: https://img.shields.io/pypi/l/dependency_injector.svg
|
||||||
:target: https://pypi.org/project/dependency-injector/
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
:alt: License
|
:alt: License
|
||||||
.. image:: https://pepy.tech/badge/dependency-injector
|
|
||||||
:target: https://pepy.tech/project/dependency-injector
|
|
||||||
:alt: Downloads
|
|
||||||
.. image:: https://img.shields.io/pypi/pyversions/dependency_injector.svg
|
.. image:: https://img.shields.io/pypi/pyversions/dependency_injector.svg
|
||||||
:target: https://pypi.org/project/dependency-injector/
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
:alt: Supported Python versions
|
:alt: Supported Python versions
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/implementation/dependency_injector.svg
|
.. image:: https://img.shields.io/pypi/implementation/dependency_injector.svg
|
||||||
:target: https://pypi.org/project/dependency-injector/
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
:alt: Supported Python implementations
|
:alt: Supported Python implementations
|
||||||
|
|
||||||
|
.. image:: https://pepy.tech/badge/dependency-injector
|
||||||
|
:target: https://pepy.tech/project/dependency-injector
|
||||||
|
:alt: Downloads
|
||||||
|
|
||||||
|
.. image:: https://pepy.tech/badge/dependency-injector/month
|
||||||
|
:target: https://pepy.tech/project/dependency-injector
|
||||||
|
:alt: Downloads
|
||||||
|
|
||||||
|
.. image:: https://pepy.tech/badge/dependency-injector/week
|
||||||
|
:target: https://pepy.tech/project/dependency-injector
|
||||||
|
:alt: Downloads
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/wheel/dependency-injector.svg
|
||||||
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
|
:alt: Wheel
|
||||||
|
|
||||||
.. image:: https://travis-ci.org/ets-labs/python-dependency-injector.svg?branch=master
|
.. image:: https://travis-ci.org/ets-labs/python-dependency-injector.svg?branch=master
|
||||||
:target: https://travis-ci.org/ets-labs/python-dependency-injector
|
:target: https://travis-ci.org/ets-labs/python-dependency-injector
|
||||||
:alt: Build Status
|
:alt: Build Status
|
||||||
|
|
||||||
.. image:: http://readthedocs.org/projects/python-dependency-injector/badge/?version=latest
|
.. image:: http://readthedocs.org/projects/python-dependency-injector/badge/?version=latest
|
||||||
:target: http://python-dependency-injector.ets-labs.org/
|
:target: http://python-dependency-injector.ets-labs.org/
|
||||||
:alt: Docs Status
|
:alt: Docs Status
|
||||||
|
|
||||||
.. image:: https://coveralls.io/repos/github/ets-labs/python-dependency-injector/badge.svg?branch=master
|
.. image:: https://coveralls.io/repos/github/ets-labs/python-dependency-injector/badge.svg?branch=master
|
||||||
:target: https://coveralls.io/github/ets-labs/python-dependency-injector?branch=master
|
:target: https://coveralls.io/github/ets-labs/python-dependency-injector?branch=master
|
||||||
:alt: Coverage Status
|
:alt: Coverage Status
|
||||||
|
@ -30,370 +50,100 @@
|
||||||
What is ``Dependency Injector``?
|
What is ``Dependency Injector``?
|
||||||
================================
|
================================
|
||||||
|
|
||||||
``Dependency Injector`` is a dependency injection microframework for Python.
|
``Dependency Injector`` is a dependency injection framework for Python.
|
||||||
It was designed to be a unified and developer-friendly tool that helps
|
|
||||||
implement a dependency injection design pattern in a formal, pretty, and
|
|
||||||
Pythonic way.
|
|
||||||
|
|
||||||
The key features of the *Dependency Injector* framework are:
|
Why do I need it?
|
||||||
|
=================
|
||||||
|
|
||||||
+ Easy, smart, and pythonic style.
|
``Dependency Injector`` helps you improve application structure.
|
||||||
+ Obvious and clear structure.
|
|
||||||
+ Extensibility and flexibility.
|
|
||||||
+ High performance.
|
|
||||||
+ Memory efficiency.
|
|
||||||
+ Thread safety.
|
|
||||||
+ Documented.
|
|
||||||
+ Semantically versioned.
|
|
||||||
|
|
||||||
*Dependency Injector* containers and providers are implemented as C extension
|
With the ``Dependency Injector`` you keep **application structure in one place**.
|
||||||
types using Cython.
|
This place is called **the container**. You use the container to manage all the components of the application. All the component dependencies are defined explicitly. This provides the control on the application structure. It is **easy to understand and change** it.
|
||||||
|
|
||||||
|
.. figure:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/di-map.svg
|
||||||
|
:target: https://github.com/ets-labs/python-dependency-injector
|
||||||
|
|
||||||
|
*The container is like a map of your application. You always know what depends on what.*
|
||||||
|
|
||||||
|
``Flask`` + ``Dependency Injector`` example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
from dependency_injector.ext import flask
|
||||||
|
from github import Github
|
||||||
|
|
||||||
|
from . import services, views
|
||||||
|
|
||||||
|
|
||||||
Installation
|
class Application(containers.DeclarativeContainer):
|
||||||
------------
|
"""Application container."""
|
||||||
|
|
||||||
The *Dependency Injector* library is available on `PyPi`_::
|
config = providers.Configuration()
|
||||||
|
|
||||||
|
github_client = providers.Factory(
|
||||||
|
Github,
|
||||||
|
login_or_token=config.github.auth_token,
|
||||||
|
timeout=config.github.request_timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
search_service = providers.Factory(
|
||||||
|
services.SearchService,
|
||||||
|
github_client=github_client,
|
||||||
|
)
|
||||||
|
|
||||||
|
index_view = providers.Callable(
|
||||||
|
views.index,
|
||||||
|
search_service=search_service,
|
||||||
|
default_search_term=config.search.default_term,
|
||||||
|
default_search_limit=config.search.default_limit,
|
||||||
|
)
|
||||||
|
|
||||||
|
app = providers.Factory(
|
||||||
|
flask.create_app,
|
||||||
|
name=__name__,
|
||||||
|
routes=[
|
||||||
|
flask.Route('/', view_provider=index_view),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
See complete example here - `Flask + Dependency Injector Example <https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/ghnav-flask>`_
|
||||||
|
|
||||||
|
How to install?
|
||||||
|
---------------
|
||||||
|
|
||||||
|
- The package is available on the `PyPi`_::
|
||||||
|
|
||||||
pip install dependency-injector
|
pip install dependency-injector
|
||||||
|
|
||||||
Documentation
|
Where is the docs?
|
||||||
-------------
|
|
||||||
|
|
||||||
The *Dependency Injector* documentation is hosted on ReadTheDocs:
|
|
||||||
|
|
||||||
- `User's guide`_
|
|
||||||
- `API docs`_
|
|
||||||
|
|
||||||
Dependency injection
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
`Dependency injection`_ is a software design pattern that implements
|
|
||||||
`Inversion of control`_ to resolve dependencies. Formally, if object **A**
|
|
||||||
depends on object **B**, object **A** must not create or import object **B**
|
|
||||||
directly. Instead of this object **A** must provide a way to *inject*
|
|
||||||
object **B**. The responsibilities of objects creation and dependency
|
|
||||||
injection are delegated to external code - the *dependency injector*.
|
|
||||||
|
|
||||||
Popular terminology of the dependency injection pattern:
|
|
||||||
|
|
||||||
+ Object **A**, which depends on object **B**, is often called -
|
|
||||||
the *client*.
|
|
||||||
+ Object **B**, which is depended on, is often called - the *service*.
|
|
||||||
+ External code that is responsible for creation of objects and injection
|
|
||||||
of dependencies is often called - the *dependency injector*.
|
|
||||||
|
|
||||||
There are several ways to inject a *service* into a *client*:
|
|
||||||
|
|
||||||
+ by passing it as an ``__init__`` argument (constructor / initializer
|
|
||||||
injection)
|
|
||||||
+ by setting it as an attribute's value (attribute injection)
|
|
||||||
+ by passing it as a method's argument (method injection)
|
|
||||||
|
|
||||||
The dependency injection pattern has few strict rules that should be followed:
|
|
||||||
|
|
||||||
+ The *client* delegates to the *dependency injector* the responsibility
|
|
||||||
of injecting its dependencies - the *service(s)*.
|
|
||||||
+ The *client* doesn't know how to create the *service*, it knows only
|
|
||||||
the interface of the *service*. The *service* doesn't know that it is used by
|
|
||||||
the *client*.
|
|
||||||
+ The *dependency injector* knows how to create the *client* and
|
|
||||||
the *service*. It also knows that the *client* depends on the *service*,
|
|
||||||
and knows how to inject the *service* into the *client*.
|
|
||||||
+ The *client* and the *service* know nothing about the *dependency injector*.
|
|
||||||
|
|
||||||
The dependency injection pattern provides the following advantages:
|
|
||||||
|
|
||||||
+ Control of application structure.
|
|
||||||
+ Decreased coupling of application components.
|
|
||||||
+ Increased code reusability.
|
|
||||||
+ Increased testability.
|
|
||||||
+ Increased maintainability.
|
|
||||||
+ Reconfiguration of a system without rebuilding.
|
|
||||||
|
|
||||||
Example of dependency injection
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
Let's go through next example:
|
|
||||||
|
|
||||||
.. image:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/engines_cars/diagram.png
|
|
||||||
:width: 100%
|
|
||||||
:align: center
|
|
||||||
|
|
||||||
Listing of ``example.engines`` module:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
"""Dependency injection example, engines module."""
|
|
||||||
|
|
||||||
|
|
||||||
class Engine:
|
|
||||||
"""Example engine base class.
|
|
||||||
|
|
||||||
Engine is a heart of every car. Engine is a very common term and could be
|
|
||||||
implemented in very different ways.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class GasolineEngine(Engine):
|
|
||||||
"""Gasoline engine."""
|
|
||||||
|
|
||||||
|
|
||||||
class DieselEngine(Engine):
|
|
||||||
"""Diesel engine."""
|
|
||||||
|
|
||||||
|
|
||||||
class ElectricEngine(Engine):
|
|
||||||
"""Electric engine."""
|
|
||||||
|
|
||||||
Listing of ``example.cars`` module:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
"""Dependency injection example, cars module."""
|
|
||||||
|
|
||||||
|
|
||||||
class Car:
|
|
||||||
"""Example car."""
|
|
||||||
|
|
||||||
def __init__(self, engine):
|
|
||||||
"""Initializer."""
|
|
||||||
self._engine = engine # Engine is injected
|
|
||||||
|
|
||||||
The next example demonstrates the creation of several cars with different engines:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
"""Dependency injection example, Cars & Engines."""
|
|
||||||
|
|
||||||
import example.cars
|
|
||||||
import example.engines
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
gasoline_car = example.cars.Car(example.engines.GasolineEngine())
|
|
||||||
diesel_car = example.cars.Car(example.engines.DieselEngine())
|
|
||||||
electric_car = example.cars.Car(example.engines.ElectricEngine())
|
|
||||||
|
|
||||||
While the previous example demonstrates the advantages of dependency injection,
|
|
||||||
there is a disadvantage demonstrated as well - the creation of a car requires
|
|
||||||
additional code to specify its dependencies. However, this disadvantage
|
|
||||||
could be avoided by using a dependency injection framework for the creation of
|
|
||||||
an inversion of control container (IoC container).
|
|
||||||
|
|
||||||
Here's an example of the creation of several inversion of control containers
|
|
||||||
(IoC containers) using *Dependency Injector*:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
"""Dependency injection example, Cars & Engines IoC containers."""
|
|
||||||
|
|
||||||
import example.cars
|
|
||||||
import example.engines
|
|
||||||
|
|
||||||
import dependency_injector.containers as containers
|
|
||||||
import dependency_injector.providers as providers
|
|
||||||
|
|
||||||
|
|
||||||
class Engines(containers.DeclarativeContainer):
|
|
||||||
"""IoC container of engine providers."""
|
|
||||||
|
|
||||||
gasoline = providers.Factory(example.engines.GasolineEngine)
|
|
||||||
|
|
||||||
diesel = providers.Factory(example.engines.DieselEngine)
|
|
||||||
|
|
||||||
electric = providers.Factory(example.engines.ElectricEngine)
|
|
||||||
|
|
||||||
|
|
||||||
class Cars(containers.DeclarativeContainer):
|
|
||||||
"""IoC container of car providers."""
|
|
||||||
|
|
||||||
gasoline = providers.Factory(example.cars.Car,
|
|
||||||
engine=Engines.gasoline)
|
|
||||||
|
|
||||||
diesel = providers.Factory(example.cars.Car,
|
|
||||||
engine=Engines.diesel)
|
|
||||||
|
|
||||||
electric = providers.Factory(example.cars.Car,
|
|
||||||
engine=Engines.electric)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
gasoline_car = Cars.gasoline()
|
|
||||||
diesel_car = Cars.diesel()
|
|
||||||
electric_car = Cars.electric()
|
|
||||||
|
|
||||||
Dependency Injector structure
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
*Dependency Injector* is a microframework and has a simple structure.
|
|
||||||
|
|
||||||
There are two main entities: providers and containers.
|
|
||||||
|
|
||||||
.. image:: https://raw.githubusercontent.com/wiki/ets-labs/python-dependency-injector/img/internals.png
|
|
||||||
:width: 100%
|
|
||||||
:align: center
|
|
||||||
|
|
||||||
Providers
|
|
||||||
~~~~~~~~~
|
|
||||||
|
|
||||||
Providers describe strategies of accessing objects. They define how particular
|
|
||||||
objects are provided.
|
|
||||||
|
|
||||||
- **Provider** - base provider class.
|
|
||||||
- **Callable** - provider that calls a wrapped callable on every call. Supports
|
|
||||||
positional and keyword argument injections.
|
|
||||||
- **Factory** - provider that creates new instance of specified class on every
|
|
||||||
call. Supports positional and keyword argument injections, as well as
|
|
||||||
attribute injections.
|
|
||||||
- **Singleton** - provider that creates new instance of specified class on its
|
|
||||||
first call and returns the same instance on every next call. Supports
|
|
||||||
position and keyword argument injections, as well as attribute injections.
|
|
||||||
- **Object** - provider that returns provided instance "as is".
|
|
||||||
- **ExternalDependency** - provider that can be useful for development of
|
|
||||||
self-sufficient libraries, modules, and applications that require external
|
|
||||||
dependencies.
|
|
||||||
- **Configuration** - provider that helps with implementing late static binding
|
|
||||||
of configuration options - use first, define later.
|
|
||||||
|
|
||||||
Containers
|
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
Containers are collections of providers. The main purpose of containers is to
|
|
||||||
group providers.
|
|
||||||
|
|
||||||
- **DeclarativeContainer** - is an inversion of control container that can be
|
|
||||||
defined in a declarative manner. It covers most of the cases where a list of
|
|
||||||
providers that is be included in a container is deterministic
|
|
||||||
(that means the container will not change its structure in runtime).
|
|
||||||
- **DynamicContainer** - is an inversion of control container with a dynamic
|
|
||||||
structure. It covers most of the cases where a list of providers that
|
|
||||||
would be included in container is non-deterministic and depends on the
|
|
||||||
application's flow or its configuration (container's structure could be
|
|
||||||
determined just after the application starts and might perform some initial
|
|
||||||
work, like parsing a list of container providers from a configuration).
|
|
||||||
|
|
||||||
Dependency Injector in action
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
The brief example below is a simplified version of inversion of control
|
|
||||||
containers from a real-life application. The example demonstrates the usage
|
|
||||||
of *Dependency Injector* inversion of control container and providers for
|
|
||||||
specifying application components and their dependencies on each other in one
|
|
||||||
module. Besides other previously mentioned advantages, it shows a great
|
|
||||||
opportunity to control and manage application's structure in one place.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
"""Example of dependency injection in Python."""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import sqlite3
|
|
||||||
|
|
||||||
import boto3
|
|
||||||
|
|
||||||
from dependency_injector import containers, providers
|
|
||||||
from example import services, main
|
|
||||||
|
|
||||||
|
|
||||||
class IocContainer(containers.DeclarativeContainer):
|
|
||||||
"""Application IoC container."""
|
|
||||||
|
|
||||||
config = providers.Configuration('config')
|
|
||||||
logger = providers.Singleton(logging.Logger, name='example')
|
|
||||||
|
|
||||||
# Gateways
|
|
||||||
|
|
||||||
database_client = providers.Singleton(sqlite3.connect, config.database.dsn)
|
|
||||||
|
|
||||||
s3_client = providers.Singleton(
|
|
||||||
boto3.client, 's3',
|
|
||||||
aws_access_key_id=config.aws.access_key_id,
|
|
||||||
aws_secret_access_key=config.aws.secret_access_key,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Services
|
|
||||||
|
|
||||||
users_service = providers.Factory(
|
|
||||||
services.UsersService,
|
|
||||||
db=database_client,
|
|
||||||
logger=logger,
|
|
||||||
)
|
|
||||||
|
|
||||||
auth_service = providers.Factory(
|
|
||||||
services.AuthService,
|
|
||||||
token_ttl=config.auth.token_ttl,
|
|
||||||
db=database_client,
|
|
||||||
logger=logger,
|
|
||||||
)
|
|
||||||
|
|
||||||
photos_service = providers.Factory(
|
|
||||||
services.PhotosService,
|
|
||||||
db=database_client,
|
|
||||||
s3=s3_client,
|
|
||||||
logger=logger,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Misc
|
|
||||||
|
|
||||||
main = providers.Callable(
|
|
||||||
main.main,
|
|
||||||
users_service=users_service,
|
|
||||||
auth_service=auth_service,
|
|
||||||
photos_service=photos_service,
|
|
||||||
)
|
|
||||||
|
|
||||||
The next example demonstrates a run of the example application defined above:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
"""Run example of dependency injection in Python."""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from container import IocContainer
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
# Configure container:
|
|
||||||
container = IocContainer(
|
|
||||||
config={
|
|
||||||
'database': {
|
|
||||||
'dsn': ':memory:',
|
|
||||||
},
|
|
||||||
'aws': {
|
|
||||||
'access_key_id': 'KEY',
|
|
||||||
'secret_access_key': 'SECRET',
|
|
||||||
},
|
|
||||||
'auth': {
|
|
||||||
'token_ttl': 3600,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
container.logger().addHandler(logging.StreamHandler(sys.stdout))
|
|
||||||
|
|
||||||
# Run application:
|
|
||||||
container.main(*sys.argv[1:])
|
|
||||||
|
|
||||||
You can find more *Dependency Injector* examples in the ``/examples`` directory
|
|
||||||
on our GitHub:
|
|
||||||
|
|
||||||
https://github.com/ets-labs/python-dependency-injector
|
|
||||||
|
|
||||||
Feedback & Support
|
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
Feel free to post questions, bugs, feature requests, proposals, etc. on
|
- The documentation is available on the `Read The Docs <http://python-dependency-injector.ets-labs.org/>`_
|
||||||
the *Dependency Injector* GitHub issues page:
|
|
||||||
|
|
||||||
https://github.com/ets-labs/python-dependency-injector/issues
|
Have a question?
|
||||||
|
----------------
|
||||||
|
|
||||||
Your feedback is quite important!
|
- Open a `Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_
|
||||||
|
|
||||||
|
Found a bug?
|
||||||
|
------------
|
||||||
|
|
||||||
|
- Open a `Github Issue <https://github.com/ets-labs/python-dependency-injector/issues>`_
|
||||||
|
|
||||||
|
Want to help?
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- ⭐️ Star the ``Dependency Injector`` on the `Github <https://github.com/ets-labs/python-dependency-injector/>`_
|
||||||
|
- 🆕 Start a new project with the ``Dependency Injector``
|
||||||
|
- 💬 Tell your friend about the ``Dependency Injector``
|
||||||
|
|
||||||
|
|
||||||
.. _Dependency injection: http://en.wikipedia.org/wiki/Dependency_injection
|
Want to contribute?
|
||||||
.. _Inversion of control: https://en.wikipedia.org/wiki/Inversion_of_control
|
-------------------
|
||||||
|
|
||||||
|
- 🔀 Fork the project
|
||||||
|
- ⬅️ Open a pull request to the ``develop`` branch
|
||||||
|
|
||||||
.. _PyPi: https://pypi.org/project/dependency-injector/
|
.. _PyPi: https://pypi.org/project/dependency-injector/
|
||||||
.. _User's guide: http://python-dependency-injector.ets-labs.org/
|
|
||||||
.. _API docs: http://python-dependency-injector.ets-labs.org/api/
|
|
||||||
|
|
9
docs/api/flaskext.rst
Normal file
9
docs/api/flaskext.rst
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
``dependency_injector.ext.flask``
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
.. automodule:: dependency_injector.ext.flask
|
||||||
|
:members:
|
||||||
|
:special-members:
|
||||||
|
|
||||||
|
|
||||||
|
.. disqus::
|
|
@ -8,3 +8,4 @@ API Documentation
|
||||||
providers
|
providers
|
||||||
containers
|
containers
|
||||||
errors
|
errors
|
||||||
|
flaskext
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
======================================================================
|
=================================================================
|
||||||
Dependency Injector --- Dependency injection microframework for Python
|
Dependency Injector --- Dependency injection framework for Python
|
||||||
======================================================================
|
=================================================================
|
||||||
|
|
||||||
.. meta::
|
.. meta::
|
||||||
:google-site-verification: 6it89zX0_wccKEhAqbAiYQooS95f0BA8YfesHk6bsNA
|
:google-site-verification: 6it89zX0_wccKEhAqbAiYQooS95f0BA8YfesHk6bsNA
|
||||||
|
@ -22,24 +22,43 @@ Dependency Injector --- Dependency injection microframework for Python
|
||||||
.. image:: https://img.shields.io/pypi/v/dependency_injector.svg
|
.. image:: https://img.shields.io/pypi/v/dependency_injector.svg
|
||||||
:target: https://pypi.org/project/dependency-injector/
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
:alt: Latest Version
|
:alt: Latest Version
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/l/dependency_injector.svg
|
.. image:: https://img.shields.io/pypi/l/dependency_injector.svg
|
||||||
:target: https://pypi.org/project/dependency-injector/
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
:alt: License
|
:alt: License
|
||||||
.. image:: https://pepy.tech/badge/dependency-injector
|
|
||||||
:target: https://pepy.tech/project/dependency-injector
|
|
||||||
:alt: Downloads
|
|
||||||
.. image:: https://img.shields.io/pypi/pyversions/dependency_injector.svg
|
.. image:: https://img.shields.io/pypi/pyversions/dependency_injector.svg
|
||||||
:target: https://pypi.org/project/dependency-injector/
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
:alt: Supported Python versions
|
:alt: Supported Python versions
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/implementation/dependency_injector.svg
|
.. image:: https://img.shields.io/pypi/implementation/dependency_injector.svg
|
||||||
:target: https://pypi.org/project/dependency-injector/
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
:alt: Supported Python implementations
|
:alt: Supported Python implementations
|
||||||
|
|
||||||
|
.. image:: https://pepy.tech/badge/dependency-injector
|
||||||
|
:target: https://pepy.tech/project/dependency-injector
|
||||||
|
:alt: Downloads
|
||||||
|
|
||||||
|
.. image:: https://pepy.tech/badge/dependency-injector/month
|
||||||
|
:target: https://pepy.tech/project/dependency-injector
|
||||||
|
:alt: Downloads
|
||||||
|
|
||||||
|
.. image:: https://pepy.tech/badge/dependency-injector/week
|
||||||
|
:target: https://pepy.tech/project/dependency-injector
|
||||||
|
:alt: Downloads
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/wheel/dependency-injector.svg
|
||||||
|
:target: https://pypi.org/project/dependency-injector/
|
||||||
|
:alt: Wheel
|
||||||
|
|
||||||
.. image:: https://travis-ci.org/ets-labs/python-dependency-injector.svg?branch=master
|
.. image:: https://travis-ci.org/ets-labs/python-dependency-injector.svg?branch=master
|
||||||
:target: https://travis-ci.org/ets-labs/python-dependency-injector
|
:target: https://travis-ci.org/ets-labs/python-dependency-injector
|
||||||
:alt: Build Status
|
:alt: Build Status
|
||||||
|
|
||||||
.. image:: http://readthedocs.org/projects/python-dependency-injector/badge/?version=latest
|
.. image:: http://readthedocs.org/projects/python-dependency-injector/badge/?version=latest
|
||||||
:target: http://python-dependency-injector.ets-labs.org/
|
:target: http://python-dependency-injector.ets-labs.org/
|
||||||
:alt: Docs Status
|
:alt: Docs Status
|
||||||
|
|
||||||
.. image:: https://coveralls.io/repos/github/ets-labs/python-dependency-injector/badge.svg?branch=master
|
.. image:: https://coveralls.io/repos/github/ets-labs/python-dependency-injector/badge.svg?branch=master
|
||||||
:target: https://coveralls.io/github/ets-labs/python-dependency-injector?branch=master
|
:target: https://coveralls.io/github/ets-labs/python-dependency-injector?branch=master
|
||||||
:alt: Coverage Status
|
:alt: Coverage Status
|
||||||
|
|
|
@ -7,6 +7,13 @@ that were made in every particular version.
|
||||||
From version 0.7.6 *Dependency Injector* framework strictly
|
From version 0.7.6 *Dependency Injector* framework strictly
|
||||||
follows `Semantic versioning`_
|
follows `Semantic versioning`_
|
||||||
|
|
||||||
|
Development version
|
||||||
|
-------------------
|
||||||
|
- Add ``Flask`` integration module ``dependency_injector.flask.ext``.
|
||||||
|
- Add ``Flask`` + ``Dependency Injector`` example ``ghnav-flask``.
|
||||||
|
- Add ``Factory.provides`` attribute. It is an alias to the ``Factory.cls``.
|
||||||
|
- New README.
|
||||||
|
|
||||||
3.19.2
|
3.19.2
|
||||||
------
|
------
|
||||||
- Add logo.
|
- Add logo.
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
[pydocstyle]
|
[pydocstyle]
|
||||||
ignore = D101,D203,D213
|
ignore = D101,D103,D107,D203,D213
|
||||||
|
|
90
examples/miniapps/ghnav-flask/README.rst
Normal file
90
examples/miniapps/ghnav-flask/README.rst
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
Flask Dependency Injection Example
|
||||||
|
==================================
|
||||||
|
|
||||||
|
Application ``githubnavigator`` is a `Flask <https://flask.palletsprojects.com/>`_ +
|
||||||
|
`Dependency Injector <http://python-dependency-injector.ets-labs.org/>`_ application.
|
||||||
|
|
||||||
|
Run
|
||||||
|
---
|
||||||
|
|
||||||
|
Install requirements:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
To run the application do:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
export FLASK_APP=githubnavigator.entrypoint
|
||||||
|
export FLASK_ENV=development
|
||||||
|
flask run
|
||||||
|
|
||||||
|
The output should be something like:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
* Serving Flask app "githubnavigator.entrypoint" (lazy loading)
|
||||||
|
* Environment: development
|
||||||
|
* Debug mode: on
|
||||||
|
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
||||||
|
* Restarting with fsevents reloader
|
||||||
|
* Debugger is active!
|
||||||
|
* Debugger PIN: 473-587-859
|
||||||
|
|
||||||
|
After that visit http://127.0.0.1:5000/ in your browser.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
|
||||||
|
Github has a rate limit. When thre rate limit is exceed you will see an exception
|
||||||
|
``github.GithubException.RateLimitExceededException``. For unauthenticated requests, the rate
|
||||||
|
limit allows for up to 60 requests per hour. To extend the limit to 5000 requests per hour you
|
||||||
|
need to set personal access token.
|
||||||
|
|
||||||
|
It's easy:
|
||||||
|
|
||||||
|
- Follow this `guide <https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token>`_ to create a token.
|
||||||
|
- Set a token to the environment variable:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
export GITHUB_TOKEN=<your token>
|
||||||
|
|
||||||
|
- Restart the app with ``flask run``
|
||||||
|
|
||||||
|
`Read more on Github rate limit <https://developer.github.com/v3/#rate-limiting>`_
|
||||||
|
|
||||||
|
Test
|
||||||
|
----
|
||||||
|
|
||||||
|
This application comes with unit tests.
|
||||||
|
|
||||||
|
To run the tests do:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
py.test githubnavigator/tests.py --cov=githubnavigator
|
||||||
|
|
||||||
|
The output should be something like:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
|
||||||
|
plugins: flask-1.0.0, cov-2.10.0, asyncio-0.14.0
|
||||||
|
collected 2 items
|
||||||
|
|
||||||
|
githubnavigator/tests.py .. [100%]
|
||||||
|
|
||||||
|
---------- coverage: platform darwin, python 3.8.3-final-0 -----------
|
||||||
|
Name Stmts Miss Cover
|
||||||
|
----------------------------------------------------
|
||||||
|
githubnavigator/__init__.py 0 0 100%
|
||||||
|
githubnavigator/application.py 10 0 100%
|
||||||
|
githubnavigator/entrypoint.py 5 5 0%
|
||||||
|
githubnavigator/services.py 13 0 100%
|
||||||
|
githubnavigator/tests.py 38 0 100%
|
||||||
|
githubnavigator/views.py 7 0 100%
|
||||||
|
----------------------------------------------------
|
||||||
|
TOTAL 73 5 93%
|
5
examples/miniapps/ghnav-flask/config.yml
Normal file
5
examples/miniapps/ghnav-flask/config.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
github:
|
||||||
|
request_timeout: 10
|
||||||
|
search:
|
||||||
|
default_term: "Dependency Injector"
|
||||||
|
default_limit: 5
|
|
@ -0,0 +1 @@
|
||||||
|
"""Top-level package."""
|
39
examples/miniapps/ghnav-flask/githubnavigator/application.py
Normal file
39
examples/miniapps/ghnav-flask/githubnavigator/application.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
"""Application module."""
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
from dependency_injector.ext import flask
|
||||||
|
from github import Github
|
||||||
|
|
||||||
|
from . import services, views
|
||||||
|
|
||||||
|
|
||||||
|
class Application(containers.DeclarativeContainer):
|
||||||
|
"""Application container."""
|
||||||
|
|
||||||
|
config = providers.Configuration()
|
||||||
|
|
||||||
|
github_client = providers.Factory(
|
||||||
|
Github,
|
||||||
|
login_or_token=config.github.auth_token,
|
||||||
|
timeout=config.github.request_timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
search_service = providers.Factory(
|
||||||
|
services.SearchService,
|
||||||
|
github_client=github_client,
|
||||||
|
)
|
||||||
|
|
||||||
|
index_view = providers.Callable(
|
||||||
|
views.index,
|
||||||
|
search_service=search_service,
|
||||||
|
default_search_term=config.search.default_term,
|
||||||
|
default_search_limit=config.search.default_limit,
|
||||||
|
)
|
||||||
|
|
||||||
|
app = providers.Factory(
|
||||||
|
flask.create_app,
|
||||||
|
name=__name__,
|
||||||
|
routes=[
|
||||||
|
flask.Route('/', view_provider=index_view),
|
||||||
|
],
|
||||||
|
)
|
|
@ -0,0 +1,9 @@
|
||||||
|
"""Entrypoint module."""
|
||||||
|
|
||||||
|
from .application import Application
|
||||||
|
|
||||||
|
|
||||||
|
application = Application()
|
||||||
|
application.config.from_yaml('config.yml')
|
||||||
|
application.config.github.auth_token.from_env('GITHUB_TOKEN')
|
||||||
|
app = application.app()
|
41
examples/miniapps/ghnav-flask/githubnavigator/services.py
Normal file
41
examples/miniapps/ghnav-flask/githubnavigator/services.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
"""Services module."""
|
||||||
|
|
||||||
|
from github import Github
|
||||||
|
from github.Repository import Repository
|
||||||
|
from github.Commit import Commit
|
||||||
|
|
||||||
|
|
||||||
|
class SearchService:
|
||||||
|
"""Search service performs search on Github."""
|
||||||
|
|
||||||
|
def __init__(self, github_client: Github):
|
||||||
|
self._github_client = github_client
|
||||||
|
|
||||||
|
def search_repositories(self, term, limit):
|
||||||
|
"""Search for repositories and return formatted data."""
|
||||||
|
repositories = self._github_client.search_repositories(term, **{'in': 'name'})
|
||||||
|
return [
|
||||||
|
self._format_repo(repository)
|
||||||
|
for repository in repositories[:limit]
|
||||||
|
]
|
||||||
|
|
||||||
|
def _format_repo(self, repository: Repository):
|
||||||
|
return {
|
||||||
|
'url': repository.html_url,
|
||||||
|
'name': repository.name,
|
||||||
|
'owner': {
|
||||||
|
'login': repository.owner.login,
|
||||||
|
'url': repository.owner.html_url,
|
||||||
|
'avatar_url': repository.owner.avatar_url,
|
||||||
|
},
|
||||||
|
'created_at': repository.created_at,
|
||||||
|
'latest_commit': self._format_commit(repository.get_commits()[0]),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _format_commit(self, commit: Commit):
|
||||||
|
return {
|
||||||
|
'sha': commit.sha,
|
||||||
|
'url': commit.html_url,
|
||||||
|
'message': commit.commit.message,
|
||||||
|
'author_name': commit.commit.author.name,
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Github Navigator</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Github Navigator</h1>
|
||||||
|
<form method="get">
|
||||||
|
<p>
|
||||||
|
Search term: <input type="text" name="search_term" value="{{ search_term if search_term }}">
|
||||||
|
Limit: <input type="text" name="limit" value="{{ limit }}">
|
||||||
|
<input type="submit">
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<h2>Search results</h2>
|
||||||
|
{% if repositories|length == 0 %}
|
||||||
|
<small>No search results</small>
|
||||||
|
{% endif %}
|
||||||
|
{% for repository in repositories %} {{n}}
|
||||||
|
<p>
|
||||||
|
<small>Search result # {{ loop.index }} from {{ repositories|length }}</small>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Repository: <a href="{{ repository.url }}">{{ repository.name }}</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Repository owner:
|
||||||
|
<a href="{{ repository.owner.url }}"><img src="{{ repository.owner.avatar_url }}" alt="avatar" height="24" width="24"/></a>
|
||||||
|
<a href="{{ repository.owner.url }}">{{ repository.owner.login }}</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Created at: {{ repository.created_at }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
LastCommit: <a href="{{ repository.latest_commit.url }}">{{ repository.latest_commit.sha }}</a> {{ repository.latest_commit['message'] }} {{ repository.latest_commit.author_name }}
|
||||||
|
</p>
|
||||||
|
<hr/>
|
||||||
|
{% endfor %}
|
||||||
|
</body>
|
||||||
|
</html>
|
90
examples/miniapps/ghnav-flask/githubnavigator/tests.py
Normal file
90
examples/miniapps/ghnav-flask/githubnavigator/tests.py
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
"""Tests module."""
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from github import Github
|
||||||
|
from flask import url_for
|
||||||
|
|
||||||
|
from .application import Application
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def application():
|
||||||
|
application = Application()
|
||||||
|
application.config.from_dict(
|
||||||
|
{
|
||||||
|
'github': {
|
||||||
|
'auth_token': 'test-token',
|
||||||
|
'request_timeout': 10,
|
||||||
|
},
|
||||||
|
'search': {
|
||||||
|
'default_term': 'Dependency Injector',
|
||||||
|
'default_limit': 5,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return application
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def app(application: Application):
|
||||||
|
return application.app()
|
||||||
|
|
||||||
|
|
||||||
|
def test_index(client, application: Application):
|
||||||
|
github_client_mock = mock.Mock(spec=Github)
|
||||||
|
github_client_mock.search_repositories.return_value = [
|
||||||
|
mock.Mock(
|
||||||
|
html_url='repo1-url',
|
||||||
|
name='repo1-name',
|
||||||
|
owner=mock.Mock(
|
||||||
|
login='owner1-login',
|
||||||
|
html_url='owner1-url',
|
||||||
|
avatar_url='owner1-avatar-url',
|
||||||
|
),
|
||||||
|
created_at='repo1-created-at',
|
||||||
|
get_commits=mock.Mock(return_value=[mock.Mock()]),
|
||||||
|
),
|
||||||
|
mock.Mock(
|
||||||
|
html_url='repo2-url',
|
||||||
|
name='repo2-name',
|
||||||
|
owner=mock.Mock(
|
||||||
|
login='owner2-login',
|
||||||
|
html_url='owner2-url',
|
||||||
|
avatar_url='owner2-avatar-url',
|
||||||
|
),
|
||||||
|
created_at='repo2-created-at',
|
||||||
|
get_commits=mock.Mock(return_value=[mock.Mock()]),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
with application.github_client.override(github_client_mock):
|
||||||
|
response = client.get(url_for('index'))
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
assert b'repo1-url' in response.data
|
||||||
|
assert b'repo1-name' in response.data
|
||||||
|
assert b'owner1-login' in response.data
|
||||||
|
assert b'owner1-url' in response.data
|
||||||
|
assert b'owner1-avatar-url' in response.data
|
||||||
|
assert b'repo1-created-at' in response.data
|
||||||
|
|
||||||
|
assert b'repo2-url' in response.data
|
||||||
|
assert b'repo2-name' in response.data
|
||||||
|
assert b'owner2-login' in response.data
|
||||||
|
assert b'owner2-url' in response.data
|
||||||
|
assert b'owner2-avatar-url' in response.data
|
||||||
|
assert b'repo2-created-at' in response.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_index_no_results(client, application: Application):
|
||||||
|
github_client_mock = mock.Mock(spec=Github)
|
||||||
|
github_client_mock.search_repositories.return_value = []
|
||||||
|
|
||||||
|
with application.github_client.override(github_client_mock):
|
||||||
|
response = client.get(url_for('index'))
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert b'No search results' in response.data
|
19
examples/miniapps/ghnav-flask/githubnavigator/views.py
Normal file
19
examples/miniapps/ghnav-flask/githubnavigator/views.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
"""Views module."""
|
||||||
|
|
||||||
|
from flask import request, render_template
|
||||||
|
|
||||||
|
from .services import SearchService
|
||||||
|
|
||||||
|
|
||||||
|
def index(search_service: SearchService, default_search_term, default_search_limit):
|
||||||
|
search_term = request.args.get('search_term', default_search_term)
|
||||||
|
limit = request.args.get('limit', default_search_limit, int)
|
||||||
|
|
||||||
|
repositories = search_service.search_repositories(search_term, limit)
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
'index.html',
|
||||||
|
search_term=search_term,
|
||||||
|
limit=limit,
|
||||||
|
repositories=repositories,
|
||||||
|
)
|
4
examples/miniapps/ghnav-flask/requirements.txt
Normal file
4
examples/miniapps/ghnav-flask/requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
dependency-injector[flask,yaml]
|
||||||
|
pygithub
|
||||||
|
pytest-flask
|
||||||
|
pytest-cov
|
|
@ -6,3 +6,5 @@ flake8
|
||||||
pydocstyle
|
pydocstyle
|
||||||
sphinx_autobuild
|
sphinx_autobuild
|
||||||
pip
|
pip
|
||||||
|
|
||||||
|
-r requirements-ext.txt
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
sphinx
|
sphinx
|
||||||
sphinx_rtd_theme>=0.2.5b2
|
sphinx_rtd_theme>=0.2.5b2
|
||||||
-e git://github.com/rmk135/sphinxcontrib-disqus.git#egg=sphinxcontrib-disqus
|
-e git://github.com/rmk135/sphinxcontrib-disqus.git#egg=sphinxcontrib-disqus
|
||||||
|
|
||||||
|
-r requirements-ext.txt
|
||||||
|
|
1
requirements-ext.txt
Normal file
1
requirements-ext.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
flask
|
5
setup.py
5
setup.py
|
@ -41,6 +41,7 @@ setup(name='dependency-injector',
|
||||||
download_url='https://pypi.python.org/pypi/dependency_injector',
|
download_url='https://pypi.python.org/pypi/dependency_injector',
|
||||||
packages=[
|
packages=[
|
||||||
'dependency_injector',
|
'dependency_injector',
|
||||||
|
'dependency_injector.ext',
|
||||||
],
|
],
|
||||||
package_dir={
|
package_dir={
|
||||||
'': 'src',
|
'': 'src',
|
||||||
|
@ -63,6 +64,9 @@ setup(name='dependency-injector',
|
||||||
'yaml': [
|
'yaml': [
|
||||||
'pyyaml',
|
'pyyaml',
|
||||||
],
|
],
|
||||||
|
'flask': [
|
||||||
|
'flask',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
zip_safe=True,
|
zip_safe=True,
|
||||||
license='BSD New',
|
license='BSD New',
|
||||||
|
@ -75,6 +79,7 @@ setup(name='dependency-injector',
|
||||||
'Factory',
|
'Factory',
|
||||||
'Singleton',
|
'Singleton',
|
||||||
'Design patterns',
|
'Design patterns',
|
||||||
|
'Flask',
|
||||||
],
|
],
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 5 - Production/Stable',
|
'Development Status :: 5 - Production/Stable',
|
||||||
|
|
1
src/dependency_injector/ext/__init__.py
Normal file
1
src/dependency_injector/ext/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Extensions package."""
|
83
src/dependency_injector/ext/flask.py
Normal file
83
src/dependency_injector/ext/flask.py
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
"""Flask extension module."""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
from dependency_injector import providers, errors
|
||||||
|
|
||||||
|
|
||||||
|
def create_app(name, routes, **kwargs):
|
||||||
|
"""Create Flask app and add routes."""
|
||||||
|
app = Flask(name, **kwargs)
|
||||||
|
for route in routes:
|
||||||
|
app.add_url_rule(*route.args, **route.options)
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
def as_view(provider, name=None):
|
||||||
|
"""Transform class-based view provider to view function."""
|
||||||
|
if isinstance(provider, providers.Factory):
|
||||||
|
def view(*args, **kwargs):
|
||||||
|
self = provider()
|
||||||
|
return self.dispatch_request(*args, **kwargs)
|
||||||
|
|
||||||
|
assert name, 'Argument "endpoint" is required for class-based views'
|
||||||
|
view.__name__ = name
|
||||||
|
elif isinstance(provider, providers.Callable):
|
||||||
|
def view(*args, **kwargs):
|
||||||
|
return provider(*args, **kwargs)
|
||||||
|
|
||||||
|
view.__name__ = provider.provides.__name__
|
||||||
|
else:
|
||||||
|
raise errors.Error('Undefined provider type')
|
||||||
|
|
||||||
|
view.__doc__ = provider.provides.__doc__
|
||||||
|
view.__module__ = provider.provides.__module__
|
||||||
|
|
||||||
|
if isinstance(provider.provides, type):
|
||||||
|
view.view_class = provider.provides
|
||||||
|
|
||||||
|
if hasattr(provider.provides, 'decorators'):
|
||||||
|
for decorator in provider.provides.decorators:
|
||||||
|
view = decorator(view)
|
||||||
|
|
||||||
|
if hasattr(provider.provides, 'methods'):
|
||||||
|
view.methods = provider.provides.methods
|
||||||
|
|
||||||
|
if hasattr(provider.provides, 'provide_automatic_options'):
|
||||||
|
view.provide_automatic_options = provider.provides.provide_automatic_options
|
||||||
|
|
||||||
|
return view
|
||||||
|
|
||||||
|
|
||||||
|
class Route:
|
||||||
|
"""Route is a glue for Dependency Injector providers and Flask views."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
rule,
|
||||||
|
endpoint=None,
|
||||||
|
view_provider=None,
|
||||||
|
provide_automatic_options=None,
|
||||||
|
**options):
|
||||||
|
"""Initialize route."""
|
||||||
|
self.view_provider = view_provider
|
||||||
|
self.args = (rule, endpoint, as_view(view_provider, endpoint), provide_automatic_options)
|
||||||
|
self.options = options
|
||||||
|
|
||||||
|
def __deepcopy__(self, memo):
|
||||||
|
"""Create and return full copy of provider."""
|
||||||
|
copied = memo.get(id(self))
|
||||||
|
if copied is not None:
|
||||||
|
return copied
|
||||||
|
|
||||||
|
rule, endpoint, _, provide_automatic_options = self.args
|
||||||
|
view_provider = providers.deepcopy(self.view_provider, memo)
|
||||||
|
|
||||||
|
return self.__class__(
|
||||||
|
rule,
|
||||||
|
endpoint,
|
||||||
|
view_provider,
|
||||||
|
provide_automatic_options,
|
||||||
|
**self.options)
|
File diff suppressed because it is too large
Load Diff
|
@ -1405,6 +1405,11 @@ cdef class Factory(Provider):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cls(self):
|
def cls(self):
|
||||||
|
"""Return provided type."""
|
||||||
|
return self.provides
|
||||||
|
|
||||||
|
@property
|
||||||
|
def provides(self):
|
||||||
"""Return provided type."""
|
"""Return provided type."""
|
||||||
return self.__instantiator.provides
|
return self.__instantiator.provides
|
||||||
|
|
||||||
|
|
1
tests/unit/ext/__init__.py
Normal file
1
tests/unit/ext/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
"""Dependency injector extension unit tests."""
|
75
tests/unit/ext/test_flask_py2_py3.py
Normal file
75
tests/unit/ext/test_flask_py2_py3.py
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
"""Dependency injector Flask extension unit tests."""
|
||||||
|
|
||||||
|
import unittest2 as unittest
|
||||||
|
from flask import url_for
|
||||||
|
from flask.views import MethodView
|
||||||
|
|
||||||
|
from dependency_injector import containers, providers
|
||||||
|
from dependency_injector.ext import flask
|
||||||
|
|
||||||
|
|
||||||
|
def index():
|
||||||
|
return 'Hello World!'
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
return 'Test!'
|
||||||
|
|
||||||
|
|
||||||
|
class Test(MethodView):
|
||||||
|
def get(self):
|
||||||
|
return 'Test class-based!'
|
||||||
|
|
||||||
|
|
||||||
|
class Application(containers.DeclarativeContainer):
|
||||||
|
|
||||||
|
index_view = providers.Callable(index)
|
||||||
|
test_view = providers.Callable(test)
|
||||||
|
test_class_view = providers.Factory(Test)
|
||||||
|
|
||||||
|
app = providers.Factory(
|
||||||
|
flask.create_app,
|
||||||
|
name=__name__,
|
||||||
|
routes=[
|
||||||
|
flask.Route('/', view_provider=index_view),
|
||||||
|
flask.Route('/test', 'test-test', test_view),
|
||||||
|
flask.Route('/test-class', 'test-class', test_class_view)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationTests(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
application = Application()
|
||||||
|
self.app = application.app()
|
||||||
|
self.app.config['SERVER_NAME'] = 'test-server.com'
|
||||||
|
self.client = self.app.test_client()
|
||||||
|
self.client.__enter__()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.client.__exit__(None, None, None)
|
||||||
|
|
||||||
|
def test_index(self):
|
||||||
|
response = self.client.get('/')
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.data, b'Hello World!')
|
||||||
|
|
||||||
|
def test_test(self):
|
||||||
|
response = self.client.get('/test')
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.data, b'Test!')
|
||||||
|
|
||||||
|
def test_test_class_based(self):
|
||||||
|
response = self.client.get('/test-class')
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.data, b'Test class-based!')
|
||||||
|
|
||||||
|
def test_endpoints(self):
|
||||||
|
with self.app.app_context():
|
||||||
|
self.assertEqual(url_for('index'), 'http://test-server.com/')
|
||||||
|
self.assertEqual(url_for('test-test'), 'http://test-server.com/test')
|
||||||
|
self.assertEqual(url_for('test-class'), 'http://test-server.com/test-class')
|
2
tox.ini
2
tox.ini
|
@ -7,6 +7,7 @@ deps=
|
||||||
unittest2
|
unittest2
|
||||||
extras=
|
extras=
|
||||||
yaml
|
yaml
|
||||||
|
flask
|
||||||
commands=
|
commands=
|
||||||
unit2 discover -s tests/unit -p test_*_py3.py
|
unit2 discover -s tests/unit -p test_*_py3.py
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ commands=
|
||||||
|
|
||||||
[testenv:py34]
|
[testenv:py34]
|
||||||
extras=
|
extras=
|
||||||
|
flask
|
||||||
|
|
||||||
[testenv:pypy]
|
[testenv:pypy]
|
||||||
commands=
|
commands=
|
||||||
|
|
Loading…
Reference in New Issue
Block a user