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:
Roman Mogylatov 2020-07-11 12:15:00 -04:00 committed by GitHub
parent f4c713fc22
commit fa469618dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 2141 additions and 1771 deletions

View File

@ -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
View File

@ -0,0 +1,9 @@
``dependency_injector.ext.flask``
---------------------------------
.. automodule:: dependency_injector.ext.flask
:members:
:special-members:
.. disqus::

View File

@ -8,3 +8,4 @@ API Documentation
providers providers
containers containers
errors errors
flaskext

View File

@ -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

View File

@ -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.

View File

@ -1,2 +1,2 @@
[pydocstyle] [pydocstyle]
ignore = D101,D203,D213 ignore = D101,D103,D107,D203,D213

View 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%

View File

@ -0,0 +1,5 @@
github:
request_timeout: 10
search:
default_term: "Dependency Injector"
default_limit: 5

View File

@ -0,0 +1 @@
"""Top-level package."""

View 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),
],
)

View File

@ -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()

View 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,
}

View File

@ -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>

View 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

View 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,
)

View File

@ -0,0 +1,4 @@
dependency-injector[flask,yaml]
pygithub
pytest-flask
pytest-cov

View File

@ -6,3 +6,5 @@ flake8
pydocstyle pydocstyle
sphinx_autobuild sphinx_autobuild
pip pip
-r requirements-ext.txt

View File

@ -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
View File

@ -0,0 +1 @@
flask

View File

@ -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',

View File

@ -0,0 +1 @@
"""Extensions package."""

View 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

View File

@ -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

View File

@ -0,0 +1 @@
"""Dependency injector extension unit tests."""

View 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')

View File

@ -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=