diff --git a/README.rst b/README.rst index 76de13c8..2617825d 100644 --- a/README.rst +++ b/README.rst @@ -103,8 +103,8 @@ the application structure. It is **easy to understand and change** it. index_view = flask.View( views.index, search_service=search_service, - default_search_term=config.search.default_term, - default_search_limit=config.search.default_limit, + default_query=config.search.default_query, + default_limit=config.search.default_limit, ) Running such container looks like this: diff --git a/docs/index.rst b/docs/index.rst index 9ee19f8d..fb9d8aa7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -77,6 +77,8 @@ the application structure. It is **easy to understand and change** it. Explore the documentation to know more about the ``Dependency Injector``. +.. _contents: + Contents -------- diff --git a/docs/introduction/di_in_python.rst b/docs/introduction/di_in_python.rst index 65a3727c..3efa6600 100644 --- a/docs/introduction/di_in_python.rst +++ b/docs/introduction/di_in_python.rst @@ -126,6 +126,16 @@ using :doc:`Dependency Injector <../index>`: .. literalinclude:: ../../examples/miniapps/engines_cars/example_ioc_containers.py :language: python +What's next? +~~~~~~~~~~~~ + +Choose one of the following as a next step: + ++ Pass the dependency injection :ref:`flask-tutorial`. ++ Look at the other dependency injection :ref:`tutorials`. ++ Know more about the :ref:`providers`. ++ Go to the :ref:`contents`. + Useful links ~~~~~~~~~~~~ diff --git a/docs/main/changelog.rst b/docs/main/changelog.rst index c953274a..4a90c5a1 100644 --- a/docs/main/changelog.rst +++ b/docs/main/changelog.rst @@ -7,6 +7,11 @@ that were made in every particular version. From version 0.7.6 *Dependency Injector* framework strictly follows `Semantic versioning`_ +3.23.0 +------ +- Add ``Flask`` tutorial. +- Add PyPI classifiers. + 3.22.0 ------ - Migrate docs to ``alabaster`` theme. diff --git a/docs/providers/index.rst b/docs/providers/index.rst index 446f155c..901b6204 100644 --- a/docs/providers/index.rst +++ b/docs/providers/index.rst @@ -1,3 +1,5 @@ +.. _providers: + Providers ========= diff --git a/docs/tutorials/aiohttp.rst b/docs/tutorials/aiohttp.rst index 84f2d82e..2406c8b7 100644 --- a/docs/tutorials/aiohttp.rst +++ b/docs/tutorials/aiohttp.rst @@ -2,3 +2,5 @@ Aiohttp tutorial ================ Coming soon... + +.. disqus:: diff --git a/docs/tutorials/asyncio.rst b/docs/tutorials/asyncio.rst index 406c3788..d32c8cd2 100644 --- a/docs/tutorials/asyncio.rst +++ b/docs/tutorials/asyncio.rst @@ -2,3 +2,5 @@ Asyncio tutorial ================ Coming soon... + +.. disqus:: diff --git a/docs/tutorials/flask.rst b/docs/tutorials/flask.rst index b0a25ac7..6fa6265f 100644 --- a/docs/tutorials/flask.rst +++ b/docs/tutorials/flask.rst @@ -1,4 +1,1117 @@ +.. _flask-tutorial: + Flask tutorial ============== -Coming soon... +This tutorials shows how to build ``Flask`` application following dependency injection principle. + +Start from the scratch or jump to the section: + +.. contents:: + :local: + :backlinks: none + +You can find complete project on the +`Github `_. + +What are we going to build? +--------------------------- + +We will build a web application that helps to search for repositories on the Github. Let's call it +Github Navigator. + +How does Github Navigator work? + +- User opens a web page that asks to provide a search query. +- User types the query and hits Enter. +- Github Navigator takes that and searches through the Github for matching repositories. +- When search is done Github Navigator returns user a web page with the result. +- The results page shows all matching repositories and the provided search query. +- For any matching repository user sees: + - the repository name + - the owner of the repository + - the last commit to the repository +- User can click on the repository, the repository owner or the last commit to open its web page + on the Github. + +.. image:: flask_images/screen_02.png + +Prepare the environment +----------------------- + +Let's create the environment for the project. + +First we need to create a project folder and the virtual environment: + +.. code-block:: bash + + mkdir ghnav-flask-tutorial + cd ghnav-flask-tutorial + python3 -m venv venv + +Now let's activate the virtual environment: + +.. code-block:: bash + + . venv/bin/activate + +Project layout +-------------- + +Environment is ready and now we're going to create the layout of the project. + +Create next structure in the current directory. All files should be empty. That's ok for now. + +Initial project layout:: + + ./ + ├── githubnavigator/ + │ ├── __init__.py + │ ├── application.py + │ ├── containers.py + │ └── views.py + ├── venv/ + └── requirements.txt + +Now it's time to install ``Flask`` and ``Dependency Injector``. + +Put next lines into the ``requirements.txt`` file:: + + dependency-injector + flask + +Now let's install it:: + + pip install -r requirements.txt + +And check that installation is successful:: + + python -c "import dependency_injector; print(dependency_injector.__version__)" + python -c "import flask; print(flask.__version__)" + + +You should see something like:: + + (venv) $ python -c "import dependency_injector; print(dependency_injector.__version__)" + 3.22.0 + (venv) $ python -c "import flask; print(flask.__version__)" + 1.1.2 + +*Versions can be different. That's fine.* + +Hello world! +------------ + +Let's create minimal application. + +Put next into the ``views.py``: + +.. code-block:: python + + """Views module.""" + + + def index(): + return 'Hello, World!' + +Ok, we have the view. + +Now let's create the heart of our application - the container. Container will keep all of the +application components and their dependencies. First two providers we need to add are +the ``Flask`` application provider and the view provider. + +Put next into the ``containers.py``: + +.. code-block:: python + + """Application containers module.""" + + from dependency_injector import containers + from dependency_injector.ext import flask + from flask import Flask + + from . import views + + + class ApplicationContainer(containers.DeclarativeContainer): + """Application container.""" + + app = flask.Application(Flask, __name__) + + index_view = flask.View(views.index) + +Finally we need to create the Flask application factory. It is traditionally called +``create_app()``. It will create the container. Then it will use the container to create +the Flask application. Last step is to configure the routing - we will assign ``index_view`` from the +container to handle user requests to the root ``/`` if our web application. + +Put next into the ``application.py``: + +.. code-block:: python + + """Application module.""" + + from .containers import ApplicationContainer + + + def create_app(): + """Create and return Flask application.""" + container = ApplicationContainer() + + app = container.app() + app.container = container + + app.add_url_rule('/', view_func=container.index_view.as_view()) + + return app + +.. note:: + + Container is the first object in the application. + + The container is used to create all other objects. + +Ok. Now we're ready to say "Hello, World!". + +Do next in the terminal:: + + export FLASK_APP=githubnavigator.application + export FLASK_ENV=development + flask run + +The output should be something like:: + + * Serving Flask app "githubnavigator.application" (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 + +Open your browser and go to the ``http://127.0.0.1:5000/``. + +You should see ``Hello, World!``. + +That's it. Our minimal application is up and running. + +Make it pretty +-------------- + +Now let's make it look pretty. We will use `Bootstrap 4 `_. +For adding it to our application we will get +`Bootstrap-Flask `_ extension. +It will help us to add all needed static files in few clicks. + +Add ``bootstrap-flask`` to the ``requirements.txt``: + +.. code-block:: + :emphasize-lines: 3 + + dependency-injector + flask + bootstrap-flask + +and run in the terminal:: + + pip install --upgrade -r requirements.txt + +Now we need to add ``bootstrap-flask`` extension to the container. + +Edit ``containers.py``: + +.. code-block:: python + :emphasize-lines: 6,16 + + """Application containers module.""" + + from dependency_injector import containers + from dependency_injector.ext import flask + from flask import Flask + from flask_bootstrap import Bootstrap + + from . import views + + + class ApplicationContainer(containers.DeclarativeContainer): + """Application container.""" + + app = flask.Application(Flask, __name__) + + bootstrap = flask.Extension(Bootstrap) + + index_view = flask.View(views.index) + +Let's initialize ``bootstrap-flask`` extension. We will need to modify ``create_app()``. + +Edit ``application.py``: + +.. code-block:: python + :emphasize-lines: 13-14 + + """Application module.""" + + from .containers import ApplicationContainer + + + def create_app(): + """Create and return Flask application.""" + container = ApplicationContainer() + + app = container.app() + app.container = container + + bootstrap = container.bootstrap() + bootstrap.init_app(app) + + app.add_url_rule('/', view_func=container.index_view.as_view()) + + return app + +Now we need to add the templates. For doing this we will need to add the folder ``templates/`` to +the ``githubnavigator`` package. We also will need two files there: + +- ``base.html`` - the layout +- ``index.html`` - the main page + +Create ``templates`` folder and put two empty files into it ``base.html`` and ``index.html``: + +.. code-block:: + :emphasize-lines: 3-5 + + ./ + ├── githubnavigator/ + │ ├── templates/ + │ │ ├── base.html + │ │ └── index.html + │ ├── __init__.py + │ ├── application.py + │ ├── containers.py + │ └── views.py + ├── venv/ + └── requirements.txt + +Now let's fill in the layout. + +Put next into the ``base.html``: + +.. code-block:: html + + + + + {% block head %} + + + + + {% block styles %} + + {{ bootstrap.load_css() }} + {% endblock %} + + {% block title %}{% endblock %} + {% endblock %} + + + + {% block content %}{% endblock %} + + {% block scripts %} + + {{ bootstrap.load_js() }} + {% endblock %} + + + +And put something to the index page. + +Put next into the ``index.html``: + +.. code-block:: html + + {% extends "base.html" %} + + {% block title %}Github Navigator{% endblock %} + + {% block content %} +
+

Github Navigator

+ +
+
+
+ + +
+
+ + +
+
+
+ +

Results found: {{ repositories|length }}

+ + + + + + + + + + + + {% for repository in repositories %} {{n}} + + + + + + + {% endfor %} + +
#RepositoryRepository ownerLast commit
{{ loop.index }} + {{ repository.name }} + + avatar + + {{ repository.owner.login }} + + {{ repository.latest_commit.sha }} + {{ repository.latest_commit.message }} + {{ repository.latest_commit.author_name }} +
+
+ + {% endblock %} + +Ok, almost there. The last step is to make ``index`` view to render the ``index.html`` template. + +Edit ``views.py``: + +.. code-block:: python + + """Views module.""" + + from flask import request, render_template + + + def index(): + query = request.args.get('query', 'Dependency Injector') + limit = request.args.get('limit', 10, int) + + repositories = [] + + return render_template( + 'index.html', + query=query, + limit=limit, + repositories=repositories, + ) + +That's it. + +Make sure the app is running or use ``flask run`` and open ``http://127.0.0.1:5000/``. + +You should see: + +.. image:: flask_images/screen_01.png + +Connect to the GitHub +--------------------- + +In this section we will integrate our application with Github API. + +We will use `PyGithub `_ library for working with Github API. + +Let's add it to the ``requirements.txt``: + +.. code-block:: + :emphasize-lines: 4 + + dependency-injector + flask + bootstrap-flask + pygithub + +and run in the terminal:: + + pip install --upgrade -r requirements.txt + +Now we need to add Github API client the container. We will need to add two more providers from +the ``dependency_injector.providers`` module: + +- ``Factory`` provider that will create ``Github`` client. +- ``Configuration`` provider that will be used for providing the API token and the request timeout + for the ``Github`` client. + +Let's do it. + +Edit ``containers.py``: + +.. code-block:: python + :emphasize-lines: 3,7,19,21-25 + + """Application containers module.""" + + from dependency_injector import containers, providers + from dependency_injector.ext import flask + from flask import Flask + from flask_bootstrap import Bootstrap + from github import Github + + from . import views + + + class ApplicationContainer(containers.DeclarativeContainer): + """Application container.""" + + app = flask.Application(Flask, __name__) + + bootstrap = flask.Extension(Bootstrap) + + config = providers.Configuration() + + github_client = providers.Factory( + Github, + login_or_token=config.github.auth_token, + timeout=config.github.request_timeout, + ) + + index_view = flask.View(views.index) + +.. note:: + + We have used the configuration value before it was defined. That's the principle how + ``Configuration`` provider works. + + Use first, define later. + +Now let's add the configuration file. + +We will use YAML. + +Create an empty file ``config.yml`` in the root root of the project: + +.. code-block:: + :emphasize-lines: 11 + + ./ + ├── githubnavigator/ + │ ├── templates/ + │ │ ├── base.html + │ │ └── index.html + │ ├── __init__.py + │ ├── application.py + │ ├── containers.py + │ └── views.py + ├── venv/ + ├── config.yml + └── requirements.txt + +and put next into it:: + + github: + request_timeout: 10 + +We will use `PyYAML `_ library for parsing the configuration +file. Let's add it to the requirements file. + +Edit ``requirements.txt``: + +.. code-block:: + :emphasize-lines: 5 + + dependency-injector + flask + bootstrap-flask + pygithub + pyyaml + +and install it:: + + pip install --upgrade -r requirements.txt + +We will use environment variable ``GITHUB_TOKEN`` to provide the API token. + +Now we need to edit ``create_app()`` to make two things when application starts: + +- Load the configuration file the ``config.yml``. +- Load the API token from the ``GITHUB_TOKEN`` environment variable. + +Edit ``application.py``: + +.. code-block:: python + :emphasize-lines: 9-10 + + """Application module.""" + + from .containers import ApplicationContainer + + + def create_app(): + """Create and return Flask application.""" + container = ApplicationContainer() + container.config.from_yaml('config.yml') + container.config.github.auth_token.from_env('GITHUB_TOKEN') + + app = container.app() + app.container = container + + bootstrap = container.bootstrap() + bootstrap.init_app(app) + + app.add_url_rule('/', view_func=container.index_view.as_view()) + + return app + +Now we need create an API token. + +As for now, don't worry, just take this one: + +.. code-block:: bash + + export GITHUB_TOKEN=cbde697a6e01424856fde2b7f94a88d1b501320e + +.. note:: + + To create your own token: + + - Follow the `Github guide `_. + - Set the token to the environment variable: + + .. code-block:: bash + + export GITHUB_TOKEN= + +That's it. + +Github API client setup is done. + +Search service +-------------- + +Now it's time to add ``SearchService``. It will: + +- Perform the search. +- Fetch commit extra data for each result. +- Format result data + +``SearchService`` will use ``Github`` API client. + +Create empty file ``services.py`` in the ``githubnavigator`` package: + +.. code-block:: + :emphasize-lines: 9 + + ./ + ├── githubnavigator/ + │ ├── templates/ + │ │ ├── base.html + │ │ └── index.html + │ ├── __init__.py + │ ├── application.py + │ ├── containers.py + │ ├── services.py + │ └── views.py + ├── venv/ + ├── config.yml + └── requirements.txt + +and put next into it: + +.. code-block:: python + + """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, query, limit): + """Search for repositories and return formatted data.""" + repositories = self._github_client.search_repositories( + query=query, + **{'in': 'name'}, + ) + return [ + self._format_repo(repository) + for repository in repositories[:limit] + ] + + def _format_repo(self, repository: Repository): + commits = repository.get_commits() + return { + 'url': repository.html_url, + 'name': repository.name, + 'owner': { + 'login': repository.owner.login, + 'url': repository.owner.html_url, + 'avatar_url': repository.owner.avatar_url, + }, + 'latest_commit': self._format_commit(commits[0]) if commits else {}, + } + + def _format_commit(self, commit: Commit): + return { + 'sha': commit.sha, + 'url': commit.html_url, + 'message': commit.commit.message, + 'author_name': commit.commit.author.name, + } + +Now let's add ``SearchService`` to the container. + +Edit ``containers.py``: + +.. code-block:: python + :emphasize-lines: 9,27-30 + + """Application containers module.""" + + from dependency_injector import containers, providers + from dependency_injector.ext import flask + from flask import Flask + from flask_bootstrap import Bootstrap + from github import Github + + from . import services, views + + + class ApplicationContainer(containers.DeclarativeContainer): + """Application container.""" + + app = flask.Application(Flask, __name__) + + bootstrap = flask.Extension(Bootstrap) + + 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 = flask.View(views.index) + +Make the search work +-------------------- + +Now we are ready to make the search work. Let's use the ``SearchService`` in the ``index`` view. + +Edit ``views.py``: + +.. code-block:: python + :emphasize-lines: 5,8,12 + + """Views module.""" + + from flask import request, render_template + + from .services import SearchService + + + def index(search_service: SearchService): + query = request.args.get('query', 'Dependency Injector') + limit = request.args.get('limit', 10, int) + + repositories = search_service.search_repositories(query, limit) + + return render_template( + 'index.html', + query=query, + limit=limit, + repositories=repositories, + ) + +Now let's inject the ``SearchService`` dependency into the ``index`` view. + +Edit ``containers.py``: + +.. code-block:: python + :emphasize-lines: 32-38 + + """Application containers module.""" + + from dependency_injector import containers, providers + from dependency_injector.ext import flask + from flask import Flask + from flask_bootstrap import Bootstrap + from github import Github + + from . import services, views + + + class ApplicationContainer(containers.DeclarativeContainer): + """Application container.""" + + app = flask.Application(Flask, __name__) + + bootstrap = flask.Extension(Bootstrap) + + 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 = flask.View( + views.index, + search_service=search_service, + ) + +Make sure the app is running or use ``flask run`` and open ``http://127.0.0.1:5000/``. + +You should see: + +.. image:: flask_images/screen_02.png + +Make some refactoring +--------------------- + +Our ``index`` view has two hardcoded config values: + +- Default search term +- Limit of the results + +Let's make some refactoring. We will move these values to the config. + +Edit ``views.py``: + +.. code-block:: python + :emphasize-lines: 8-14 + + """Views module.""" + + from flask import request, render_template + + from .services import SearchService + + + def index( + search_service: SearchService, + default_query: str, + default_limit: int, + ): + query = request.args.get('query', default_query) + limit = request.args.get('limit', default_limit, int) + + repositories = search_service.search_repositories(query, limit) + + return render_template( + 'index.html', + query=query, + limit=limit, + repositories=repositories, + ) + +Now we need to inject these values. Let's update the container. + +Edit ``containers.py``: + +.. code-block:: python + :emphasize-lines: 35-36 + + """Application containers module.""" + + from dependency_injector import containers, providers + from dependency_injector.ext import flask + from flask import Flask + from flask_bootstrap import Bootstrap + from github import Github + + from . import services, views + + + class ApplicationContainer(containers.DeclarativeContainer): + """Application container.""" + + app = flask.Application(Flask, __name__) + + bootstrap = flask.Extension(Bootstrap) + + 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 = flask.View( + views.index, + search_service=search_service, + default_query=config.search.default_query, + default_limit=config.search.default_limit, + ) + +Finally let's update the config. + +Edit ``config.yml``: + +.. code-block:: + :emphasize-lines: 3-5 + + github: + request_timeout: 10 + search: + default_query: "Dependency Injector" + default_limit: 10 + +That's it. + +The refactoring is done. We've made it cleaner. + +Tests +----- + +It would be nice to add some tests. Let's do this. + +We will use `pytest `_ and +`coverage `_. + +Edit ``requirements.txt``: + +.. code-block:: + :emphasize-lines: 6-7 + + dependency-injector + flask + bootstrap-flask + pygithub + pyyaml + pytest-flask + pytest-cov + +And let's install it:: + + pip install -r requirements.txt + + +Create empty file ``tests.py`` in the ``githubnavigator`` package: + +.. code-block:: + :emphasize-lines: 10 + + ./ + ├── githubnavigator/ + │ ├── templates/ + │ │ ├── base.html + │ │ └── index.html + │ ├── __init__.py + │ ├── application.py + │ ├── containers.py + │ ├── services.py + │ ├── tests.py + │ └── views.py + ├── venv/ + ├── config.yml + └── requirements.txt + +and put next into it: + +.. code-block:: python + :emphasize-lines: 42,65 + + """Tests module.""" + + from unittest import mock + + import pytest + from github import Github + from flask import url_for + + from .application import create_app + + + @pytest.fixture + def app(): + return create_app() + + + def test_index(client, app): + 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', + ), + 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', + ), + get_commits=mock.Mock(return_value=[mock.Mock()]), + ), + ] + + with app.container.github_client.override(github_client_mock): + response = client.get(url_for('index')) + + assert response.status_code == 200 + assert b'Results found: 2' in response.data + + 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'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 + + + def test_index_no_results(client, app): + github_client_mock = mock.Mock(spec=Github) + github_client_mock.search_repositories.return_value = [] + + with app.container.github_client.override(github_client_mock): + response = client.get(url_for('index')) + + assert response.status_code == 200 + assert b'Results found: 0' in response.data + +Now let's run it and check the coverage:: + + py.test githubnavigator/tests.py --cov=githubnavigator + +You should see: + +.. 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 + 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 11 0 100% + githubnavigator/containers.py 13 0 100% + githubnavigator/services.py 14 0 100% + githubnavigator/tests.py 32 0 100% + githubnavigator/views.py 7 0 100% + ---------------------------------------------------- + TOTAL 77 0 100% + +.. note:: + + Take a look on the highlights in the ``tests.py``. + + It emphasizes the overriding of the ``Github`` API client. + +Conclusion +---------- + +We are done. + +It this tutorial we've build ``Flask`` application following dependency injection principle. +We've used ``Dependency Injector`` as a dependency injection framework. + +The heart of this application is the container. It keeps all the application components and their +dependencies in one place: + +.. code-block:: python + + """Application containers module.""" + + from dependency_injector import containers, providers + from dependency_injector.ext import flask + from flask import Flask + from flask_bootstrap import Bootstrap + from github import Github + + from . import services, views + + + class ApplicationContainer(containers.DeclarativeContainer): + """Application container.""" + + app = flask.Application(Flask, __name__) + + bootstrap = flask.Extension(Bootstrap) + + 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 = flask.View( + views.index, + search_service=search_service, + default_query=config.search.default_query, + default_limit=config.search.default_limit, + ) + +What's next? + +- Look at the other :ref:`tutorials`. +- Know more about the :ref:`providers`. +- Go to the :ref:`contents`. + + +.. disqus:: diff --git a/docs/tutorials/flask_images/screen_01.png b/docs/tutorials/flask_images/screen_01.png new file mode 100644 index 00000000..ecf44c9d Binary files /dev/null and b/docs/tutorials/flask_images/screen_01.png differ diff --git a/docs/tutorials/flask_images/screen_02.png b/docs/tutorials/flask_images/screen_02.png new file mode 100644 index 00000000..350aaa67 Binary files /dev/null and b/docs/tutorials/flask_images/screen_02.png differ diff --git a/docs/tutorials/index.rst b/docs/tutorials/index.rst index d3efc2b6..51c6f09a 100644 --- a/docs/tutorials/index.rst +++ b/docs/tutorials/index.rst @@ -1,3 +1,5 @@ +.. _tutorials: + Tutorials ========= @@ -10,3 +12,5 @@ frameworks. flask aiohttp asyncio + +.. disqus:: diff --git a/examples/miniapps/ghnav-flask/README.rst b/examples/miniapps/ghnav-flask/README.rst index 89f6f64f..ca6b3a2c 100644 --- a/examples/miniapps/ghnav-flask/README.rst +++ b/examples/miniapps/ghnav-flask/README.rst @@ -4,6 +4,8 @@ Flask Dependency Injection Example Application ``githubnavigator`` is a `Flask `_ + `Dependency Injector `_ application. +.. image:: screenshot.png + Run --- @@ -79,7 +81,7 @@ 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 + plugins: flask-1.0.0, cov-2.10.0 collected 2 items githubnavigator/tests.py .. [100%] @@ -88,10 +90,10 @@ The output should be something like: Name Stmts Miss Cover ---------------------------------------------------- githubnavigator/__init__.py 0 0 100% - githubnavigator/application.py 8 0 100% - githubnavigator/containers.py 11 0 100% + githubnavigator/application.py 11 0 100% + githubnavigator/containers.py 13 0 100% githubnavigator/services.py 14 0 100% - githubnavigator/tests.py 33 0 100% + githubnavigator/tests.py 32 0 100% githubnavigator/views.py 7 0 100% ---------------------------------------------------- - TOTAL 73 0 100% + TOTAL 77 0 100% diff --git a/examples/miniapps/ghnav-flask/config.yml b/examples/miniapps/ghnav-flask/config.yml index 9bd0ccd3..193fc06e 100644 --- a/examples/miniapps/ghnav-flask/config.yml +++ b/examples/miniapps/ghnav-flask/config.yml @@ -1,5 +1,5 @@ github: request_timeout: 10 search: - default_term: "Dependency Injector" - default_limit: 5 + default_query: "Dependency Injector" + default_limit: 10 diff --git a/examples/miniapps/ghnav-flask/githubnavigator/containers.py b/examples/miniapps/ghnav-flask/githubnavigator/containers.py index 872640f1..1bd47748 100644 --- a/examples/miniapps/ghnav-flask/githubnavigator/containers.py +++ b/examples/miniapps/ghnav-flask/githubnavigator/containers.py @@ -32,6 +32,6 @@ class ApplicationContainer(containers.DeclarativeContainer): index_view = flask.View( views.index, search_service=search_service, - default_search_term=config.search.default_term, - default_search_limit=config.search.default_limit, + default_query=config.search.default_query, + default_limit=config.search.default_limit, ) diff --git a/examples/miniapps/ghnav-flask/githubnavigator/services.py b/examples/miniapps/ghnav-flask/githubnavigator/services.py index 9df52007..9c6ff839 100644 --- a/examples/miniapps/ghnav-flask/githubnavigator/services.py +++ b/examples/miniapps/ghnav-flask/githubnavigator/services.py @@ -11,9 +11,12 @@ class SearchService: def __init__(self, github_client: Github): self._github_client = github_client - def search_repositories(self, term, limit): + def search_repositories(self, query, limit): """Search for repositories and return formatted data.""" - repositories = self._github_client.search_repositories(term, **{'in': 'name'}) + repositories = self._github_client.search_repositories( + query=query, + **{'in': 'name'}, + ) return [ self._format_repo(repository) for repository in repositories[:limit] diff --git a/examples/miniapps/ghnav-flask/githubnavigator/templates/index.html b/examples/miniapps/ghnav-flask/githubnavigator/templates/index.html index 9f75018e..c80c70ac 100644 --- a/examples/miniapps/ghnav-flask/githubnavigator/templates/index.html +++ b/examples/miniapps/ghnav-flask/githubnavigator/templates/index.html @@ -7,24 +7,32 @@

Github Navigator

-
- -
- -
-
+
+
+ + +
+
+ + +
+
- {% if repositories|length == 0 %} - No search results - {% endif %} - -

- Results found: {{ repositories|length }} -

+

Results found: {{ repositories|length }}

@@ -32,22 +40,29 @@ - - {% for repository in repositories %} {{n}} + {% for repository in repositories %} {{n}} - - + + - - {% endfor %} - + {% endfor %}
# Repository Repository owner Last commit
{{ loop.index }}{{ repository.name }}avatar - {{ repository.owner.login }} + + {{ repository.name }} + + avatar + + {{ repository.owner.login }} + + {{ repository.latest_commit.sha }} + {{ repository.latest_commit.message }} + {{ repository.latest_commit.author_name }} {{ repository.latest_commit.sha }} {{ repository.latest_commit['message'] }} {{ repository.latest_commit.author_name }}
diff --git a/examples/miniapps/ghnav-flask/githubnavigator/tests.py b/examples/miniapps/ghnav-flask/githubnavigator/tests.py index fa36678c..f8ea3d87 100644 --- a/examples/miniapps/ghnav-flask/githubnavigator/tests.py +++ b/examples/miniapps/ghnav-flask/githubnavigator/tests.py @@ -43,6 +43,7 @@ def test_index(client, app): response = client.get(url_for('index')) assert response.status_code == 200 + assert b'Results found: 2' in response.data assert b'repo1-url' in response.data assert b'repo1-name' in response.data @@ -65,4 +66,4 @@ def test_index_no_results(client, app): response = client.get(url_for('index')) assert response.status_code == 200 - assert b'No search results' in response.data + assert b'Results found: 0' in response.data diff --git a/examples/miniapps/ghnav-flask/githubnavigator/views.py b/examples/miniapps/ghnav-flask/githubnavigator/views.py index f444c559..9d97001c 100644 --- a/examples/miniapps/ghnav-flask/githubnavigator/views.py +++ b/examples/miniapps/ghnav-flask/githubnavigator/views.py @@ -5,15 +5,19 @@ 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) +def index( + search_service: SearchService, + default_query: str, + default_limit: int, +): + query = request.args.get('query', default_query) + limit = request.args.get('limit', default_limit, int) - repositories = search_service.search_repositories(search_term, limit) + repositories = search_service.search_repositories(query, limit) return render_template( 'index.html', - search_term=search_term, + query=query, limit=limit, repositories=repositories, ) diff --git a/examples/miniapps/ghnav-flask/requirements.txt b/examples/miniapps/ghnav-flask/requirements.txt index f24e8604..78a650f6 100644 --- a/examples/miniapps/ghnav-flask/requirements.txt +++ b/examples/miniapps/ghnav-flask/requirements.txt @@ -1,4 +1,7 @@ -dependency-injector[flask,yaml] +dependency-injector +flask +bootstrap-flask pygithub +pyyaml pytest-flask pytest-cov diff --git a/examples/miniapps/ghnav-flask/screenshot.png b/examples/miniapps/ghnav-flask/screenshot.png new file mode 100644 index 00000000..350aaa67 Binary files /dev/null and b/examples/miniapps/ghnav-flask/screenshot.png differ diff --git a/setup.py b/setup.py index f429e4bd..2aa43a52 100644 --- a/setup.py +++ b/setup.py @@ -97,6 +97,14 @@ setup(name='dependency-injector', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', + 'Framework :: AsyncIO', + 'Framework :: Bottle', + 'Framework :: Django', + 'Framework :: Flask', + 'Framework :: Pylons', + 'Framework :: Pyramid', + 'Framework :: Pytest', + 'Framework :: TurboGears', 'Topic :: Software Development', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Python Modules', diff --git a/src/dependency_injector/__init__.py b/src/dependency_injector/__init__.py index 64f79523..1a705d15 100644 --- a/src/dependency_injector/__init__.py +++ b/src/dependency_injector/__init__.py @@ -1,6 +1,6 @@ """Dependency injector top-level package.""" -__version__ = '3.22.0' +__version__ = '3.23.0' """Version number that follows semantic versioning. :type: str