From d1a6209d0b687b485da7ff23b758b5e49cea3501 Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Mon, 28 Feb 2022 14:30:13 +0100 Subject: [PATCH 01/44] ISSUE #149 --- .../minos-graphql-aiohttp/.editorconfig | 21 + .../plugins/minos-graphql-aiohttp/AUTHORS.md | 15 + .../plugins/minos-graphql-aiohttp/HISTORY.md | 5 + .../plugins/minos-graphql-aiohttp/LICENSE | 21 + .../plugins/minos-graphql-aiohttp/Makefile | 42 + .../plugins/minos-graphql-aiohttp/README.md | 36 + .../minos-graphql-aiohttp/RUNTHETESTS.md | 21 + .../plugins/minos-graphql-aiohttp/SETUP.md | 11 + .../minos-graphql-aiohttp/docs/Makefile | 20 + .../minos-graphql-aiohttp/docs/authors.rst | 1 + .../minos-graphql-aiohttp/docs/conf.py | 197 ++ .../minos-graphql-aiohttp/docs/history.rst | 1 + .../minos-graphql-aiohttp/docs/index.rst | 18 + .../minos-graphql-aiohttp/docs/make.bat | 36 + .../minos-graphql-aiohttp/docs/readme.rst | 4 + .../docs/runthetests.rst | 1 + .../minos-graphql-aiohttp/docs/usage.rst | 7 + .../minos/plugins/graphql_aiohttp/__init__.py | 19 + .../plugins/graphql_aiohttp/exceptions.py | 7 + .../graphql_aiohttp/graphql_example.py | 17 + .../minos/plugins/graphql_aiohttp/handlers.py | 169 ++ .../minos/plugins/graphql_aiohttp/requests.py | 234 +++ .../plugins/graphql_aiohttp/responses.py | 117 ++ .../minos/plugins/graphql_aiohttp/services.py | 44 + .../plugins/minos-graphql-aiohttp/poetry.lock | 1811 +++++++++++++++++ .../plugins/minos-graphql-aiohttp/poetry.toml | 2 + .../minos-graphql-aiohttp/pyproject.toml | 56 + .../plugins/minos-graphql-aiohttp/setup.cfg | 28 + .../minos-graphql-aiohttp/tests/__init__.py | 0 .../tests/services/commands.py | 36 + .../tests/services/queries.py | 19 + .../tests/test_config.yml | 39 + .../test_rest_aiohttp/test_handlers.py | 131 ++ .../test_rest_aiohttp/test_requests.py | 441 ++++ .../test_rest_aiohttp/test_services.py | 48 + .../test_plugins/test_rest_aiohttp/utils.py | 100 + .../minos-graphql-aiohttp/tests/utils.py | 27 + 37 files changed, 3802 insertions(+) create mode 100644 packages/plugins/minos-graphql-aiohttp/.editorconfig create mode 100644 packages/plugins/minos-graphql-aiohttp/AUTHORS.md create mode 100644 packages/plugins/minos-graphql-aiohttp/HISTORY.md create mode 100644 packages/plugins/minos-graphql-aiohttp/LICENSE create mode 100644 packages/plugins/minos-graphql-aiohttp/Makefile create mode 100644 packages/plugins/minos-graphql-aiohttp/README.md create mode 100644 packages/plugins/minos-graphql-aiohttp/RUNTHETESTS.md create mode 100644 packages/plugins/minos-graphql-aiohttp/SETUP.md create mode 100644 packages/plugins/minos-graphql-aiohttp/docs/Makefile create mode 100644 packages/plugins/minos-graphql-aiohttp/docs/authors.rst create mode 100755 packages/plugins/minos-graphql-aiohttp/docs/conf.py create mode 100644 packages/plugins/minos-graphql-aiohttp/docs/history.rst create mode 100644 packages/plugins/minos-graphql-aiohttp/docs/index.rst create mode 100644 packages/plugins/minos-graphql-aiohttp/docs/make.bat create mode 100644 packages/plugins/minos-graphql-aiohttp/docs/readme.rst create mode 100644 packages/plugins/minos-graphql-aiohttp/docs/runthetests.rst create mode 100644 packages/plugins/minos-graphql-aiohttp/docs/usage.rst create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/exceptions.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/requests.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/responses.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py create mode 100644 packages/plugins/minos-graphql-aiohttp/poetry.lock create mode 100644 packages/plugins/minos-graphql-aiohttp/poetry.toml create mode 100644 packages/plugins/minos-graphql-aiohttp/pyproject.toml create mode 100644 packages/plugins/minos-graphql-aiohttp/setup.cfg create mode 100644 packages/plugins/minos-graphql-aiohttp/tests/__init__.py create mode 100644 packages/plugins/minos-graphql-aiohttp/tests/services/commands.py create mode 100644 packages/plugins/minos-graphql-aiohttp/tests/services/queries.py create mode 100644 packages/plugins/minos-graphql-aiohttp/tests/test_config.yml create mode 100644 packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_handlers.py create mode 100644 packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_requests.py create mode 100644 packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_services.py create mode 100644 packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/utils.py create mode 100644 packages/plugins/minos-graphql-aiohttp/tests/utils.py diff --git a/packages/plugins/minos-graphql-aiohttp/.editorconfig b/packages/plugins/minos-graphql-aiohttp/.editorconfig new file mode 100644 index 000000000..d4a2c4405 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/.editorconfig @@ -0,0 +1,21 @@ +# http://editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true +charset = utf-8 +end_of_line = lf + +[*.bat] +indent_style = tab +end_of_line = crlf + +[LICENSE] +insert_final_newline = false + +[Makefile] +indent_style = tab diff --git a/packages/plugins/minos-graphql-aiohttp/AUTHORS.md b/packages/plugins/minos-graphql-aiohttp/AUTHORS.md new file mode 100644 index 000000000..30ff94991 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/AUTHORS.md @@ -0,0 +1,15 @@ +# Credits + +## Development Lead + +* Andrea Mucci + +## Core Devs + +* Sergio Garcia Prado +* Vladyslav Fenchak +* Alberto Amigo Alonso + +## Contributors + +None yet. Why not be the first? diff --git a/packages/plugins/minos-graphql-aiohttp/HISTORY.md b/packages/plugins/minos-graphql-aiohttp/HISTORY.md new file mode 100644 index 000000000..1cab554e5 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/HISTORY.md @@ -0,0 +1,5 @@ +# History + +## 0.0.1 (2022-02-17) + +* First release on PyPI. diff --git a/packages/plugins/minos-graphql-aiohttp/LICENSE b/packages/plugins/minos-graphql-aiohttp/LICENSE new file mode 100644 index 000000000..4daf85bf2 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Clariteia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/plugins/minos-graphql-aiohttp/Makefile b/packages/plugins/minos-graphql-aiohttp/Makefile new file mode 100644 index 000000000..854bc90bc --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/Makefile @@ -0,0 +1,42 @@ +.PHONY: docs + +lint: + poetry run flake8 + +test: + poetry run pytest + +coverage: + poetry run coverage run -m pytest + poetry run coverage report -m + poetry run coverage xml + +reformat: + poetry run black --line-length 120 minos tests + poetry run isort minos tests + +docs: + rm -rf docs/api + poetry run $(MAKE) --directory=docs html + +release: + $(MAKE) dist + poetry publish + +dist: + poetry build + ls -l dist + +install: + poetry install + +update: + poetry update + +check: + $(MAKE) install + $(MAKE) reformat + $(MAKE) lint + $(MAKE) test + $(MAKE) docs + $(MAKE) dist diff --git a/packages/plugins/minos-graphql-aiohttp/README.md b/packages/plugins/minos-graphql-aiohttp/README.md new file mode 100644 index 000000000..3be59ccff --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/README.md @@ -0,0 +1,36 @@ +

+ Minos logo +

+ +## minos-graphql-aiohttp + +[![PyPI Latest Release](https://img.shields.io/pypi/v/minos-broker-kafka.svg)](https://pypi.org/project/minos-broker-kafka/) +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/minos-framework/minos-python/pages%20build%20and%20deployment?label=docs)](https://minos-framework.github.io/minos-python) +[![License](https://img.shields.io/github/license/minos-framework/minos-python.svg)](https://github.com/minos-framework/minos-python/blob/main/LICENSE) +[![Coverage](https://codecov.io/github/minos-framework/minos-python/coverage.svg?branch=main)](https://codecov.io/gh/minos-framework/minos-python) +[![Stack Overflow](https://img.shields.io/badge/Stack%20Overflow-Ask%20a%20question-green)](https://stackoverflow.com/questions/tagged/minos) + +## Summary + +Minos is a framework which helps you create [reactive](https://www.reactivemanifesto.org/) microservices in Python. +Internally, it leverages Event Sourcing, CQRS and a message driven architecture to fulfil the commitments of an +asynchronous environment. + +## Documentation + +The official API Reference is publicly available at the [GitHub Pages](https://minos-framework.github.io/minos-python). + +## Source Code + +The source code of this project is hosted at the [GitHub Repository](https://github.com/minos-framework/minos-python). + +## Getting Help + +For usage questions, the best place to go to is [StackOverflow](https://stackoverflow.com/questions/tagged/minos). + +## Discussion and Development +Most development discussions take place over the [GitHub Issues](https://github.com/minos-framework/minos-python/issues). In addition, a [Gitter channel](https://gitter.im/minos-framework/community) is available for development-related questions. + +## License + +This project is distributed under the [MIT](https://raw.githubusercontent.com/minos-framework/minos-python/main/LICENSE) license. diff --git a/packages/plugins/minos-graphql-aiohttp/RUNTHETESTS.md b/packages/plugins/minos-graphql-aiohttp/RUNTHETESTS.md new file mode 100644 index 000000000..8b5e95b1f --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/RUNTHETESTS.md @@ -0,0 +1,21 @@ +Run the tests +============== + +In order to run the tests, please make sure you have the `Docker Engine `_ +and `Docker Compose `_ installed. + +Move into tests/ directory + +`cd tests/` + +Run service dependencies: + +`docker-compose up -d` + +Install library dependencies: + +`make install` + +Run tests: + +`make test` diff --git a/packages/plugins/minos-graphql-aiohttp/SETUP.md b/packages/plugins/minos-graphql-aiohttp/SETUP.md new file mode 100644 index 000000000..8203965c7 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/SETUP.md @@ -0,0 +1,11 @@ +Set up a development environment +================================= + +Since we use `poetry` as the default package manager, it must be installed. Please refer to +`https://python-poetry.org/docs/#installation`. + +Run `poetry install` to get the dependencies. + +Run `pre-commit install` to set the git checks before commiting. + +Make yourself sure you are able to run the tests. Refer to the appropriate section in this guide. diff --git a/packages/plugins/minos-graphql-aiohttp/docs/Makefile b/packages/plugins/minos-graphql-aiohttp/docs/Makefile new file mode 100644 index 000000000..86af5d733 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = python -msphinx +SPHINXPROJ = minos_rest_aiohttp +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/packages/plugins/minos-graphql-aiohttp/docs/authors.rst b/packages/plugins/minos-graphql-aiohttp/docs/authors.rst new file mode 100644 index 000000000..cf16fc494 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/docs/authors.rst @@ -0,0 +1 @@ +.. mdinclude:: ../AUTHORS.md diff --git a/packages/plugins/minos-graphql-aiohttp/docs/conf.py b/packages/plugins/minos-graphql-aiohttp/docs/conf.py new file mode 100755 index 000000000..b83dc06a1 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/docs/conf.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python +# +# minos documentation build configuration file, created by +# sphinx-quickstart on Fri Jun 9 13:47:02 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another +# directory, add these directories to sys.path here. If the directory is +# relative to the documentation root, use os.path.abspath to make it +# absolute, like shown here. +# +import os +import sys + +sys.path.insert(0, os.path.abspath("..")) + +import sphinx_rtd_theme + +from minos.plugins import ( + rest_aiohttp, +) + +# -- General configuration --------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. + + +extensions = [ + "sphinxcontrib.apidoc", + "sphinx.ext.autodoc", + "sphinx_autodoc_typehints", + "sphinx.ext.viewcode", + "sphinx_rtd_theme", + "m2r2", +] +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = [".rst", ".md"] + +# The master toctree document. +master_doc = "index" + +# General information about the project. +project = "Minos Rest Aiohttp" +copyright = "2021, Clariteia" +author = "Clariteia Devs" + +# The version info for the project you're documenting, acts as replacement +# for |version| and |release|, also used in various other places throughout +# the built documents. +# +# The short X.Y version. +version = rest_aiohttp.__version__ +# The full version, including alpha/beta/rc tags. +release = rest_aiohttp.__version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# + +html_theme = "sphinx_rtd_theme" + +# Theme options are theme-specific and customize the look and feel of a +# theme further. For a list of options available for each theme, see the +# documentation. +# + +# html_theme_options = { +# "codecov_button": True, +# "description": "Reactive microservices for an asynchronous world", +# "github_button": True, +# "github_user": "Clariteia", +# "github_repo": "plugins", +# "github_type": "star", +# } + +html_sidebars = {"**": ["about.html", "navigation.html", "searchbox.html"]} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + + +# -- Options for HTMLHelp output --------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = "minosdoc" + + +# -- Options for LaTeX output ------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass +# [howto, manual, or own class]). +latex_documents = [ + (master_doc, "minos.tex", "Minos Rest Aiohttp Documentation", "Clariteia Devs", "manual"), +] + + +# -- Options for manual page output ------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, "minos", "Minos Rest Aiohttp Documentation", [author], 1)] + + +# -- Options for Texinfo output ---------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "minos", + "Minos Rest Aiohttp Documentation", + author, + "minos", + "One line description of project.", + "Miscellaneous", + ), +] + +# "apidoc" extension +apidoc_module_dir = "../minos" +apidoc_output_dir = "api" +apidoc_separate_modules = True +autodoc_default_options = { + "inherited-members": True, + "special-members": "__init__", + "undoc-members": True, +} + +apidoc_toc_file = False +apidoc_module_first = True +apidoc_extra_args = [ + "--force", + "--implicit-namespaces", +] +# "autodoc typehints" extension + +set_type_checking_flag = True +typehints_fully_qualified = True diff --git a/packages/plugins/minos-graphql-aiohttp/docs/history.rst b/packages/plugins/minos-graphql-aiohttp/docs/history.rst new file mode 100644 index 000000000..d26e5be83 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/docs/history.rst @@ -0,0 +1 @@ +.. mdinclude:: ../HISTORY.md diff --git a/packages/plugins/minos-graphql-aiohttp/docs/index.rst b/packages/plugins/minos-graphql-aiohttp/docs/index.rst new file mode 100644 index 000000000..c1b30a0fd --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/docs/index.rst @@ -0,0 +1,18 @@ +Welcome to Minos Rest Aiohttp's documentation! +====================================== + +.. toctree:: + :maxdepth: 2 + + readme + runthetests + usage + api/minos + authors + history + +Indices and tables +==================== +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/packages/plugins/minos-graphql-aiohttp/docs/make.bat b/packages/plugins/minos-graphql-aiohttp/docs/make.bat new file mode 100644 index 000000000..916674840 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/docs/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=python -msphinx +) +set SOURCEDIR=. +set BUILDDIR=_build +set SPHINXPROJ=minos_rest_aiohttp + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The Sphinx module was not found. Make sure you have Sphinx installed, + echo.then set the SPHINXBUILD environment variable to point to the full + echo.path of the 'sphinx-build' executable. Alternatively you may add the + echo.Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/packages/plugins/minos-graphql-aiohttp/docs/readme.rst b/packages/plugins/minos-graphql-aiohttp/docs/readme.rst new file mode 100644 index 000000000..af09813a4 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/docs/readme.rst @@ -0,0 +1,4 @@ +Introduction +************** + +.. mdinclude:: ../README.md diff --git a/packages/plugins/minos-graphql-aiohttp/docs/runthetests.rst b/packages/plugins/minos-graphql-aiohttp/docs/runthetests.rst new file mode 100644 index 000000000..309db1b96 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/docs/runthetests.rst @@ -0,0 +1 @@ +.. mdinclude:: ../RUNTHETESTS.md diff --git a/packages/plugins/minos-graphql-aiohttp/docs/usage.rst b/packages/plugins/minos-graphql-aiohttp/docs/usage.rst new file mode 100644 index 000000000..1f662b1ca --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/docs/usage.rst @@ -0,0 +1,7 @@ +===== +Usage +===== + +To use Minos Rest Aiohttp in a project:: + + import minos_rest_aiohttp diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py new file mode 100644 index 000000000..1a9008f48 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py @@ -0,0 +1,19 @@ +__author__ = "Minos Framework Devs" +__email__ = "hey@minos.run" +__version__ = "0.6.0" + +from .exceptions import ( + RestResponseException, +) +from .handlers import ( + RestHandler, +) +from .requests import ( + RestRequest, +) +from .responses import ( + RestResponse, +) +from .services import ( + RestService, +) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/exceptions.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/exceptions.py new file mode 100644 index 000000000..e32f39495 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/exceptions.py @@ -0,0 +1,7 @@ +from minos.networks import ( + ResponseException, +) + + +class RestResponseException(ResponseException): + """Rest Response Exception class.""" diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py new file mode 100644 index 000000000..99bb89a39 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py @@ -0,0 +1,17 @@ +import asyncio +from graphql import ( + graphql, GraphQLSchema, GraphQLObjectType, GraphQLField, GraphQLString) + + +async def resolve_hello(obj, info): + await asyncio.sleep(3) + return 'world' + +schema = GraphQLSchema( + query=GraphQLObjectType( + name='RootQueryType', + fields={ + 'hello': GraphQLField( + GraphQLString, + resolve=resolve_hello) + })) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py new file mode 100644 index 000000000..573a751a4 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py @@ -0,0 +1,169 @@ +from __future__ import ( + annotations, +) + +import logging +from functools import ( + cached_property, + wraps, +) +from inspect import ( + isawaitable, +) +from typing import ( + Awaitable, + Callable, + Optional, + Union, +) + +from aiohttp import ( + web, +) + +from minos.common import ( + MinosConfig, + MinosSetup, +) +from minos.networks import ( + REQUEST_USER_CONTEXT_VAR, + EnrouteBuilder, + ResponseException, +) + +from .requests import ( + RestRequest, +) +from .responses import ( + RestResponse, +) + +logger = logging.getLogger(__name__) + + +class RestHandler(MinosSetup): + """Rest Handler class.""" + + def __init__(self, host: str, port: int, endpoints: dict[(str, str), Callable], **kwargs): + super().__init__(**kwargs) + self._host = host + self._port = port + self._endpoints = endpoints + + @property + def endpoints(self) -> dict[(str, str), Callable]: + """Endpoints getter. + + :return: A dictionary value. + """ + return self._endpoints + + @classmethod + def _from_config(cls, *args, config: MinosConfig, **kwargs) -> RestHandler: + host = config.rest.host + port = config.rest.port + endpoints = cls._endpoints_from_config(config) + + return cls(host=host, port=port, endpoints=endpoints, **kwargs) + + @staticmethod + def _endpoints_from_config(config: MinosConfig, **kwargs) -> dict[(str, str), Callable]: + builder = EnrouteBuilder(*config.services, middleware=config.middleware) + decorators = builder.get_rest_command_query(config=config, **kwargs) + endpoints = {(decorator.url, decorator.method): fn for decorator, fn in decorators.items()} + return endpoints + + @property + def host(self) -> str: + """Get the rest host. + + :return: A ``str`` object. + """ + return self._host + + @property + def port(self) -> int: + """Get the rest port. + + :return: An integer value. + """ + return self._port + + def get_app(self) -> web.Application: + """Return rest application instance. + + :return: A `web.Application` instance. + """ + return self._app + + @cached_property + def _app(self) -> web.Application: + app = web.Application() + self._mount_routes(app) + return app + + def _mount_routes(self, app: web.Application): + """Load routes from config file.""" + for (url, method), fn in self._endpoints.items(): + self._mount_one_route(method, url, fn, app) + + # Load default routes + self._mount_system_health(app) + + def _mount_one_route(self, method: str, url: str, action: Callable, app: web.Application) -> None: + handler = self.get_callback(action) + app.router.add_route(method, url, handler) + + @staticmethod + def get_callback( + fn: Callable[[RestRequest], Union[Optional[RestResponse], Awaitable[Optional[RestResponse]]]] + ) -> Callable[[web.Request], Awaitable[web.Response]]: + """Get the handler function to be used by the ``aiohttp`` Controller. + + :param fn: The action function. + :return: A wrapper function around the given one that is compatible with the ``aiohttp`` Controller. + """ + + @wraps(fn) + async def _wrapper(request: web.Request) -> web.Response: + logger.info(f"Dispatching '{request!s}' from '{request.remote!s}'...") + + request = RestRequest(request) + token = REQUEST_USER_CONTEXT_VAR.set(request.user) + + try: + response = fn(request) + if isawaitable(response): + response = await response + + if not isinstance(response, RestResponse): + response = RestResponse.from_response(response) + + content = await response.content() + content_type = response.content_type + status = response.status + + return web.Response(body=content, content_type=content_type, status=status) + + except ResponseException as exc: + logger.warning(f"Raised an application exception: {exc!s}") + return web.Response(text=str(exc), status=exc.status) + except Exception as exc: + logger.exception(f"Raised a system exception: {exc!r}") + raise web.HTTPInternalServerError() + finally: + REQUEST_USER_CONTEXT_VAR.reset(token) + + return _wrapper + + def _mount_system_health(self, app: web.Application): + """Mount System Health Route.""" + app.router.add_get("/system/health", self._system_health_handler) + + @staticmethod + async def _system_health_handler(request: web.Request) -> web.Response: + """System Health Route Handler. + :return: A `web.json_response` response. + """ + logger.info(f"Dispatching '{request!s}' from '{request.remote!s}'...") + return web.json_response({"host": request.host}) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/requests.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/requests.py new file mode 100644 index 000000000..0b99c93b0 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/requests.py @@ -0,0 +1,234 @@ +from __future__ import ( + annotations, +) + +import warnings +from collections import ( + defaultdict, +) +from collections.abc import ( + Callable, + Iterable, +) +from itertools import ( + chain, +) +from typing import ( + Any, + Optional, + Union, +) +from urllib.parse import ( + parse_qsl, +) +from uuid import ( + UUID, +) + +from aiohttp.web import Request as AioHttpRequest +from cached_property import ( + cached_property, +) + +from minos.common import ( + AvroDataDecoder, + AvroSchemaDecoder, + MinosAvroProtocol, + import_module, +) +from minos.networks import ( + NotHasParamsException, + Request, +) + + +class RestRequest(Request): + """Rest Request class.""" + + __slots__ = "raw" + + def __init__(self, raw: AioHttpRequest, *args, **kwargs): + super().__init__(*args, **kwargs) + self.raw = raw + + @property + def raw_request(self) -> AioHttpRequest: + """Get the raw request within the instance. + + :return: An ``aiohttp.web.Request`` instance. + """ + warnings.warn( + f"'{RestRequest.__name__}s.raw_request' is deprecated in favor of '{RestRequest.__name__}.raw'.", + DeprecationWarning, + ) + return self.raw + + def __eq__(self, other: RestRequest) -> bool: + return type(self) == type(other) and self.raw == other.raw + + def __repr__(self) -> str: + return f"{type(self).__name__}({self.raw!r})" + + @cached_property + def user(self) -> Optional[UUID]: + """ + Returns the UUID of the user making the Request. + """ + if "user" not in self.headers: + return None + return UUID(self.headers["user"]) + + @property + def headers(self) -> dict[str, str]: + """Get the headers of the request. + + :return: A dictionary in which keys are ``str`` instances and values are ``str`` instances. + """ + # noinspection PyTypeChecker + return self.raw.headers + + @property + def has_content(self) -> bool: + """Check if the request has content. + + :return: ``True`` if it has content or ``False`` otherwise. + """ + return self.raw.body_exists + + async def _content(self, type_: Optional[Union[type, str]] = None, **kwargs) -> Any: + if "model_type" in kwargs: + warnings.warn("The 'model_type' argument is deprecated. Use 'type_' instead", DeprecationWarning) + if type_ is None: + type_ = kwargs["model_type"] + + data = await self._content_parser() + return self._build(data, type_) + + @cached_property + def _content_parser(self) -> Callable: + mapper = { + "application/json": self._raw_json, + "application/x-www-form-urlencoded": self._raw_form, + "avro/binary": self._raw_avro, + "text/plain": self._raw_text, + "application/octet-stream": self._raw_bytes, + } + + if self.content_type not in mapper: + raise ValueError( + f"The given Content-Type ({self.content_type!r}) is not supported for automatic content parsing yet." + ) + + return mapper[self.content_type] + + async def _raw_json(self) -> Any: + return await self.raw.json() + + async def _raw_form(self) -> dict[str, Any]: + form = await self.raw.json(loads=parse_qsl) + return self._parse_multi_dict(form) + + async def _raw_avro(self) -> Any: + data = MinosAvroProtocol.decode(await self._raw_bytes()) + schema = MinosAvroProtocol.decode_schema(await self._raw_bytes()) + + type_ = AvroSchemaDecoder(schema).build() + return AvroDataDecoder(type_).build(data) + + async def _raw_text(self) -> str: + return await self.raw.text() + + async def _raw_bytes(self) -> bytes: + return await self.raw.read() + + @property + def content_type(self) -> str: + """Get the content type. + + :return: A ``str`` value. + """ + return self.raw.content_type + + @property + def has_params(self) -> bool: + """Check if the request has params. + + :return: ``True`` if it has params or ``False`` otherwise. + """ + sentinel = object() + return next(chain(self._raw_url_params, self._raw_query_params), sentinel) is not sentinel + + async def _params(self, type_: Optional[Union[type, str]] = None, **kwargs) -> dict[str, Any]: + data = self._parse_multi_dict(chain(self._raw_url_params, self._raw_query_params)) + return self._build(data, type_) + + async def url_params(self, type_: Optional[Union[type, str]] = None, **kwargs) -> Any: + """Get the url params. + + :param type_: Optional ``type`` or ``str`` (classname) that defines the request content type. + :param kwargs: Additional named arguments. + :return: A dictionary instance. + """ + if not self.has_url_params: + raise NotHasParamsException(f"{self!r} has not url params.") + + data = self._parse_multi_dict(self._raw_url_params) + return self._build(data, type_) + + @property + def has_url_params(self) -> bool: + """Check if the request has url params. + + :return: ``True`` if it has url params or ``False`` otherwise. + """ + sentinel = object() + return next(iter(self._raw_url_params), sentinel) is not sentinel + + @property + def _raw_url_params(self): + return self.raw.rel_url.query.items() # pragma: no cover + + async def query_params(self, type_: Optional[Union[type, str]] = None, **kwargs) -> Any: + """Get the query params. + + + :param type_: Optional ``type`` or ``str`` (classname) that defines the request content type. + :param kwargs: Additional named arguments. + :return: A dictionary instance. + """ + if not self.has_query_params: + raise NotHasParamsException(f"{self!r} has not query params.") + + data = self._parse_multi_dict(self._raw_query_params) + return self._build(data, type_) + + @property + def has_query_params(self) -> bool: + # noinspection GrazieInspection + """Check if the request has query params. + + :return: ``True`` if it has query params or ``False`` otherwise. + """ + sentinel = object() + return next(iter(self._raw_query_params), sentinel) is not sentinel + + @property + def _raw_query_params(self): + return self.raw.match_info.items() # pragma: no cover + + @staticmethod + def _build(data: Any, type_: Union[type, str]) -> Any: + if type_ is None: + return data + + if isinstance(type_, str): + type_ = import_module(type_) + + return AvroDataDecoder(type_).build(data) + + @staticmethod + def _parse_multi_dict(raw: Iterable[str, Any]) -> dict[str, Any]: + args = defaultdict(list) + for k, v in raw: + args[k].append(v) + return {k: v if len(v) > 1 else v[0] for k, v in args.items()} diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/responses.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/responses.py new file mode 100644 index 000000000..ca082d103 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/responses.py @@ -0,0 +1,117 @@ +from __future__ import ( + annotations, +) + +from collections.abc import ( + Callable, +) +from typing import ( + Any, + Awaitable, + Optional, +) +from urllib.parse import ( + urlencode, +) + +from cached_property import ( + cached_property, +) +from orjson import ( + orjson, +) + +from minos.common import ( + AvroDataEncoder, + AvroSchemaEncoder, + MinosAvroProtocol, + TypeHintBuilder, +) +from minos.networks import ( + Response, +) + + +class RestResponse(Response): + """Rest Response class.""" + + def __init__(self, *args, content_type: str = "application/json", **kwargs): + super().__init__(*args, **kwargs) + self.content_type = content_type + + @classmethod + def from_response(cls, response: Optional[Response]) -> RestResponse: + """Build a new ``RestRequest`` from another response. + + :param response: The base response. + :return: A ``RestResponse`` instance. + """ + if isinstance(response, RestResponse): + return response + + if response is None: + return RestResponse() + + return RestResponse(response._data) + + # noinspection PyUnusedLocal + async def content(self, **kwargs) -> Optional[bytes]: + """Raw response content. + + :param kwargs: Additional named arguments. + :return: The raw content as a ``bytes`` instance. + """ + if not self.has_content: + return None + return await self._content_parser() + + @cached_property + def _content_parser(self) -> Callable[[], Awaitable[bytes]]: + mapper = { + "application/json": self._raw_json, + "application/x-www-form-urlencoded": self._raw_form, + "avro/binary": self._raw_avro, + "text/plain": self._raw_text, + "application/octet-stream": self._raw_bytes, + } + + if self.content_type not in mapper: + return self._raw_bytes + + return mapper[self.content_type] + + async def _raw_json(self) -> bytes: + return orjson.dumps(self._raw_data) + + async def _raw_form(self) -> bytes: + return urlencode(self._raw_data).encode() + + async def _raw_avro(self) -> bytes: + type_ = TypeHintBuilder(self._data).build() + schema = AvroSchemaEncoder(type_).build() + data = AvroDataEncoder(self._data).build() + + return MinosAvroProtocol.encode(data, schema) + + async def _raw_text(self) -> bytes: + if not isinstance(self._raw_data, str): + raise ValueError( + f"Given 'Content-Type' ({self.content_type!r}) is not supported for the given data: {self._raw_data!r}." + ) + return self._raw_data.encode() + + async def _raw_bytes(self) -> bytes: + if not isinstance(self._raw_data, bytes): + raise ValueError( + f"Given 'Content-Type' ({self.content_type!r}) is not supported for the given data: {self._raw_data!r}." + ) + return self._raw_data + + # noinspection PyUnusedLocal + @cached_property + def _raw_data(self) -> Any: + """Raw response content. + + :return: A list of raw items. + """ + return AvroDataEncoder(self._data).build() diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py new file mode 100644 index 000000000..e9b01883e --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py @@ -0,0 +1,44 @@ +from aiohttp import ( + web, +) +from aiomisc.service.aiohttp import ( + AIOHTTPService, +) + +from .handlers import ( + RestHandler, +) +from graphql_server.aiohttp import GraphQLView + +from .graphql_example import schema + + +class RestService(AIOHTTPService): + """ + Rest Interface + + Expose REST Interface handler using aiomisc AIOHTTPService. + + """ + + def __init__(self, **kwargs): + pass + #self.handler = RestHandler.from_config(**kwargs) + #super().__init__(**(kwargs | {"address": self.handler.host, "port": self.handler.port})) + + async def create_application(self) -> web.Application: + """Create the web application. + + :return: A ``web.Application`` instance. + """ + app = web.Application() + + GraphQLView.attach(app, schema=schema, graphiql=True) + return app + # return self.handler.get_app() # pragma: no cover + + +async def main(): + web.run_app(RestService.create_application()) + + diff --git a/packages/plugins/minos-graphql-aiohttp/poetry.lock b/packages/plugins/minos-graphql-aiohttp/poetry.lock new file mode 100644 index 000000000..e4f51e8c9 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/poetry.lock @@ -0,0 +1,1811 @@ +[[package]] +name = "aiohttp" +version = "3.8.1" +description = "Async http client/server framework (asyncio)" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["aiodns", "brotli", "cchardet"] + +[[package]] +name = "aiomisc" +version = "15.6.8" +description = "aiomisc - miscellaneous utils for asyncio" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +colorlog = "*" + +[package.extras] +aiohttp = ["aiohttp"] +asgi = ["aiohttp-asgi"] +carbon = ["aiocarbon (>=0.15,<1.0)"] +contextvars = ["contextvars (>=2.4,<3.0)"] +cron = ["croniter (>=0.3.34,<0.4.0)"] +develop = ["aiocontextvars (==0.2.2)", "aiohttp-asgi", "aiohttp (<4)", "async-timeout", "coveralls", "croniter (>=0.3.34,<0.4.0)", "fastapi", "freezegun (<1.1)", "mypy (>=0.782,<1.0)", "pylava", "pytest", "pytest-cov (>=3.0,<4.0)", "pytest-freezegun (>=0.4.2,<0.5.0)", "pytest-rst", "pytest-subtests", "rich", "setproctitle", "sphinx-autobuild", "sphinx-intl", "sphinx (>=3.5.1)", "timeout-decorator", "tox (>=2.4)", "types-croniter"] +raven = ["raven-aiohttp"] +uvloop = ["uvloop (>=0.14,<1)"] + +[[package]] +name = "aiopg" +version = "1.3.3" +description = "Postgres integration with asyncio." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +async-timeout = ">=3.0,<5.0" +psycopg2-binary = ">=2.8.4" + +[package.extras] +sa = ["sqlalchemy[postgresql_psycopg2binary] (>=1.3,<1.5)"] + +[[package]] +name = "aiosignal" +version = "1.2.0" +description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "alabaster" +version = "0.7.12" +description = "A configurable sidebar-enabled Sphinx theme" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "21.4.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] +docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] + +[[package]] +name = "babel" +version = "2.9.1" +description = "Internationalization utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +pytz = ">=2015.7" + +[[package]] +name = "black" +version = "22.1.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = ">=1.1.0" +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "cached-property" +version = "1.5.2" +description = "A decorator for caching properties in classes." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "certifi" +version = "2021.10.8" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "charset-normalizer" +version = "2.0.12" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.0.4" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "colorlog" +version = "6.6.0" +description = "Add colours to the output of Python's logging module." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +development = ["black", "flake8", "mypy", "pytest", "types-colorama"] + +[[package]] +name = "coverage" +version = "6.3.2" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "crontab" +version = "0.23.0" +description = "Parse and use crontab schedules in Python" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "dependency-injector" +version = "4.38.0" +description = "Dependency injection framework for Python" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.7.0,<=1.16.0" + +[package.extras] +aiohttp = ["aiohttp"] +flask = ["flask"] +pydantic = ["pydantic"] +yaml = ["pyyaml"] + +[[package]] +name = "distlib" +version = "0.3.4" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "docutils" +version = "0.17.1" +description = "Docutils -- Python Documentation Utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "fastavro" +version = "1.4.9" +description = "Fast read/write of AVRO files" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +codecs = ["python-snappy", "zstandard", "lz4"] +lz4 = ["lz4"] +snappy = ["python-snappy"] +zstandard = ["zstandard"] + +[[package]] +name = "filelock" +version = "3.6.0" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] +testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] + +[[package]] +name = "flake8" +version = "4.0.1" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.8.0,<2.9.0" +pyflakes = ">=2.4.0,<2.5.0" + +[[package]] +name = "frozenlist" +version = "1.3.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "graphql-core" +version = "3.2.0" +description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." +category = "main" +optional = false +python-versions = ">=3.6,<4" + +[[package]] +name = "graphql-server" +version = "3.0.0b5" +description = "GraphQL Server tools for powering your server" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +aiohttp = {version = ">=3.8,<4", optional = true, markers = "extra == \"aiohttp\""} +graphql-core = ">=3.2,<3.3" +typing-extensions = ">=4,<5" + +[package.extras] +aiohttp = ["aiohttp (>=3.8,<4)"] +all = ["graphql-core (>=3.2,<3.3)", "typing-extensions (>=4,<5)", "flask (>=1,<2)", "sanic (>=20.3,<21)", "webob (>=1.8.7,<2)", "aiohttp (>=3.8,<4)", "quart (>=0.6.15,<0.15)"] +dev = ["graphql-core (>=3.2,<3.3)", "typing-extensions (>=4,<5)", "flask (>=1,<2)", "sanic (>=20.3,<21)", "webob (>=1.8.7,<2)", "aiohttp (>=3.8,<4)", "quart (>=0.6.15,<0.15)", "flake8 (>=4,<5)", "isort (>=5,<6)", "black (>=19.10b0)", "mypy (>=0.931,<1)", "check-manifest (>=0.47,<1)", "pytest (>=6.2,<6.3)", "pytest-asyncio (>=0.16,<1)", "pytest-cov (>=3,<4)", "aiohttp (>=3.8,<4)", "Jinja2 (>=2.11,<3)"] +flask = ["flask (>=1,<2)"] +quart = ["quart (>=0.6.15,<0.15)"] +sanic = ["sanic (>=20.3,<21)"] +test = ["graphql-core (>=3.2,<3.3)", "typing-extensions (>=4,<5)", "flask (>=1,<2)", "sanic (>=20.3,<21)", "webob (>=1.8.7,<2)", "aiohttp (>=3.8,<4)", "quart (>=0.6.15,<0.15)", "pytest (>=6.2,<6.3)", "pytest-asyncio (>=0.16,<1)", "pytest-cov (>=3,<4)", "aiohttp (>=3.8,<4)", "Jinja2 (>=2.11,<3)"] +webob = ["webob (>=1.8.7,<2)"] + +[[package]] +name = "identify" +version = "2.4.11" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.3" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "imagesize" +version = "1.3.0" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "importlib-metadata" +version = "4.11.2" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "isort" +version = "5.10.1" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6.1,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] +plugins = ["setuptools"] + +[[package]] +name = "jinja2" +version = "3.0.3" +description = "A very fast and expressive template engine." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "lmdb" +version = "1.3.0" +description = "Universal Python binding for the LMDB 'Lightning' Database" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "m2r2" +version = "0.3.2" +description = "Markdown and reStructuredText in a single file." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +docutils = "*" +mistune = "0.8.4" + +[[package]] +name = "markupsafe" +version = "2.1.0" +description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "minos-microservice-common" +version = "0.5.2" +description = "The common core of the Minos Framework" +category = "main" +optional = false +python-versions = "^3.9" +develop = true + +[package.dependencies] +aiomisc = ">=14.0.3,<16.0.0" +aiopg = "^1.2.1" +cached-property = "^1.5.2" +dependency-injector = "^4.32.2" +fastavro = "^1.4.0" +lmdb = "^1.2.1" +orjson = "^3.5.2" +PyYAML = ">=5.4.1,<7.0.0" + +[package.source] +type = "directory" +url = "../../core/minos-microservice-common" + +[[package]] +name = "minos-microservice-networks" +version = "0.5.2" +description = "The networks core of the Minos Framework" +category = "main" +optional = false +python-versions = "^3.9" +develop = true + +[package.dependencies] +aiohttp = "^3.7.4" +aiomisc = ">=14.0.3,<16.0.0" +aiopg = "^1.2.1" +crontab = "^0.23.0" +dependency-injector = "^4.32.2" +minos-microservice-common = "^0.5.0" +orjson = "^3.6.5" + +[package.source] +type = "directory" +url = "../../core/minos-microservice-networks" + +[[package]] +name = "mistune" +version = "0.8.4" +description = "The fastest markdown parser in pure Python" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "multidict" +version = "6.0.2" +description = "multidict implementation" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "nodeenv" +version = "1.6.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "orjson" +version = "3.6.7" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "pbr" +version = "5.8.1" +description = "Python Build Reasonableness" +category = "dev" +optional = false +python-versions = ">=2.6" + +[[package]] +name = "platformdirs" +version = "2.5.1" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "2.17.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + +[[package]] +name = "psycopg2-binary" +version = "2.9.3" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pycodestyle" +version = "2.8.0" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pyflakes" +version = "2.4.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pygments" +version = "2.11.2" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "pyparsing" +version = "3.0.7" +description = "Python parsing module" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "6.2.5" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +toml = "*" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] + +[[package]] +name = "pytz" +version = "2021.3" +description = "World timezone definitions, modern and historical" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "requests" +version = "2.27.1" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "sphinx" +version = "4.4.0" +description = "Python documentation generator" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=1.3" +colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.14,<0.18" +imagesize = "*" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} +Jinja2 = ">=2.3" +packaging = "*" +Pygments = ">=2.0" +requests = ">=2.5.0" +snowballstemmer = ">=1.1" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "docutils-stubs", "types-typed-ast", "types-requests"] +test = ["pytest", "pytest-cov", "html5lib", "cython", "typed-ast"] + +[[package]] +name = "sphinx-autodoc-typehints" +version = "1.17.0" +description = "Type hints (PEP 484) support for the Sphinx autodoc extension" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +Sphinx = ">=4" + +[package.extras] +testing = ["covdefaults (>=2)", "coverage (>=6)", "diff-cover (>=6.4)", "nptyping (>=1)", "pytest (>=6)", "pytest-cov (>=3)", "sphobjinv (>=2)", "typing-extensions (>=3.5)"] +type_comments = ["typed-ast (>=1.4.0)"] + +[[package]] +name = "sphinx-rtd-theme" +version = "1.0.0" +description = "Read the Docs theme for Sphinx" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" + +[package.dependencies] +docutils = "<0.18" +sphinx = ">=1.6" + +[package.extras] +dev = ["transifex-client", "sphinxcontrib-httpdomain", "bump2version"] + +[[package]] +name = "sphinxcontrib-apidoc" +version = "0.3.0" +description = "A Sphinx extension for running 'sphinx-apidoc' on each build" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +pbr = "*" +Sphinx = ">=1.6.0" + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.2" +description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.0" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest", "html5lib"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +test = ["pytest", "flake8", "mypy"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["flake8", "mypy", "docutils-stubs"] +test = ["pytest"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "typing-extensions" +version = "4.1.1" +description = "Backported and Experimental Type Hints for Python 3.6+" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "urllib3" +version = "1.26.8" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.13.2" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +distlib = ">=0.3.1,<1" +filelock = ">=3.2,<4" +platformdirs = ">=2,<3" +six = ">=1.9.0,<2" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] + +[[package]] +name = "yarl" +version = "1.7.2" +description = "Yet another URL library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[[package]] +name = "zipp" +version = "3.7.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.9" +content-hash = "0dbb3f5d394a3212b60abcc7c2b56b0babee5106f012b46671236628363623bc" + +[metadata.files] +aiohttp = [ + {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8"}, + {file = "aiohttp-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1"}, + {file = "aiohttp-3.8.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3"}, + {file = "aiohttp-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2"}, + {file = "aiohttp-3.8.1-cp310-cp310-win32.whl", hash = "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa"}, + {file = "aiohttp-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32"}, + {file = "aiohttp-3.8.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091"}, + {file = "aiohttp-3.8.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782"}, + {file = "aiohttp-3.8.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4"}, + {file = "aiohttp-3.8.1-cp36-cp36m-win32.whl", hash = "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602"}, + {file = "aiohttp-3.8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96"}, + {file = "aiohttp-3.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2"}, + {file = "aiohttp-3.8.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7"}, + {file = "aiohttp-3.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c"}, + {file = "aiohttp-3.8.1-cp37-cp37m-win32.whl", hash = "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9"}, + {file = "aiohttp-3.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b"}, + {file = "aiohttp-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675"}, + {file = "aiohttp-3.8.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155"}, + {file = "aiohttp-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33"}, + {file = "aiohttp-3.8.1-cp38-cp38-win32.whl", hash = "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a"}, + {file = "aiohttp-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74"}, + {file = "aiohttp-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf"}, + {file = "aiohttp-3.8.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866"}, + {file = "aiohttp-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2"}, + {file = "aiohttp-3.8.1-cp39-cp39-win32.whl", hash = "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1"}, + {file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"}, + {file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"}, +] +aiomisc = [ + {file = "aiomisc-15.6.8-py3-none-any.whl", hash = "sha256:b65f7f8e50d9791bbb745253e814862880b8a35bf21e4eea545ada9daf89836c"}, + {file = "aiomisc-15.6.8.tar.gz", hash = "sha256:09024d1f544de98b152dad8f7eb7d02c64457aaaaf03bd6c76a20b73328a3a74"}, +] +aiopg = [ + {file = "aiopg-1.3.3-py3-none-any.whl", hash = "sha256:2842dd8741460eeef940032dcb577bfba4d4115205dd82a73ce13b3271f5bf0a"}, + {file = "aiopg-1.3.3.tar.gz", hash = "sha256:547c6ba4ea0d73c2a11a2f44387d7133cc01d3c6f3b8ed976c0ac1eff4f595d7"}, +] +aiosignal = [ + {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, + {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, +] +alabaster = [ + {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, + {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, +] +async-timeout = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, + {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, +] +attrs = [ + {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, + {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, +] +babel = [ + {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, + {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, +] +black = [ + {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, + {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, + {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, + {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, + {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, + {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, + {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, + {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, + {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, + {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, + {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, + {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, + {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, + {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, + {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, + {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, + {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, + {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, + {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, + {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, + {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, +] +cached-property = [ + {file = "cached-property-1.5.2.tar.gz", hash = "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130"}, + {file = "cached_property-1.5.2-py2.py3-none-any.whl", hash = "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0"}, +] +certifi = [ + {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, + {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, +] +cfgv = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, +] +click = [ + {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"}, + {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +colorlog = [ + {file = "colorlog-6.6.0-py2.py3-none-any.whl", hash = "sha256:351c51e866c86c3217f08e4b067a7974a678be78f07f85fc2d55b8babde6d94e"}, + {file = "colorlog-6.6.0.tar.gz", hash = "sha256:344f73204009e4c83c5b6beb00b3c45dc70fcdae3c80db919e0a4171d006fde8"}, +] +coverage = [ + {file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"}, + {file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"}, + {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"}, + {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"}, + {file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"}, + {file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"}, + {file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"}, + {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"}, + {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"}, + {file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"}, + {file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"}, + {file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"}, + {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"}, + {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"}, + {file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"}, + {file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"}, + {file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"}, + {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"}, + {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"}, + {file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"}, + {file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"}, + {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, + {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, +] +crontab = [ + {file = "crontab-0.23.0.tar.gz", hash = "sha256:ca79dede9c2f572bb32f38703e8fddcf3427e86edc838f2ffe7ae4b9ee2b0733"}, +] +dependency-injector = [ + {file = "dependency-injector-4.38.0.tar.gz", hash = "sha256:bab4c323d822d3fc9936e8eb3c2f5553d75e9efdadac11d5b293a016e31a1477"}, + {file = "dependency_injector-4.38.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:025eb5f97021663715bff8e01feb83d5b2f66fc17ece1042a194f1aae88c0d85"}, + {file = "dependency_injector-4.38.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3c61334b196ab767eae43af207764349287d5eb0283d8ed1ab24608121aea35"}, + {file = "dependency_injector-4.38.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9bf4bfc52015e81c4f17647b7bbbe4e4549f041b3c6635b44a9ecede7594932c"}, + {file = "dependency_injector-4.38.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7aeba5882b06baf78978f33228e4c44133dada9173e330da68fbccca98520848"}, + {file = "dependency_injector-4.38.0-cp310-cp310-win32.whl", hash = "sha256:445dbf5324eee215a465d7f3b1965b05e199c31caa09e63abf0f02d982791306"}, + {file = "dependency_injector-4.38.0-cp310-cp310-win_amd64.whl", hash = "sha256:880edbcb5d810faa0623112e2e652a7bec73d20ce0708d9db998a0a40b62cbb9"}, + {file = "dependency_injector-4.38.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cd6f1c130462e7461a43f82fdc0d2ba25b5ef594db823dbd0e57f3d1b7c44f86"}, + {file = "dependency_injector-4.38.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be75905f7513886699d3ff5ed9aca233175c03808fc888da2a53b83af0a5d65"}, + {file = "dependency_injector-4.38.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d7f1a7d27de6295ce8b7ca69d1177817bf36c7cbaf73819e4bab04f87c5e962"}, + {file = "dependency_injector-4.38.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e9c1ff387d7b7d814e9d29a531c6804acc67b1cca578c09abd3590fa7ec6bf06"}, + {file = "dependency_injector-4.38.0-cp36-cp36m-win32.whl", hash = "sha256:7770efcbc6915dabbb31ea0bdeee1105dabf76e1c8e31a454cb1983dcf19ecf1"}, + {file = "dependency_injector-4.38.0-cp36-cp36m-win_amd64.whl", hash = "sha256:9e89a9c88317ad237abfb374c410e1466476ffefe6c44f3caeca3dce747a635c"}, + {file = "dependency_injector-4.38.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb75cd29c132bfaf03a11a6ac4f459dddb7a6526669543de57d2bb5fddf78def"}, + {file = "dependency_injector-4.38.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d116c56e7fc3b59b3afa6078163b5f6ff4680ebf27800dd4032de7a04f7ef777"}, + {file = "dependency_injector-4.38.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9df330ef9e24b6f94e6764d23180350c2fb99785257233ee4738e066b876fa7f"}, + {file = "dependency_injector-4.38.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7a46639038313f64eca2dc858ac4cd9e0aca5ea21bb005f5d754aadd6446ba4b"}, + {file = "dependency_injector-4.38.0-cp37-cp37m-win32.whl", hash = "sha256:d0d983b269b657744058b5858afc3c59443db53afe9c3e709e316daa9f9b9454"}, + {file = "dependency_injector-4.38.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1446df58169c166a5a2d5644ba9eeb3b7f2f679f260596f78d012c58ff140d92"}, + {file = "dependency_injector-4.38.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:307396f2d9d2f532fe92079a56d40af5fc60dacb1d766e3f9cd04c3802a1c251"}, + {file = "dependency_injector-4.38.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd96b6c454ab86648f57f37b895c1c976b1a82b76f835011f607ee8a78ebc0e"}, + {file = "dependency_injector-4.38.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fb44b17e649cabcfd1f370ecfd492ac7a93216776d91954b31598eecb36cdb13"}, + {file = "dependency_injector-4.38.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ab8ebe728dd9b3be23c40ca5a5dbe195050d9ad35d42d7e9fdaea816f7134584"}, + {file = "dependency_injector-4.38.0-cp38-cp38-win32.whl", hash = "sha256:1da3bad1452212bab76e87fbf7a71d3675190a1a909f345aaf8bae2fa97b878f"}, + {file = "dependency_injector-4.38.0-cp38-cp38-win_amd64.whl", hash = "sha256:2918776e88de88be0e2be036261180ca0605c8f64ead43d835ce852f16a9efd2"}, + {file = "dependency_injector-4.38.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:493be62561218624380d4eca9243a46753444f677217db6292a7b715cf429172"}, + {file = "dependency_injector-4.38.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a70511db88f84ac4228b27e37e12ea0e04a9c2a32cae3602b9af9a27c0db992"}, + {file = "dependency_injector-4.38.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e3e1cfe41a5ada0ff71889563441f538caff0399e41d3ee377b60fa50a858bf"}, + {file = "dependency_injector-4.38.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:116cc679a2c6d40c6a4f968aefe68535e596e88e96315dbd0d0ad2ff76654e3d"}, + {file = "dependency_injector-4.38.0-cp39-cp39-win32.whl", hash = "sha256:f703c2c161de067ba9893b56743d24fb4c9dbff92eb504bc082c2d2cfeab4c01"}, + {file = "dependency_injector-4.38.0-cp39-cp39-win_amd64.whl", hash = "sha256:6821a864d6405dc0d5f794ac1b10da4a8c7e8731c6a0651a9682a0b76f5a5b3e"}, + {file = "dependency_injector-4.38.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6c9e8dc91aa5831bd3a58fec1b94ed8c52f78f601f5ab91135b5ad44325beef7"}, + {file = "dependency_injector-4.38.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b4eedadef0c84295b70803a79a3ce5a10a59dddd8f306876f6fa6bfc4de8e00"}, + {file = "dependency_injector-4.38.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2e88b5d09c80e20d6b3d985cc360f39a81e748667c20f6bc7eee9f4b832176ed"}, + {file = "dependency_injector-4.38.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b1fcd71ff46e097f4e47917ccdf6aa388b6a6372557f7d9f83db1e879e95f8bb"}, + {file = "dependency_injector-4.38.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:9633d6366282e83a3f21543c5c78299787948333d9fe6649b020cfac93d8b7ca"}, +] +distlib = [ + {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, + {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, +] +docutils = [ + {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, + {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, +] +fastavro = [ + {file = "fastavro-1.4.9-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:2e64a77c529b638e89a879ff0211debfab5b2d114c26a2af29c81f6b013f395a"}, + {file = "fastavro-1.4.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fc9c95b7c1d59c5a2d29be21075870a122152cf927d84587dafc96da6b2ac3d"}, + {file = "fastavro-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:927fd6148a8dd9646c129c0a0e8571aea829abc3cba04a3d5a4010a866934f4c"}, + {file = "fastavro-1.4.9-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:000b70c5109a61bdbfddeb2821a506de8f5333f243c608cbced61d44657d6c2f"}, + {file = "fastavro-1.4.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37a77a1b5347a06416e236c77027c750aaeda29ef8189aa456eb2a2571274b43"}, + {file = "fastavro-1.4.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce2c7747ce958115388872db0756d3eeb0d796084eea9b46dc3758ef32c4d952"}, + {file = "fastavro-1.4.9-cp37-cp37m-win_amd64.whl", hash = "sha256:d6ccb77604903a0308316e696bb65a8943361af5f757d10985689656c9bce6ed"}, + {file = "fastavro-1.4.9-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9a6ada2d6e133a2319438248c2e023b6735747b249c5a79d5f08f9d431e5d226"}, + {file = "fastavro-1.4.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7537e4df7782b03b9761e9338cef9fc7bfcc41100ab93c36c5c60fa568e724a"}, + {file = "fastavro-1.4.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a9cd6d8471beb4020b4126fd04150ed7295f74ae7234d0dc9205b55c193851e"}, + {file = "fastavro-1.4.9-cp38-cp38-win_amd64.whl", hash = "sha256:fa9d8b47e0533c84152332ad491bb63bbae76a8a7a0df1caa821e0cbebf0fb70"}, + {file = "fastavro-1.4.9-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:3759bdc77009ee1e2e76fc9f58b951c05c00a8600ef9ddbff59fee3cb0c9e235"}, + {file = "fastavro-1.4.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b98ef2bdb123b95945aa6d69d6a7d79f211df3274b2dd7786da7852ddec964d0"}, + {file = "fastavro-1.4.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32b804aa6920d80c0e94e1180d480f28f56c4b901849bd80ed180851752b5ce6"}, + {file = "fastavro-1.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:f9b04acaf06b16218b47985e92d8daa98c1116d58f3cff81a5b3cf39cef9afc0"}, + {file = "fastavro-1.4.9.tar.gz", hash = "sha256:be3fec387eb2cdc9627060b5ae0690542c687dddc951b63fa11203553769ae5e"}, +] +filelock = [ + {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, + {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, +] +flake8 = [ + {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, + {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, +] +frozenlist = [ + {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3"}, + {file = "frozenlist-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4a44ebbf601d7bac77976d429e9bdb5a4614f9f4027777f9e54fd765196e9d3b"}, + {file = "frozenlist-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:45334234ec30fc4ea677f43171b18a27505bfb2dba9aca4398a62692c0ea8868"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47be22dc27ed933d55ee55845d34a3e4e9f6fee93039e7f8ebadb0c2f60d403f"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03a7dd1bfce30216a3f51a84e6dd0e4a573d23ca50f0346634916ff105ba6e6b"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:691ddf6dc50480ce49f68441f1d16a4c3325887453837036e0fb94736eae1e58"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bde99812f237f79eaf3f04ebffd74f6718bbd216101b35ac7955c2d47c17da02"}, + {file = "frozenlist-1.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a202458d1298ced3768f5a7d44301e7c86defac162ace0ab7434c2e961166e8"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9e3e9e365991f8cc5f5edc1fd65b58b41d0514a6a7ad95ef5c7f34eb49b3d3e"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:04cb491c4b1c051734d41ea2552fde292f5f3a9c911363f74f39c23659c4af78"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:436496321dad302b8b27ca955364a439ed1f0999311c393dccb243e451ff66aa"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:754728d65f1acc61e0f4df784456106e35afb7bf39cfe37227ab00436fb38676"}, + {file = "frozenlist-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb275c6385dd72594758cbe96c07cdb9bd6becf84235f4a594bdf21e3596c9d"}, + {file = "frozenlist-1.3.0-cp310-cp310-win32.whl", hash = "sha256:e30b2f9683812eb30cf3f0a8e9f79f8d590a7999f731cf39f9105a7c4a39489d"}, + {file = "frozenlist-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f7353ba3367473d1d616ee727945f439e027f0bb16ac1a750219a8344d1d5d3c"}, + {file = "frozenlist-1.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88aafd445a233dbbf8a65a62bc3249a0acd0d81ab18f6feb461cc5a938610d24"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4406cfabef8f07b3b3af0f50f70938ec06d9f0fc26cbdeaab431cbc3ca3caeaa"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8cf829bd2e2956066dd4de43fd8ec881d87842a06708c035b37ef632930505a2"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:603b9091bd70fae7be28bdb8aa5c9990f4241aa33abb673390a7f7329296695f"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25af28b560e0c76fa41f550eacb389905633e7ac02d6eb3c09017fa1c8cdfde1"}, + {file = "frozenlist-1.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c7a8a9fc9383b52c410a2ec952521906d355d18fccc927fca52ab575ee8b93"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:65bc6e2fece04e2145ab6e3c47428d1bbc05aede61ae365b2c1bddd94906e478"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3f7c935c7b58b0d78c0beea0c7358e165f95f1fd8a7e98baa40d22a05b4a8141"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd89acd1b8bb4f31b47072615d72e7f53a948d302b7c1d1455e42622de180eae"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6983a31698490825171be44ffbafeaa930ddf590d3f051e397143a5045513b01"}, + {file = "frozenlist-1.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:adac9700675cf99e3615eb6a0eb5e9f5a4143c7d42c05cea2e7f71c27a3d0846"}, + {file = "frozenlist-1.3.0-cp37-cp37m-win32.whl", hash = "sha256:0c36e78b9509e97042ef869c0e1e6ef6429e55817c12d78245eb915e1cca7468"}, + {file = "frozenlist-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:57f4d3f03a18facacb2a6bcd21bccd011e3b75d463dc49f838fd699d074fabd1"}, + {file = "frozenlist-1.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8c905a5186d77111f02144fab5b849ab524f1e876a1e75205cd1386a9be4b00a"}, + {file = "frozenlist-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b5009062d78a8c6890d50b4e53b0ddda31841b3935c1937e2ed8c1bda1c7fb9d"}, + {file = "frozenlist-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2fdc3cd845e5a1f71a0c3518528bfdbfe2efaf9886d6f49eacc5ee4fd9a10953"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e650bd09b5dda929523b9f8e7f99b24deac61240ecc1a32aeba487afcd970f"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:40dff8962b8eba91fd3848d857203f0bd704b5f1fa2b3fc9af64901a190bba08"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:768efd082074bb203c934e83a61654ed4931ef02412c2fbdecea0cff7ecd0274"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:006d3595e7d4108a12025ddf415ae0f6c9e736e726a5db0183326fd191b14c5e"}, + {file = "frozenlist-1.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:871d42623ae15eb0b0e9df65baeee6976b2e161d0ba93155411d58ff27483ad8"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aff388be97ef2677ae185e72dc500d19ecaf31b698986800d3fc4f399a5e30a5"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9f892d6a94ec5c7b785e548e42722e6f3a52f5f32a8461e82ac3e67a3bd073f1"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:e982878792c971cbd60ee510c4ee5bf089a8246226dea1f2138aa0bb67aff148"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c6c321dd013e8fc20735b92cb4892c115f5cdb82c817b1e5b07f6b95d952b2f0"}, + {file = "frozenlist-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:30530930410855c451bea83f7b272fb1c495ed9d5cc72895ac29e91279401db3"}, + {file = "frozenlist-1.3.0-cp38-cp38-win32.whl", hash = "sha256:40ec383bc194accba825fbb7d0ef3dda5736ceab2375462f1d8672d9f6b68d07"}, + {file = "frozenlist-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:f20baa05eaa2bcd5404c445ec51aed1c268d62600362dc6cfe04fae34a424bd9"}, + {file = "frozenlist-1.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0437fe763fb5d4adad1756050cbf855bbb2bf0d9385c7bb13d7a10b0dd550486"}, + {file = "frozenlist-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b684c68077b84522b5c7eafc1dc735bfa5b341fb011d5552ebe0968e22ed641c"}, + {file = "frozenlist-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:93641a51f89473837333b2f8100f3f89795295b858cd4c7d4a1f18e299dc0a4f"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6d32ff213aef0fd0bcf803bffe15cfa2d4fde237d1d4838e62aec242a8362fa"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31977f84828b5bb856ca1eb07bf7e3a34f33a5cddce981d880240ba06639b94d"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c62964192a1c0c30b49f403495911298810bada64e4f03249ca35a33ca0417a"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4eda49bea3602812518765810af732229b4291d2695ed24a0a20e098c45a707b"}, + {file = "frozenlist-1.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acb267b09a509c1df5a4ca04140da96016f40d2ed183cdc356d237286c971b51"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e1e26ac0a253a2907d654a37e390904426d5ae5483150ce3adedb35c8c06614a"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f96293d6f982c58ebebb428c50163d010c2f05de0cde99fd681bfdc18d4b2dc2"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e84cb61b0ac40a0c3e0e8b79c575161c5300d1d89e13c0e02f76193982f066ed"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:ff9310f05b9d9c5c4dd472983dc956901ee6cb2c3ec1ab116ecdde25f3ce4951"}, + {file = "frozenlist-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d26b650b71fdc88065b7a21f8ace70175bcf3b5bdba5ea22df4bfd893e795a3b"}, + {file = "frozenlist-1.3.0-cp39-cp39-win32.whl", hash = "sha256:01a73627448b1f2145bddb6e6c2259988bb8aee0fb361776ff8604b99616cd08"}, + {file = "frozenlist-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:772965f773757a6026dea111a15e6e2678fbd6216180f82a48a40b27de1ee2ab"}, + {file = "frozenlist-1.3.0.tar.gz", hash = "sha256:ce6f2ba0edb7b0c1d8976565298ad2deba6f8064d2bebb6ffce2ca896eb35b0b"}, +] +graphql-core = [ + {file = "graphql-core-3.2.0.tar.gz", hash = "sha256:86e2a0be008bfde19ef78388de8a725a1d942a9190ca431c24a60837973803ce"}, + {file = "graphql_core-3.2.0-py3-none-any.whl", hash = "sha256:0dda7e63676f119bb3d814621190fedad72fda07a8e9ab780bedd9f1957c6dc6"}, +] +graphql-server = [ + {file = "graphql-server-3.0.0b5.tar.gz", hash = "sha256:85c12c421e14f339883865d75fe316f626f3f875c70f8c366a93d50c5b1c8a23"}, +] +identify = [ + {file = "identify-2.4.11-py2.py3-none-any.whl", hash = "sha256:fd906823ed1db23c7a48f9b176a1d71cb8abede1e21ebe614bac7bdd688d9213"}, + {file = "identify-2.4.11.tar.gz", hash = "sha256:2986942d3974c8f2e5019a190523b0b0e2a07cb8e89bf236727fb4b26f27f8fd"}, +] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +imagesize = [ + {file = "imagesize-1.3.0-py2.py3-none-any.whl", hash = "sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c"}, + {file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.11.2-py3-none-any.whl", hash = "sha256:d16e8c1deb60de41b8e8ed21c1a7b947b0bc62fab7e1d470bcdf331cea2e6735"}, + {file = "importlib_metadata-4.11.2.tar.gz", hash = "sha256:b36ffa925fe3139b2f6ff11d6925ffd4fa7bc47870165e3ac260ac7b4f91e6ac"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +isort = [ + {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, +] +jinja2 = [ + {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"}, + {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"}, +] +lmdb = [ + {file = "lmdb-1.3.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:63cb73fe7ce9eb93d992d632c85a0476b4332670d9e6a2802b5062f603b7809f"}, + {file = "lmdb-1.3.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:abbc439cd9fe60ffd6197009087ea885ac150017dc85384093b1d376f83f0ec4"}, + {file = "lmdb-1.3.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6260a526e4ad85b1f374a5ba9475bf369fb07e7728ea6ec57226b02c40d1976b"}, + {file = "lmdb-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e568ae0887ae196340947d9800136e90feaed6b86a261ef01f01b2ba65fc8106"}, + {file = "lmdb-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6a816954d212f40fd15007cd81ab7a6bebb77436d949a6a9ae04af57fc127f3"}, + {file = "lmdb-1.3.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:fa6439356e591d3249ab0e1778a6f8d8408e993f66dc911914c78208f5310309"}, + {file = "lmdb-1.3.0-cp35-cp35m-win_amd64.whl", hash = "sha256:c6adbd6f7f9048e97f31a069e652eb51020a81e80a0ce92dbb9810d21da2409a"}, + {file = "lmdb-1.3.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:eefb392f6b5cd43aada49258c5a79be11cb2c8cd3fc3e2d9319a1e0b9f906458"}, + {file = "lmdb-1.3.0-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5a14aca2651c3af6f0d0a6b9168200eea0c8f2d27c40b01a442f33329a6e8dff"}, + {file = "lmdb-1.3.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cfa4aa9c67f8aee89b23005e98d1f3f32490b6b905fd1cb604b207cbd5755ab"}, + {file = "lmdb-1.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7da05d70fcc6561ac6b09e9fb1bf64b7ca294652c64c8a2889273970cee796b9"}, + {file = "lmdb-1.3.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:008243762decf8f6c90430a9bced56290ebbcdb5e877d90e42343bb97033e494"}, + {file = "lmdb-1.3.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:17215a42a4b9814c383deabecb160581e4fb75d00198eef0e3cea54f230ffbea"}, + {file = "lmdb-1.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65334eafa5d430b18d81ebd5362559a41483c362e1931f6e1b15bab2ecb7d75d"}, + {file = "lmdb-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:18c69fabdaf04efaf246587739cc1062b3e57c6ef0743f5c418df89e5e7e7b9b"}, + {file = "lmdb-1.3.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:41318717ab5d15ad2d6d263d34fbf614a045210f64b25e59ce734bb2105e421f"}, + {file = "lmdb-1.3.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:df2724bad7820114a205472994091097d0fa65a3e5fff5a8e688d123fb8c6326"}, + {file = "lmdb-1.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ddd590e1c7fcb395931aa3782fb89b9db4550ab2d81d006ecd239e0d462bc41"}, + {file = "lmdb-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:4172fba19417d7b29409beca7d73c067b54e5d8ab1fb9b51d7b4c1445d20a167"}, + {file = "lmdb-1.3.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2df38115dd9428a54d59ae7c712a4c7cce0d6b1d66056de4b1a8c38718066106"}, + {file = "lmdb-1.3.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d9103aa4908f0bca43c5911ca067d4e3d01f682dff0c0381a1239bd2bd757984"}, + {file = "lmdb-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:394df860c3f93cfd92b6f4caba785f38208cc9614c18b3803f83a2cc1695042f"}, + {file = "lmdb-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:62ab28e3593bdc318ea2f2fa1574e5fca3b6d1f264686d773ba54a637d4f563b"}, + {file = "lmdb-1.3.0-pp27-pypy_73-macosx_10_7_x86_64.whl", hash = "sha256:e6a704b3baced9182836c7f77b769f23856f3a8f62d0282b1bc1feaf81a86712"}, + {file = "lmdb-1.3.0-pp27-pypy_73-win_amd64.whl", hash = "sha256:08f4b5129f4683802569b02581142e415c8dcc0ff07605983ec1b07804cecbad"}, + {file = "lmdb-1.3.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:f291e3f561f58dddf63a92a5a6a4b8af3a0920b6705d35e2f80e52e86ee238a2"}, + {file = "lmdb-1.3.0.tar.gz", hash = "sha256:60a11efc21aaf009d06518996360eed346f6000bfc9de05114374230879f992e"}, +] +m2r2 = [ + {file = "m2r2-0.3.2-py3-none-any.whl", hash = "sha256:d3684086b61b4bebe2307f15189495360f05a123c9bda2a66462649b7ca236aa"}, + {file = "m2r2-0.3.2.tar.gz", hash = "sha256:ccd95b052dcd1ac7442ecb3111262b2001c10e4119b459c34c93ac7a5c2c7868"}, +] +markupsafe = [ + {file = "MarkupSafe-2.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3028252424c72b2602a323f70fbf50aa80a5d3aa616ea6add4ba21ae9cc9da4c"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:290b02bab3c9e216da57c1d11d2ba73a9f73a614bbdcc027d299a60cdfabb11a"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e104c0c2b4cd765b4e83909cde7ec61a1e313f8a75775897db321450e928cce"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24c3be29abb6b34052fd26fc7a8e0a49b1ee9d282e3665e8ad09a0a68faee5b3"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204730fd5fe2fe3b1e9ccadb2bd18ba8712b111dcabce185af0b3b5285a7c989"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d3b64c65328cb4cd252c94f83e66e3d7acf8891e60ebf588d7b493a55a1dbf26"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:96de1932237abe0a13ba68b63e94113678c379dca45afa040a17b6e1ad7ed076"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75bb36f134883fdbe13d8e63b8675f5f12b80bb6627f7714c7d6c5becf22719f"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-win32.whl", hash = "sha256:4056f752015dfa9828dce3140dbadd543b555afb3252507348c493def166d454"}, + {file = "MarkupSafe-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:d4e702eea4a2903441f2735799d217f4ac1b55f7d8ad96ab7d4e25417cb0827c"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f0eddfcabd6936558ec020130f932d479930581171368fd728efcfb6ef0dd357"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ddea4c352a488b5e1069069f2f501006b1a4362cb906bee9a193ef1245a7a61"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09c86c9643cceb1d87ca08cdc30160d1b7ab49a8a21564868921959bd16441b8"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0a0abef2ca47b33fb615b491ce31b055ef2430de52c5b3fb19a4042dbc5cadb"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:736895a020e31b428b3382a7887bfea96102c529530299f426bf2e636aacec9e"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:679cbb78914ab212c49c67ba2c7396dc599a8479de51b9a87b174700abd9ea49"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:84ad5e29bf8bab3ad70fd707d3c05524862bddc54dc040982b0dbcff36481de7"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-win32.whl", hash = "sha256:8da5924cb1f9064589767b0f3fc39d03e3d0fb5aa29e0cb21d43106519bd624a"}, + {file = "MarkupSafe-2.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:454ffc1cbb75227d15667c09f164a0099159da0c1f3d2636aa648f12675491ad"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:142119fb14a1ef6d758912b25c4e803c3ff66920635c44078666fe7cc3f8f759"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b2a5a856019d2833c56a3dcac1b80fe795c95f401818ea963594b345929dffa7"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d1fb9b2eec3c9714dd936860850300b51dbaa37404209c8d4cb66547884b7ed"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62c0285e91414f5c8f621a17b69fc0088394ccdaa961ef469e833dbff64bd5ea"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc3150f85e2dbcf99e65238c842d1cfe69d3e7649b19864c1cc043213d9cd730"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f02cf7221d5cd915d7fa58ab64f7ee6dd0f6cddbb48683debf5d04ae9b1c2cc1"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5653619b3eb5cbd35bfba3c12d575db2a74d15e0e1c08bf1db788069d410ce8"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7d2f5d97fcbd004c03df8d8fe2b973fe2b14e7bfeb2cfa012eaa8759ce9a762f"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-win32.whl", hash = "sha256:3cace1837bc84e63b3fd2dfce37f08f8c18aeb81ef5cf6bb9b51f625cb4e6cd8"}, + {file = "MarkupSafe-2.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:fabbe18087c3d33c5824cb145ffca52eccd053061df1d79d4b66dafa5ad2a5ea"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:023af8c54fe63530545f70dd2a2a7eed18d07a9a77b94e8bf1e2ff7f252db9a3"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d66624f04de4af8bbf1c7f21cc06649c1c69a7f84109179add573ce35e46d448"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c532d5ab79be0199fa2658e24a02fce8542df196e60665dd322409a03db6a52c"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ec74fada3841b8c5f4c4f197bea916025cb9aa3fe5abf7d52b655d042f956"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c653fde75a6e5eb814d2a0a89378f83d1d3f502ab710904ee585c38888816c"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:961eb86e5be7d0973789f30ebcf6caab60b844203f4396ece27310295a6082c7"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:598b65d74615c021423bd45c2bc5e9b59539c875a9bdb7e5f2a6b92dfcfc268d"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:599941da468f2cf22bf90a84f6e2a65524e87be2fce844f96f2dd9a6c9d1e635"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-win32.whl", hash = "sha256:e6f7f3f41faffaea6596da86ecc2389672fa949bd035251eab26dc6697451d05"}, + {file = "MarkupSafe-2.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:b8811d48078d1cf2a6863dafb896e68406c5f513048451cd2ded0473133473c7"}, + {file = "MarkupSafe-2.1.0.tar.gz", hash = "sha256:80beaf63ddfbc64a0452b841d8036ca0611e049650e20afcb882f5d3c266d65f"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +minos-microservice-common = [] +minos-microservice-networks = [] +mistune = [ + {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"}, + {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"}, +] +multidict = [ + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"}, + {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"}, + {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"}, + {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"}, + {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"}, + {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"}, + {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"}, + {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"}, + {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"}, + {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, + {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +nodeenv = [ + {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, + {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, +] +orjson = [ + {file = "orjson-3.6.7-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:93188a9d6eb566419ad48befa202dfe7cd7a161756444b99c4ec77faea9352a4"}, + {file = "orjson-3.6.7-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:82515226ecb77689a029061552b5df1802b75d861780c401e96ca6bc8495f775"}, + {file = "orjson-3.6.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3af57ffab7848aaec6ba6b9e9b41331250b57bf696f9d502bacdc71a0ebab0ba"}, + {file = "orjson-3.6.7-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:a7297504d1142e7efa236ffc53f056d73934a993a08646dbcee89fc4308a8fcf"}, + {file = "orjson-3.6.7-cp310-cp310-manylinux_2_24_x86_64.whl", hash = "sha256:5a50cde0dbbde255ce751fd1bca39d00ecd878ba0903c0480961b31984f2fab7"}, + {file = "orjson-3.6.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d21f9a2d1c30e58070f93988db4cad154b9009fafbde238b52c1c760e3607fbe"}, + {file = "orjson-3.6.7-cp310-none-win_amd64.whl", hash = "sha256:e152464c4606b49398afd911777decebcf9749cc8810c5b4199039e1afb0991e"}, + {file = "orjson-3.6.7-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:0a65f3c403f38b0117c6dd8e76e85a7bd51fcd92f06c5598dfeddbc44697d3e5"}, + {file = "orjson-3.6.7-cp37-cp37m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:6c47cfca18e41f7f37b08ff3e7abf5ada2d0f27b5ade934f05be5fc5bb956e9d"}, + {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:63185af814c243fad7a72441e5f98120c9ecddf2675befa486d669fb65539e9b"}, + {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2da6fde42182b80b40df2e6ab855c55090ebfa3fcc21c182b7ad1762b61d55c"}, + {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:48c5831ec388b4e2682d4ff56d6bfa4a2ef76c963f5e75f4ff4785f9cf338a80"}, + {file = "orjson-3.6.7-cp37-cp37m-manylinux_2_24_x86_64.whl", hash = "sha256:913fac5d594ccabf5e8fbac15b9b3bb9c576d537d49eeec9f664e7a64dde4c4b"}, + {file = "orjson-3.6.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:58f244775f20476e5851e7546df109f75160a5178d44257d437ba6d7e562bfe8"}, + {file = "orjson-3.6.7-cp37-none-win_amd64.whl", hash = "sha256:2d5f45c6b85e5f14646df2d32ecd7ff20fcccc71c0ea1155f4d3df8c5299bbb7"}, + {file = "orjson-3.6.7-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:612d242493afeeb2068bc72ff2544aa3b1e627578fcf92edee9daebb5893ffea"}, + {file = "orjson-3.6.7-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:539cdc5067db38db27985e257772d073cd2eb9462d0a41bde96da4e4e60bd99b"}, + {file = "orjson-3.6.7-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6d103b721bbc4f5703f62b3882e638c0b65fcdd48622531c7ffd45047ef8e87c"}, + {file = "orjson-3.6.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb10a20f80e95102dd35dfbc3a22531661b44a09b55236b012a446955846b023"}, + {file = "orjson-3.6.7-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:bb68d0da349cf8a68971a48ad179434f75256159fe8b0715275d9b49fa23b7a3"}, + {file = "orjson-3.6.7-cp38-cp38-manylinux_2_24_x86_64.whl", hash = "sha256:4a2c7d0a236aaeab7f69c17b7ab4c078874e817da1bfbb9827cb8c73058b3050"}, + {file = "orjson-3.6.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3be045ca3b96119f592904cf34b962969ce97bd7843cbfca084009f6c8d2f268"}, + {file = "orjson-3.6.7-cp38-none-win_amd64.whl", hash = "sha256:bd765c06c359d8a814b90f948538f957fa8a1f55ad1aaffcdc5771996aaea061"}, + {file = "orjson-3.6.7-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:7dd9e1e46c0776eee9e0649e3ae9584ea368d96851bcaeba18e217fa5d755283"}, + {file = "orjson-3.6.7-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:c4b4f20a1e3df7e7c83717aff0ef4ab69e42ce2fb1f5234682f618153c458406"}, + {file = "orjson-3.6.7-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7107a5673fd0b05adbb58bf71c1578fc84d662d29c096eb6d998982c8635c221"}, + {file = "orjson-3.6.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a08b6940dd9a98ccf09785890112a0f81eadb4f35b51b9a80736d1725437e22c"}, + {file = "orjson-3.6.7-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:f5d1648e5a9d1070f3628a69a7c6c17634dbb0caf22f2085eca6910f7427bf1f"}, + {file = "orjson-3.6.7-cp39-cp39-manylinux_2_24_x86_64.whl", hash = "sha256:e6201494e8dff2ce7fd21da4e3f6dfca1a3fed38f9dcefc972f552f6596a7621"}, + {file = "orjson-3.6.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:70d0386abe02879ebaead2f9632dd2acb71000b4721fd8c1a2fb8c031a38d4d5"}, + {file = "orjson-3.6.7-cp39-none-win_amd64.whl", hash = "sha256:d9a3288861bfd26f3511fb4081561ca768674612bac59513cb9081bb61fcc87f"}, + {file = "orjson-3.6.7.tar.gz", hash = "sha256:a4bb62b11289b7620eead2f25695212e9ac77fcfba76f050fa8a540fb5c32401"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +pbr = [ + {file = "pbr-5.8.1-py2.py3-none-any.whl", hash = "sha256:27108648368782d07bbf1cb468ad2e2eeef29086affd14087a6d04b7de8af4ec"}, + {file = "pbr-5.8.1.tar.gz", hash = "sha256:66bc5a34912f408bb3925bf21231cb6f59206267b7f63f3503ef865c1a292e25"}, +] +platformdirs = [ + {file = "platformdirs-2.5.1-py3-none-any.whl", hash = "sha256:bcae7cab893c2d310a711b70b24efb93334febe65f8de776ee320b517471e227"}, + {file = "platformdirs-2.5.1.tar.gz", hash = "sha256:7535e70dfa32e84d4b34996ea99c5e432fa29a708d0f4e394bbcb2a8faa4f16d"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +pre-commit = [ + {file = "pre_commit-2.17.0-py2.py3-none-any.whl", hash = "sha256:725fa7459782d7bec5ead072810e47351de01709be838c2ce1726b9591dad616"}, + {file = "pre_commit-2.17.0.tar.gz", hash = "sha256:c1a8040ff15ad3d648c70cc3e55b93e4d2d5b687320955505587fd79bbaed06a"}, +] +psycopg2-binary = [ + {file = "psycopg2-binary-2.9.3.tar.gz", hash = "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e82d38390a03da28c7985b394ec3f56873174e2c88130e6966cb1c946508e65"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57804fc02ca3ce0dbfbef35c4b3a4a774da66d66ea20f4bda601294ad2ea6092"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:083a55275f09a62b8ca4902dd11f4b33075b743cf0d360419e2051a8a5d5ff76"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:0a29729145aaaf1ad8bafe663131890e2111f13416b60e460dae0a96af5905c9"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a79d622f5206d695d7824cbf609a4f5b88ea6d6dab5f7c147fc6d333a8787e4"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:090f3348c0ab2cceb6dfbe6bf721ef61262ddf518cd6cc6ecc7d334996d64efa"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a9e1f75f96ea388fbcef36c70640c4efbe4650658f3d6a2967b4cc70e907352e"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c3ae8e75eb7160851e59adc77b3a19a976e50622e44fd4fd47b8b18208189d42"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-win32.whl", hash = "sha256:7b1e9b80afca7b7a386ef087db614faebbf8839b7f4db5eb107d0f1a53225029"}, + {file = "psycopg2_binary-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:8b344adbb9a862de0c635f4f0425b7958bf5a4b927c8594e6e8d261775796d53"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:e847774f8ffd5b398a75bc1c18fbb56564cda3d629fe68fd81971fece2d3c67e"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68641a34023d306be959101b345732360fc2ea4938982309b786f7be1b43a4a1"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3303f8807f342641851578ee7ed1f3efc9802d00a6f83c101d21c608cb864460"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:e3699852e22aa68c10de06524a3721ade969abf382da95884e6a10ff798f9281"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:526ea0378246d9b080148f2d6681229f4b5964543c170dd10bf4faaab6e0d27f"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b1c8068513f5b158cf7e29c43a77eb34b407db29aca749d3eb9293ee0d3103ca"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:15803fa813ea05bef089fa78835118b5434204f3a17cb9f1e5dbfd0b9deea5af"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:152f09f57417b831418304c7f30d727dc83a12761627bb826951692cc6491e57"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:404224e5fef3b193f892abdbf8961ce20e0b6642886cfe1fe1923f41aaa75c9d"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:1f6b813106a3abdf7b03640d36e24669234120c72e91d5cbaeb87c5f7c36c65b"}, + {file = "psycopg2_binary-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:2d872e3c9d5d075a2e104540965a1cf898b52274a5923936e5bfddb58c59c7c2"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:10bb90fb4d523a2aa67773d4ff2b833ec00857f5912bafcfd5f5414e45280fb1"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a52ecab70af13e899f7847b3e074eeb16ebac5615665db33bce8a1009cf33"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a29b3ca4ec9defec6d42bf5feb36bb5817ba3c0230dd83b4edf4bf02684cd0ae"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:12b11322ea00ad8db8c46f18b7dfc47ae215e4df55b46c67a94b4effbaec7094"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:53293533fcbb94c202b7c800a12c873cfe24599656b341f56e71dd2b557be063"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c381bda330ddf2fccbafab789d83ebc6c53db126e4383e73794c74eedce855ef"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d29409b625a143649d03d0fd7b57e4b92e0ecad9726ba682244b73be91d2fdb"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:183a517a3a63503f70f808b58bfbf962f23d73b6dccddae5aa56152ef2bcb232"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:15c4e4cfa45f5a60599d9cec5f46cd7b1b29d86a6390ec23e8eebaae84e64554"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:adf20d9a67e0b6393eac162eb81fb10bc9130a80540f4df7e7355c2dd4af9fba"}, + {file = "psycopg2_binary-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2f9ffd643bc7349eeb664eba8864d9e01f057880f510e4681ba40a6532f93c71"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:def68d7c21984b0f8218e8a15d514f714d96904265164f75f8d3a70f9c295667"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dffc08ca91c9ac09008870c9eb77b00a46b3378719584059c034b8945e26b272"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:280b0bb5cbfe8039205c7981cceb006156a675362a00fe29b16fbc264e242834"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:af9813db73395fb1fc211bac696faea4ca9ef53f32dc0cfa27e4e7cf766dcf24"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:63638d875be8c2784cfc952c9ac34e2b50e43f9f0a0660b65e2a87d656b3116c"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ffb7a888a047696e7f8240d649b43fb3644f14f0ee229077e7f6b9f9081635bd"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0c9d5450c566c80c396b7402895c4369a410cab5a82707b11aee1e624da7d004"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:d1c1b569ecafe3a69380a94e6ae09a4789bbb23666f3d3a08d06bbd2451f5ef1"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8fc53f9af09426a61db9ba357865c77f26076d48669f2e1bb24d85a22fb52307"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-win32.whl", hash = "sha256:6472a178e291b59e7f16ab49ec8b4f3bdada0a879c68d3817ff0963e722a82ce"}, + {file = "psycopg2_binary-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:35168209c9d51b145e459e05c31a9eaeffa9a6b0fd61689b48e07464ffd1a83e"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:47133f3f872faf28c1e87d4357220e809dfd3fa7c64295a4a148bcd1e6e34ec9"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91920527dea30175cc02a1099f331aa8c1ba39bf8b7762b7b56cbf54bc5cce42"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887dd9aac71765ac0d0bac1d0d4b4f2c99d5f5c1382d8b770404f0f3d0ce8a39"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:1f14c8b0942714eb3c74e1e71700cbbcb415acbc311c730370e70c578a44a25c"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:7af0dd86ddb2f8af5da57a976d27cd2cd15510518d582b478fbb2292428710b4"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93cd1967a18aa0edd4b95b1dfd554cf15af657cb606280996d393dadc88c3c35"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bda845b664bb6c91446ca9609fc69f7db6c334ec5e4adc87571c34e4f47b7ddb"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:01310cf4cf26db9aea5158c217caa92d291f0500051a6469ac52166e1a16f5b7"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99485cab9ba0fa9b84f1f9e1fef106f44a46ef6afdeec8885e0b88d0772b49e8"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-win32.whl", hash = "sha256:46f0e0a6b5fa5851bbd9ab1bc805eef362d3a230fbdfbc209f4a236d0a7a990d"}, + {file = "psycopg2_binary-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:accfe7e982411da3178ec690baaceaad3c278652998b2c45828aaac66cd8285f"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pycodestyle = [ + {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, + {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, +] +pyflakes = [ + {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, + {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, +] +pygments = [ + {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, + {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, +] +pyparsing = [ + {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, + {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, +] +pytest = [ + {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, + {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, +] +pytz = [ + {file = "pytz-2021.3-py2.py3-none-any.whl", hash = "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c"}, + {file = "pytz-2021.3.tar.gz", hash = "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326"}, +] +pyyaml = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +requests = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +snowballstemmer = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] +sphinx = [ + {file = "Sphinx-4.4.0-py3-none-any.whl", hash = "sha256:5da895959511473857b6d0200f56865ed62c31e8f82dd338063b84ec022701fe"}, + {file = "Sphinx-4.4.0.tar.gz", hash = "sha256:6caad9786055cb1fa22b4a365c1775816b876f91966481765d7d50e9f0dd35cc"}, +] +sphinx-autodoc-typehints = [ + {file = "sphinx_autodoc_typehints-1.17.0-py3-none-any.whl", hash = "sha256:081daf53077b4ae1c28347d6d858e13e63aefe3b4aacef79fd717dd60687b470"}, + {file = "sphinx_autodoc_typehints-1.17.0.tar.gz", hash = "sha256:51c7b3f5cb9ccd15d0b52088c62df3094f1abd9612930340365c26def8629a14"}, +] +sphinx-rtd-theme = [ + {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, + {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, +] +sphinxcontrib-apidoc = [ + {file = "sphinxcontrib-apidoc-0.3.0.tar.gz", hash = "sha256:729bf592cf7b7dd57c4c05794f732dc026127275d785c2a5494521fdde773fb9"}, + {file = "sphinxcontrib_apidoc-0.3.0-py2.py3-none-any.whl", hash = "sha256:6671a46b2c6c5b0dca3d8a147849d159065e50443df79614f921b42fbd15cb09"}, +] +sphinxcontrib-applehelp = [ + {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, + {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, +] +sphinxcontrib-devhelp = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] +sphinxcontrib-htmlhelp = [ + {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, + {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, +] +sphinxcontrib-jsmath = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] +sphinxcontrib-qthelp = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] +sphinxcontrib-serializinghtml = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +typing-extensions = [ + {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, + {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, +] +urllib3 = [ + {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, + {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, +] +virtualenv = [ + {file = "virtualenv-20.13.2-py2.py3-none-any.whl", hash = "sha256:e7b34c9474e6476ee208c43a4d9ac1510b041c68347eabfe9a9ea0c86aa0a46b"}, + {file = "virtualenv-20.13.2.tar.gz", hash = "sha256:01f5f80744d24a3743ce61858123488e91cb2dd1d3bdf92adaf1bba39ffdedf0"}, +] +yarl = [ + {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"}, + {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da6df107b9ccfe52d3a48165e48d72db0eca3e3029b5b8cb4fe6ee3cb870ba8b"}, + {file = "yarl-1.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1d0894f238763717bdcfea74558c94e3bc34aeacd3351d769460c1a586a8b05"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4b95b7e00c6635a72e2d00b478e8a28bfb122dc76349a06e20792eb53a523"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c145ab54702334c42237a6c6c4cc08703b6aa9b94e2f227ceb3d477d20c36c63"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca56f002eaf7998b5fcf73b2421790da9d2586331805f38acd9997743114e98"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1d3d5ad8ea96bd6d643d80c7b8d5977b4e2fb1bab6c9da7322616fd26203d125"}, + {file = "yarl-1.7.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:167ab7f64e409e9bdd99333fe8c67b5574a1f0495dcfd905bc7454e766729b9e"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:95a1873b6c0dd1c437fb3bb4a4aaa699a48c218ac7ca1e74b0bee0ab16c7d60d"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6152224d0a1eb254f97df3997d79dadd8bb2c1a02ef283dbb34b97d4f8492d23"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5bb7d54b8f61ba6eee541fba4b83d22b8a046b4ef4d8eb7f15a7e35db2e1e245"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:9c1f083e7e71b2dd01f7cd7434a5f88c15213194df38bc29b388ccdf1492b739"}, + {file = "yarl-1.7.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f44477ae29025d8ea87ec308539f95963ffdc31a82f42ca9deecf2d505242e72"}, + {file = "yarl-1.7.2-cp310-cp310-win32.whl", hash = "sha256:cff3ba513db55cc6a35076f32c4cdc27032bd075c9faef31fec749e64b45d26c"}, + {file = "yarl-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:c9c6d927e098c2d360695f2e9d38870b2e92e0919be07dbe339aefa32a090265"}, + {file = "yarl-1.7.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9b4c77d92d56a4c5027572752aa35082e40c561eec776048330d2907aead891d"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c01a89a44bb672c38f42b49cdb0ad667b116d731b3f4c896f72302ff77d71656"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c19324a1c5399b602f3b6e7db9478e5b1adf5cf58901996fc973fe4fccd73eed"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3abddf0b8e41445426d29f955b24aeecc83fa1072be1be4e0d194134a7d9baee"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6a1a9fe17621af43e9b9fcea8bd088ba682c8192d744b386ee3c47b56eaabb2c"}, + {file = "yarl-1.7.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8b0915ee85150963a9504c10de4e4729ae700af11df0dc5550e6587ed7891e92"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:29e0656d5497733dcddc21797da5a2ab990c0cb9719f1f969e58a4abac66234d"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:bf19725fec28452474d9887a128e98dd67eee7b7d52e932e6949c532d820dc3b"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:d6f3d62e16c10e88d2168ba2d065aa374e3c538998ed04996cd373ff2036d64c"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ac10bbac36cd89eac19f4e51c032ba6b412b3892b685076f4acd2de18ca990aa"}, + {file = "yarl-1.7.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aa32aaa97d8b2ed4e54dc65d241a0da1c627454950f7d7b1f95b13985afd6c5d"}, + {file = "yarl-1.7.2-cp36-cp36m-win32.whl", hash = "sha256:87f6e082bce21464857ba58b569370e7b547d239ca22248be68ea5d6b51464a1"}, + {file = "yarl-1.7.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ac35ccde589ab6a1870a484ed136d49a26bcd06b6a1c6397b1967ca13ceb3913"}, + {file = "yarl-1.7.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a467a431a0817a292121c13cbe637348b546e6ef47ca14a790aa2fa8cc93df63"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ab0c3274d0a846840bf6c27d2c60ba771a12e4d7586bf550eefc2df0b56b3b4"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d260d4dc495c05d6600264a197d9d6f7fc9347f21d2594926202fd08cf89a8ba"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4dd8b01a8112809e6b636b00f487846956402834a7fd59d46d4f4267181c41"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c1164a2eac148d85bbdd23e07dfcc930f2e633220f3eb3c3e2a25f6148c2819e"}, + {file = "yarl-1.7.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:67e94028817defe5e705079b10a8438b8cb56e7115fa01640e9c0bb3edf67332"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:89ccbf58e6a0ab89d487c92a490cb5660d06c3a47ca08872859672f9c511fc52"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8cce6f9fa3df25f55521fbb5c7e4a736683148bcc0c75b21863789e5185f9185"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:211fcd65c58bf250fb994b53bc45a442ddc9f441f6fec53e65de8cba48ded986"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c10ea1e80a697cf7d80d1ed414b5cb8f1eec07d618f54637067ae3c0334133c4"}, + {file = "yarl-1.7.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:52690eb521d690ab041c3919666bea13ab9fbff80d615ec16fa81a297131276b"}, + {file = "yarl-1.7.2-cp37-cp37m-win32.whl", hash = "sha256:695ba021a9e04418507fa930d5f0704edbce47076bdcfeeaba1c83683e5649d1"}, + {file = "yarl-1.7.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c17965ff3706beedafd458c452bf15bac693ecd146a60a06a214614dc097a271"}, + {file = "yarl-1.7.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fce78593346c014d0d986b7ebc80d782b7f5e19843ca798ed62f8e3ba8728576"}, + {file = "yarl-1.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c2a1ac41a6aa980db03d098a5531f13985edcb451bcd9d00670b03129922cd0d"}, + {file = "yarl-1.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:39d5493c5ecd75c8093fa7700a2fb5c94fe28c839c8e40144b7ab7ccba6938c8"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eb6480ef366d75b54c68164094a6a560c247370a68c02dddb11f20c4c6d3c9d"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ba63585a89c9885f18331a55d25fe81dc2d82b71311ff8bd378fc8004202ff6"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e39378894ee6ae9f555ae2de332d513a5763276a9265f8e7cbaeb1b1ee74623a"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c0910c6b6c31359d2f6184828888c983d54d09d581a4a23547a35f1d0b9484b1"}, + {file = "yarl-1.7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6feca8b6bfb9eef6ee057628e71e1734caf520a907b6ec0d62839e8293e945c0"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8300401dc88cad23f5b4e4c1226f44a5aa696436a4026e456fe0e5d2f7f486e6"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:788713c2896f426a4e166b11f4ec538b5736294ebf7d5f654ae445fd44270832"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:fd547ec596d90c8676e369dd8a581a21227fe9b4ad37d0dc7feb4ccf544c2d59"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:737e401cd0c493f7e3dd4db72aca11cfe069531c9761b8ea474926936b3c57c8"}, + {file = "yarl-1.7.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baf81561f2972fb895e7844882898bda1eef4b07b5b385bcd308d2098f1a767b"}, + {file = "yarl-1.7.2-cp38-cp38-win32.whl", hash = "sha256:ede3b46cdb719c794427dcce9d8beb4abe8b9aa1e97526cc20de9bd6583ad1ef"}, + {file = "yarl-1.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:cc8b7a7254c0fc3187d43d6cb54b5032d2365efd1df0cd1749c0c4df5f0ad45f"}, + {file = "yarl-1.7.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:580c1f15500e137a8c37053e4cbf6058944d4c114701fa59944607505c2fe3a0"}, + {file = "yarl-1.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ec1d9a0d7780416e657f1e405ba35ec1ba453a4f1511eb8b9fbab81cb8b3ce1"}, + {file = "yarl-1.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3bf8cfe8856708ede6a73907bf0501f2dc4e104085e070a41f5d88e7faf237f3"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1be4bbb3d27a4e9aa5f3df2ab61e3701ce8fcbd3e9846dbce7c033a7e8136746"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:534b047277a9a19d858cde163aba93f3e1677d5acd92f7d10ace419d478540de"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6ddcd80d79c96eb19c354d9dca95291589c5954099836b7c8d29278a7ec0bda"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9bfcd43c65fbb339dc7086b5315750efa42a34eefad0256ba114cd8ad3896f4b"}, + {file = "yarl-1.7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f64394bd7ceef1237cc604b5a89bf748c95982a84bcd3c4bbeb40f685c810794"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044daf3012e43d4b3538562da94a88fb12a6490652dbc29fb19adfa02cf72eac"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:368bcf400247318382cc150aaa632582d0780b28ee6053cd80268c7e72796dec"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:bab827163113177aee910adb1f48ff7af31ee0289f434f7e22d10baf624a6dfe"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0cba38120db72123db7c58322fa69e3c0efa933040ffb586c3a87c063ec7cae8"}, + {file = "yarl-1.7.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:59218fef177296451b23214c91ea3aba7858b4ae3306dde120224cfe0f7a6ee8"}, + {file = "yarl-1.7.2-cp39-cp39-win32.whl", hash = "sha256:1edc172dcca3f11b38a9d5c7505c83c1913c0addc99cd28e993efeaafdfaa18d"}, + {file = "yarl-1.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:797c2c412b04403d2da075fb93c123df35239cd7b4cc4e0cd9e5839b73f52c58"}, + {file = "yarl-1.7.2.tar.gz", hash = "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd"}, +] +zipp = [ + {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, + {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, +] diff --git a/packages/plugins/minos-graphql-aiohttp/poetry.toml b/packages/plugins/minos-graphql-aiohttp/poetry.toml new file mode 100644 index 000000000..ab1033bd3 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/poetry.toml @@ -0,0 +1,2 @@ +[virtualenvs] +in-project = true diff --git a/packages/plugins/minos-graphql-aiohttp/pyproject.toml b/packages/plugins/minos-graphql-aiohttp/pyproject.toml new file mode 100644 index 000000000..338ba801e --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/pyproject.toml @@ -0,0 +1,56 @@ +[tool.poetry] +name = "minos-graphql-aiohttp" +version = "0.6.0" +description = "The rest plugin of the Minos Framework." +readme = "README.md" +repository = "https://github.com/minos-framework/minos-python" +homepage = "http://www.minos.run/" +authors = ["Minos Framework Devs "] +license = "MIT" +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Natural Language :: English", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", +] +keywords = [ + "clariteia", + "minos", + "microservice", + "saga", +] +packages = [ + { include = "minos" } +] +include = [ + "AUTHORS.md", + "HISTORY.md", + "LICENSE", +] + +[tool.poetry.dependencies] +python = "^3.9" +minos-microservice-common = "^0.5.0" +minos-microservice-networks = "^0.5.0" +aiohttp = "^3.8.1" +graphql-server = {version = "^3.0.0-beta.5", extras = ["aiohttp"]} + +[tool.poetry.dev-dependencies] +minos-microservice-common = { path = "../../core/minos-microservice-common", develop = true } +minos-microservice-networks = { path = "../../core/minos-microservice-networks", develop = true } +black = "^22.1" +isort = "^5.8.0" +pytest = "^6.2.4" +coverage = "^6.3" +flake8 = "^4.0.1" +Sphinx = "^4.0.1" +pre-commit = "^2.12.1" +sphinx-autodoc-typehints = "^1.12.0" +sphinxcontrib-apidoc = "^0.3.0" +sphinx-rtd-theme = "^1.0.0" +m2r2 = "^0.3.2" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/packages/plugins/minos-graphql-aiohttp/setup.cfg b/packages/plugins/minos-graphql-aiohttp/setup.cfg new file mode 100644 index 000000000..dbb9ac849 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/setup.cfg @@ -0,0 +1,28 @@ +[coverage:run] +source = + minos + +[coverage:report] +exclude_lines = + pragma: no cover + raise NotImplementedError + if TYPE_CHECKING: + pass +precision = 2 + +[flake8] +filename = + ./minos/**/*.py, + ./tests/**/*.py, + ./examples/**/*.py +max-line-length = 120 +per-file-ignores = + ./**/__init__.py:F401,W391 + +[isort] +known_first_party=minos +multi_line_output = 3 +include_trailing_comma = True +force_grid_wrap = 1 +use_parentheses = True +line_length = 120 diff --git a/packages/plugins/minos-graphql-aiohttp/tests/__init__.py b/packages/plugins/minos-graphql-aiohttp/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/packages/plugins/minos-graphql-aiohttp/tests/services/commands.py b/packages/plugins/minos-graphql-aiohttp/tests/services/commands.py new file mode 100644 index 000000000..7f9713073 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/tests/services/commands.py @@ -0,0 +1,36 @@ +from minos.networks import ( + BrokerResponse, + Request, + Response, + enroute, +) + + +class CommandService: + @enroute.rest.command(url="/order", method="GET") + def get_order_rest(self, request: Request) -> Response: + return Response("get_order") + + @enroute.broker.command("GetOrder") + def get_order_command(self, request: Request) -> Response: + return BrokerResponse("get_order") + + @enroute.broker.command("AddOrder") + def add_order(self, request: Request) -> Response: + return BrokerResponse("add_order") + + @enroute.broker.command("DeleteOrder") + def delete_order(self, request: Request) -> Response: + return BrokerResponse("delete_order") + + @enroute.broker.command("UpdateOrder") + def update_order(self, request: Request) -> Response: + return BrokerResponse("update_order") + + @enroute.broker.event("TicketAdded") + def ticket_added(self, request: Request) -> str: + return "command_service_ticket_added" + + @enroute.periodic.event("@daily") + def recompute_something(self, request: Request) -> None: + """For testing purposes.""" diff --git a/packages/plugins/minos-graphql-aiohttp/tests/services/queries.py b/packages/plugins/minos-graphql-aiohttp/tests/services/queries.py new file mode 100644 index 000000000..726f70b12 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/tests/services/queries.py @@ -0,0 +1,19 @@ +from minos.networks import ( + Request, + Response, + enroute, +) + + +class QueryService: + @enroute.rest.query(url="/ticket", method="POST") + def add_ticket(self, request: Request) -> Response: + return Response("ticket_added") + + @enroute.broker.event("TicketAdded") + def ticket_added(self, request: Request): + return "query_service_ticket_added" + + @enroute.broker.event("TicketDeleted") + def ticket_deleted(self, request: Request): + return "ticket_deleted" diff --git a/packages/plugins/minos-graphql-aiohttp/tests/test_config.yml b/packages/plugins/minos-graphql-aiohttp/tests/test_config.yml new file mode 100644 index 000000000..79786973a --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/tests/test_config.yml @@ -0,0 +1,39 @@ +service: + name: Order + aggregate: tests.utils.Order +services: + - tests.services.commands.CommandService + - tests.services.queries.QueryService +rest: + host: localhost + port: 8080 +repository: + database: order_db + user: minos + password: min0s + host: localhost + port: 5432 +snapshot: + database: order_db + user: minos + password: min0s + host: localhost + port: 5432 +broker: + host: localhost + port: 9092 + queue: + database: order_db + user: minos + password: min0s + host: localhost + port: 5432 + records: 10 + retry: 2 +saga: + storage: + path: "./order.lmdb" +discovery: + client: minos.networks.InMemoryDiscoveryClient + host: discovery-service + port: 8080 diff --git a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_handlers.py b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_handlers.py new file mode 100644 index 000000000..78a174cf9 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_handlers.py @@ -0,0 +1,131 @@ +import unittest +from unittest.mock import ( + AsyncMock, +) +from uuid import ( + uuid4, +) + +from aiohttp import ( + web, +) +from aiohttp.web_exceptions import ( + HTTPInternalServerError, +) +from orjson import ( + orjson, +) + +from minos.common.testing import ( + PostgresAsyncTestCase, +) +from minos.networks import ( + REQUEST_USER_CONTEXT_VAR, + Request, + Response, +) +from minos.plugins.rest_aiohttp import ( + RestHandler, + RestResponse, + RestResponseException, +) +from tests.test_plugins.test_rest_aiohttp.utils import ( + json_mocked_request, + mocked_request, +) +from tests.utils import ( + BASE_PATH, +) + +APPLICATION_JSON = "application/json" + + +class _Cls: + @staticmethod + async def _fn(request: Request) -> Response: + return RestResponse(await request.content()) + + @staticmethod + async def _fn_status(request: Request) -> Response: + return RestResponse(status=await request.content()) + + @staticmethod + async def _fn_none(request: Request): + return + + @staticmethod + async def _fn_raises_response(request: Request) -> Response: + raise RestResponseException("") + + @staticmethod + async def _fn_raises_exception(request: Request) -> Response: + raise ValueError + + +class TestRestHandler(PostgresAsyncTestCase): + CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" + + def setUp(self) -> None: + super().setUp() + self.handler = RestHandler.from_config(config=self.config) + + def test_from_config(self): + self.assertIsInstance(self.handler, RestHandler) + self.assertEqual({("/order", "GET"), ("/ticket", "POST")}, set(self.handler.endpoints.keys())) + + def test_from_config_raises(self): + with self.assertRaises(Exception): + RestHandler.from_config() + + def test_get_app(self): + self.assertIsInstance(self.handler.get_app(), web.Application) + + async def test_get_callback(self): + handler = self.handler.get_callback(_Cls._fn) + response = await handler(json_mocked_request({"foo": "bar"})) + self.assertIsInstance(response, web.Response) + self.assertEqual(orjson.dumps({"foo": "bar"}), response.body) + self.assertEqual(APPLICATION_JSON, response.content_type) + + async def test_get_callback_status(self): + handler = self.handler.get_callback(_Cls._fn_status) + response = await handler(json_mocked_request(203)) + self.assertIsInstance(response, web.Response) + self.assertEqual(None, response.body) + self.assertEqual(APPLICATION_JSON, response.content_type) + self.assertEqual(203, response.status) + + async def test_get_callback_none(self): + handler = self.handler.get_callback(_Cls._fn_none) + response = await handler(mocked_request()) + self.assertIsInstance(response, web.Response) + self.assertEqual(None, response.text) + self.assertEqual(APPLICATION_JSON, response.content_type) + + async def test_get_callback_raises_response(self): + handler = self.handler.get_callback(_Cls._fn_raises_response) + response = await handler(json_mocked_request({"foo": "bar"})) + self.assertEqual(400, response.status) + + async def test_get_callback_raises_exception(self): + handler = self.handler.get_callback(_Cls._fn_raises_exception) + with self.assertRaises(HTTPInternalServerError): + await handler(json_mocked_request({"foo": "bar"})) + + async def test_get_callback_with_user(self): + user = uuid4() + + async def _fn(request) -> None: + self.assertEqual(user, request.user) + self.assertEqual(user, REQUEST_USER_CONTEXT_VAR.get()) + + mock = AsyncMock(side_effect=_fn) + + handler = self.handler.get_callback(mock) + await handler(json_mocked_request({"foo": "bar"}, user=user)) + + self.assertEqual(1, mock.call_count) + + +if __name__ == "__main__": + unittest.main() diff --git a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_requests.py b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_requests.py new file mode 100644 index 000000000..0c824073f --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_requests.py @@ -0,0 +1,441 @@ +import unittest +import warnings +from uuid import ( + uuid4, +) + +from orjson import ( + orjson, +) + +from minos.common import ( + MinosAvroProtocol, + ModelType, + classname, +) +from minos.networks import ( + NotHasContentException, + NotHasParamsException, + Response, +) +from minos.plugins.rest_aiohttp import ( + RestRequest, + RestResponse, +) +from tests.test_plugins.test_rest_aiohttp.utils import ( + avro_mocked_request, + bytes_mocked_request, + form_mocked_request, + json_mocked_request, + mocked_request, + text_mocked_request, +) +from tests.utils import ( + FakeModel, +) + +APPLICATION_JSON = "application/json" +APPLICATION_URLENCODED = "application/x-www-form-urlencoded" +APPLICATION_OCTET_STREAM = "application/octet-stream" +TEXT_PLAIN = "text/plain" +AVRO_BINARY = "avro/binary" +IMAGE_PNG = "image/png" + + +class TestRestRequest(unittest.IsolatedAsyncioTestCase): + def test_raw(self): + raw = mocked_request() + request = RestRequest(raw) + self.assertEqual(raw, request.raw) + + def test_raw_request(self): + raw = mocked_request() + request = RestRequest(raw) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + # noinspection PyDeprecation + self.assertEqual(request.raw, request.raw_request) + + def test_repr(self): + raw = mocked_request() + request = RestRequest(raw) + self.assertEqual(f"RestRequest({raw!r})", repr(request)) + + def test_eq_true(self): + request = mocked_request() + self.assertEqual(RestRequest(request), RestRequest(request)) + + def test_eq_false(self): + self.assertNotEqual(RestRequest(mocked_request()), RestRequest(mocked_request())) + + def test_headers(self): + raw = mocked_request(headers={"something": "123"}) + request = RestRequest(raw) + self.assertEqual({"something": "123"}, request.headers) + + def test_user(self): + uuid = uuid4() + raw = mocked_request(user=uuid) + request = RestRequest(raw) + user = request.user + self.assertEqual(uuid, user) + + def test_user_unset(self): + raw = mocked_request() + request = RestRequest(raw) + self.assertEqual(None, request.user) + + +class TestRestRequestContent(unittest.IsolatedAsyncioTestCase): + def test_has_content_false(self): + raw = mocked_request() + request = RestRequest(raw) + self.assertEqual(False, request.has_content) + + def test_has_content_true(self): + raw = json_mocked_request(123) + request = RestRequest(raw) + self.assertEqual(True, request.has_content) + + async def test_empty_raises(self): + raw = mocked_request() + request = RestRequest(raw) + with self.assertRaises(NotHasContentException): + await request.content() + + async def test_json_int(self): + expected = 56 + + raw = json_mocked_request(56) + request = RestRequest(raw) + observed = await request.content() + + self.assertEqual(expected, observed) + + async def test_json_list(self): + expected = [{"doors": 3, "color": "blue", "owner": None}, {"doors": 5, "color": "red", "owner": None}] + + raw = json_mocked_request( + [{"doors": 3, "color": "blue", "owner": None}, {"doors": 5, "color": "red", "owner": None}] + ) + request = RestRequest(raw) + observed = await request.content() + + self.assertEqual(expected, observed) + + async def test_json_dict(self): + expected = {"doors": 3, "color": "blue", "owner": None} + + raw = json_mocked_request({"doors": 3, "color": "blue", "owner": None}) + request = RestRequest(raw) + observed = await request.content() + + self.assertEqual(expected, observed) + + async def test_form(self): + expected = {"foo": "foo1", "bar": ["bar1", "bar2"]} + + raw = form_mocked_request({"foo": "foo1", "bar": ["bar1", "bar2"]}) + request = RestRequest(raw) + observed = await request.content() + + self.assertEqual(expected, observed) + + async def test_avro_int(self): + expected = 56 + + raw = avro_mocked_request(56, "int") + request = RestRequest(raw) + observed = await request.content() + + self.assertEqual(expected, observed) + + async def test_avro_uuid(self): + expected = uuid4() + + raw = avro_mocked_request(expected, {"type": "string", "logicalType": "uuid"}) + request = RestRequest(raw) + observed = await request.content() + + self.assertEqual(expected, observed) + + async def test_avro_dto(self): + expected = ModelType.build("FakeModel", {"text": str})("foobar") + + raw = avro_mocked_request( + {"text": "foobar"}, {"name": "FakeModel", "type": "record", "fields": [{"name": "text", "type": "string"}]} + ) + request = RestRequest(raw) + observed = await request.content() + + self.assertEqual(expected, observed) + + async def test_avro_model(self): + expected = FakeModel("foobar") + + raw = avro_mocked_request(expected.avro_data, expected.avro_schema) + request = RestRequest(raw) + observed = await request.content() + + self.assertEqual(expected, observed) + + async def test_text(self): + expected = "foobar" + + raw = text_mocked_request("foobar") + request = RestRequest(raw) + observed = await request.content() + + self.assertEqual(expected, observed) + + async def test_bytes(self): + expected = bytes("foobar", "utf-8") + + raw = bytes_mocked_request(bytes("foobar", "utf-8")) + request = RestRequest(raw) + observed = await request.content() + + self.assertEqual(expected, observed) + + async def test_raises(self): + raw = mocked_request(content_type="foo/bar", data="foobar".encode()) + request = RestRequest(raw) + with self.assertRaises(ValueError): + await request.content() + + async def test_with_type(self): + # noinspection PyPep8Naming + car = ModelType.build("Car", {"doors": int, "color": str, "owner": type(None)}) + expected = [car(3, "blue", None), car(5, "red", None)] + + raw = json_mocked_request([{"doors": "3", "color": "blue"}, {"doors": "5", "color": "red"}]) + request = RestRequest(raw) + observed = await request.content(type_=list[car]) + + self.assertEqual(expected, observed) + + async def test_with_type_classname(self): + expected = 3 + + raw = json_mocked_request("3") + request = RestRequest(raw) + observed = await request.content(type_=classname(int)) + + self.assertEqual(expected, observed) + + async def test_with_model_type(self): + expected = 3 + + raw = json_mocked_request("3") + request = RestRequest(raw) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + observed = await request.content(model_type=classname(int)) + + self.assertEqual(expected, observed) + + +class TestRestRequestParams(unittest.IsolatedAsyncioTestCase): + def test_has_url_params_false(self): + raw = mocked_request() + request = RestRequest(raw) + self.assertEqual(False, request.has_url_params) + + def test_has_url_params_true(self): + raw = mocked_request(url_params=[("foo", "1"), ("bar", "2"), ("foo", "3")]) + request = RestRequest(raw) + self.assertEqual(True, request.has_url_params) + + async def test_url_params(self): + expected = {"bar": "2", "foo": ["1", "3"]} + + raw = mocked_request(url_params=[("foo", "1"), ("bar", "2"), ("foo", "3")]) + request = RestRequest(raw) + observed = await request.url_params() + + self.assertEqual(expected, observed) + + async def test_url_params_with_type(self): + # noinspection PyPep8Naming + params = ModelType.build("Params", {"foo": list[int], "bar": int}) + expected = params(foo=[1, 3], bar=2) + + raw = mocked_request(url_params=[("foo", "1"), ("bar", "2"), ("foo", "3")]) + request = RestRequest(raw) + observed = await request.url_params(type_=params) + + self.assertEqual(expected, observed) + + async def test_url_params_raises(self): + raw = mocked_request() + request = RestRequest(raw) + with self.assertRaises(NotHasParamsException): + await request.url_params() + + def test_has_query_params_false(self): + raw = mocked_request() + request = RestRequest(raw) + self.assertEqual(False, request.has_query_params) + + def test_has_query_params_true(self): + raw = mocked_request(query_params=[("one", "1"), ("two", "2"), ("one", "3")]) + request = RestRequest(raw) + self.assertEqual(True, request.has_query_params) + + async def test_query_params(self): + expected = {"one": ["1", "3"], "two": "2"} + + raw = mocked_request(query_params=[("one", "1"), ("two", "2"), ("one", "3")]) + request = RestRequest(raw) + observed = await request.query_params() + + self.assertEqual(expected, observed) + + async def test_query_params_with_type(self): + # noinspection PyPep8Naming + params = ModelType.build("Params", {"one": list[int], "two": int}) + expected = params(one=[1, 3], two=2) + + raw = mocked_request(query_params=[("one", "1"), ("two", "2"), ("one", "3")]) + request = RestRequest(raw) + observed = await request.query_params(type_=params) + + self.assertEqual(expected, observed) + + async def test_query_params_raises(self): + raw = mocked_request() + request = RestRequest(raw) + with self.assertRaises(NotHasParamsException): + await request.query_params() + + def test_has_params_false(self): + raw = mocked_request() + request = RestRequest(raw) + self.assertEqual(False, request.has_params) + + def test_has_params_true(self): + raw = mocked_request(url_params=[("foo", "1"), ("bar", "2"), ("foo", "3")]) + request = RestRequest(raw) + self.assertEqual(True, request.has_params) + + raw = mocked_request(query_params=[("one", "1"), ("two", "2"), ("one", "3")]) + request = RestRequest(raw) + self.assertEqual(True, request.has_params) + + raw = mocked_request( + url_params=[("foo", "1"), ("bar", "2"), ("foo", "3")], + query_params=[("one", "1"), ("two", "2"), ("one", "3")], + ) + request = RestRequest(raw) + self.assertEqual(True, request.has_params) + + async def test_params(self): + expected = {"bar": "2", "foo": ["1", "3"], "one": ["1", "3"], "two": "2"} + + raw = mocked_request( + url_params=[("foo", "1"), ("bar", "2"), ("foo", "3")], + query_params=[("one", "1"), ("two", "2"), ("one", "3")], + ) + request = RestRequest(raw) + observed = await request.params() + + self.assertEqual(expected, observed) + + async def test_params_with_type(self): + # noinspection PyPep8Naming + params = ModelType.build("Params", {"foo": list[int], "bar": int, "one": list[int], "two": int}) + expected = params(foo=[1, 3], bar=2, one=[1, 3], two=2) + + raw = mocked_request( + url_params=[("foo", "1"), ("bar", "2"), ("foo", "3")], + query_params=[("one", "1"), ("two", "2"), ("one", "3")], + ) + request = RestRequest(raw) + observed = await request.params(type_=params) + + self.assertEqual(expected, observed) + + async def test_params_raises(self): + raw = mocked_request() + request = RestRequest(raw) + with self.assertRaises(NotHasParamsException): + await request.params() + + +class TestRestResponse(unittest.IsolatedAsyncioTestCase): + async def test_from_response(self): + base = Response("foobar") + + response = RestResponse.from_response(base) + + self.assertEqual(orjson.dumps("foobar"), await response.content()) + self.assertEqual(APPLICATION_JSON, response.content_type) + + def test_from_response_already(self): + base = RestResponse() + + response = RestResponse.from_response(base) + + self.assertEqual(base, response) + + def test_from_response_empty(self): + response = RestResponse.from_response(None) + + self.assertEqual(RestResponse(), response) + + async def test_empty(self): + response = RestResponse() + self.assertEqual(None, await response.content()) + self.assertEqual(APPLICATION_JSON, response.content_type) + + async def test_content_json(self): + data = [FakeModel("foo"), FakeModel("bar")] + response = RestResponse(data) + self.assertEqual(orjson.dumps([item.avro_data for item in data]), await response.content()) + self.assertEqual(APPLICATION_JSON, response.content_type) + + async def test_content_form(self): + data = {"foo": "bar", "one": "two"} + response = RestResponse(data, content_type="application/x-www-form-urlencoded") + self.assertEqual("foo=bar&one=two".encode(), await response.content()) + self.assertEqual(APPLICATION_URLENCODED, response.content_type) + + async def test_content_bytes(self): + data = "foobar".encode() + response = RestResponse(data, content_type=APPLICATION_OCTET_STREAM) + self.assertEqual("foobar".encode(), await response.content()) + self.assertEqual(APPLICATION_OCTET_STREAM, response.content_type) + + async def test_content_bytes_raises(self): + data = 56 + response = RestResponse(data, content_type=APPLICATION_OCTET_STREAM) + with self.assertRaises(ValueError): + await response.content() + + async def test_content_text(self): + data = "foobar" + response = RestResponse(data, content_type=TEXT_PLAIN) + self.assertEqual("foobar".encode(), await response.content()) + self.assertEqual(TEXT_PLAIN, response.content_type) + + async def test_content_text_raises(self): + data = 56 + response = RestResponse(data, content_type=TEXT_PLAIN) + with self.assertRaises(ValueError): + await response.content() + + async def test_content_avro(self): + data = "foobar" + response = RestResponse(data, content_type=AVRO_BINARY) + self.assertEqual(data, MinosAvroProtocol.decode(await response.content())) + self.assertEqual(AVRO_BINARY, response.content_type) + + async def test_content_image(self): + data = bytes("image", "utf-8") + response = RestResponse(data, content_type=IMAGE_PNG) + self.assertEqual(data, await response.content()) + self.assertEqual(IMAGE_PNG, response.content_type) + + +if __name__ == "__main__": + unittest.main() diff --git a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_services.py b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_services.py new file mode 100644 index 000000000..0f7c0188a --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_services.py @@ -0,0 +1,48 @@ +import unittest + +from aiohttp.test_utils import ( + AioHTTPTestCase, +) + +from minos.common import ( + MinosConfig, +) +from minos.plugins.rest_aiohttp import ( + RestService, +) +from tests.utils import ( + BASE_PATH, +) + + +class TestRestService(AioHTTPTestCase): + CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" + + async def get_application(self): + """ + Override the get_app method to return your application. + """ + config = MinosConfig(self.CONFIG_FILE_PATH) + rest_interface = RestService(config=config) + + return await rest_interface.create_application() + + async def test_methods(self): + url = "/order" + resp = await self.client.request("GET", url) + assert resp.status == 200 + text = await resp.text() + assert "get_order" in text + + url = "/ticket" + resp = await self.client.request("POST", url) + assert resp.status == 200 + text = await resp.text() + assert "ticket_added" in text + + resp = await self.client.request("GET", "/system/health") + assert resp.status == 200 + + +if __name__ == "__main__": + unittest.main() diff --git a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/utils.py b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/utils.py new file mode 100644 index 000000000..2cb3e5466 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/utils.py @@ -0,0 +1,100 @@ +import json +from typing import ( + Any, + Optional, + Union, +) +from unittest.mock import ( + AsyncMock, +) +from urllib.parse import ( + urlencode, +) +from uuid import ( + UUID, +) + +from aiohttp import ( + test_utils, + web, +) +from aiohttp.streams import ( + EmptyStreamReader, +) +from multidict import ( + MultiDict, +) + +from minos.common import ( + MinosAvroProtocol, +) + + +def json_mocked_request(data: Any, **kwargs) -> web.Request: + """For testng purposes.""" + return mocked_request(json.dumps(data).encode(), content_type="application/json", **kwargs) + + +def form_mocked_request(data: dict[str, Any], **kwargs) -> web.Request: + """For testng purposes.""" + return mocked_request( + urlencode(data, doseq=True).encode(), content_type="application/x-www-form-urlencoded", **kwargs + ) + + +def avro_mocked_request(data: Any, schema: Any, **kwargs) -> web.Request: + """For testng purposes.""" + return mocked_request(MinosAvroProtocol.encode(data, schema), content_type="avro/binary", **kwargs) + + +def text_mocked_request(data: str, **kwargs) -> web.Request: + """For testng purposes.""" + return mocked_request(data.encode(), content_type="text/plain", **kwargs) + + +def bytes_mocked_request(data: bytes, **kwargs) -> web.Request: + """For testng purposes.""" + return mocked_request(data, content_type="application/octet-stream", **kwargs) + + +def mocked_request( + data: Optional[bytes] = None, + headers: Optional[dict[str, str]] = None, + user: Optional[Union[str, UUID]] = None, + content_type: Optional[str] = None, + method: str = "POST", + path: str = "localhost", + url_params: Optional[list[tuple[str, str]]] = None, + query_params: Optional[list[tuple[str, str]]] = None, +) -> web.Request: + """For testing purposes""" + if headers is None: + headers = dict() + else: + headers = headers.copy() + + if user is not None: + headers["user"] = str(user) + + if content_type is not None: + headers["Content-Type"] = content_type + + kwargs = { + "method": method, + "path": path, + "headers": headers, + } + if data is None: + kwargs["payload"] = EmptyStreamReader() + + request = test_utils.make_mocked_request(**kwargs) + request.read = AsyncMock(return_value=data) + + if url_params is not None: + # noinspection PyProtectedMember + request._rel_url = request._rel_url.with_query(url_params) + + if query_params is not None: + request._match_info = MultiDict(map(lambda kv: (str(kv[0]), str(kv[1])), query_params)) + + return request diff --git a/packages/plugins/minos-graphql-aiohttp/tests/utils.py b/packages/plugins/minos-graphql-aiohttp/tests/utils.py new file mode 100644 index 000000000..d7424f6ce --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/tests/utils.py @@ -0,0 +1,27 @@ +from functools import ( + total_ordering, +) +from pathlib import ( + Path, +) +from typing import ( + Any, +) + +from minos.common import ( + DeclarativeModel, +) + +BASE_PATH = Path(__file__).parent +CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" + + +@total_ordering +class FakeModel(DeclarativeModel): + """For testing purposes""" + + data: Any + + def __lt__(self, other: Any) -> bool: + # noinspection PyBroadException + return isinstance(other, type(self)) and self.data < other.data From 05f92c60586bdce768651422fdf607beebe08cff Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Thu, 3 Mar 2022 17:47:54 +0100 Subject: [PATCH 02/44] ISSUE #149 --- .../minos/networks/__init__.py | 2 ++ .../minos/networks/decorators/__init__.py | 2 ++ .../minos/networks/decorators/analyzers.py | 10 ++++++ .../minos/networks/decorators/api.py | 2 ++ .../decorators/definitions/__init__.py | 4 +++ .../decorators/definitions/graphql.py | 34 +++++++++++++++++++ .../networks/decorators/definitions/kinds.py | 1 + .../tests/services/graphql.py | 14 ++++++++ .../test_decorators/test_analyzers.py | 16 +++++++++ .../test_networks/test_decorators/test_api.py | 5 +++ .../tests/utils.py | 12 +++++++ 11 files changed, 102 insertions(+) create mode 100644 packages/core/minos-microservice-networks/minos/networks/decorators/definitions/graphql.py create mode 100644 packages/core/minos-microservice-networks/tests/services/graphql.py diff --git a/packages/core/minos-microservice-networks/minos/networks/__init__.py b/packages/core/minos-microservice-networks/minos/networks/__init__.py index 9d28eff31..a6b81ea39 100644 --- a/packages/core/minos-microservice-networks/minos/networks/__init__.py +++ b/packages/core/minos-microservice-networks/minos/networks/__init__.py @@ -63,6 +63,8 @@ RestCommandEnrouteDecorator, RestEnrouteDecorator, RestQueryEnrouteDecorator, + GraphqlEnrouteDecorator, + GraphqlQueryEnrouteDecorator, enroute, ) from .discovery import ( diff --git a/packages/core/minos-microservice-networks/minos/networks/decorators/__init__.py b/packages/core/minos-microservice-networks/minos/networks/decorators/__init__.py index 05204050d..08529cf15 100644 --- a/packages/core/minos-microservice-networks/minos/networks/decorators/__init__.py +++ b/packages/core/minos-microservice-networks/minos/networks/decorators/__init__.py @@ -28,4 +28,6 @@ RestCommandEnrouteDecorator, RestEnrouteDecorator, RestQueryEnrouteDecorator, + GraphqlEnrouteDecorator, + GraphqlQueryEnrouteDecorator, ) diff --git a/packages/core/minos-microservice-networks/minos/networks/decorators/analyzers.py b/packages/core/minos-microservice-networks/minos/networks/decorators/analyzers.py index 96d071712..ba95a1085 100644 --- a/packages/core/minos-microservice-networks/minos/networks/decorators/analyzers.py +++ b/packages/core/minos-microservice-networks/minos/networks/decorators/analyzers.py @@ -28,6 +28,8 @@ RestCommandEnrouteDecorator, RestEnrouteDecorator, RestQueryEnrouteDecorator, + GraphqlEnrouteDecorator, + GraphqlQueryEnrouteDecorator, ) @@ -50,6 +52,14 @@ def get_rest_command_query(self) -> dict[str, set[RestEnrouteDecorator]]: # noinspection PyTypeChecker return self._get_items({RestCommandEnrouteDecorator, RestQueryEnrouteDecorator}) + def get_graphql(self) -> dict[str, set[GraphqlEnrouteDecorator]]: + """Returns rest's command and query values. + + :return: A mapping with functions as keys and a sets of decorators as values. + """ + # noinspection PyTypeChecker + return self._get_items({GraphqlQueryEnrouteDecorator}) + def get_broker_command_query_event(self) -> dict[str, set[BrokerEnrouteDecorator]]: """Returns broker's command, query and event values. diff --git a/packages/core/minos-microservice-networks/minos/networks/decorators/api.py b/packages/core/minos-microservice-networks/minos/networks/decorators/api.py index b3eddd752..7964f11f8 100644 --- a/packages/core/minos-microservice-networks/minos/networks/decorators/api.py +++ b/packages/core/minos-microservice-networks/minos/networks/decorators/api.py @@ -9,6 +9,7 @@ PeriodicEventEnrouteDecorator, RestCommandEnrouteDecorator, RestQueryEnrouteDecorator, + GraphqlQueryEnrouteDecorator ) @@ -39,6 +40,7 @@ class Enroute: broker = BrokerEnroute rest = RestEnroute periodic = PeriodicEnroute + graphql = GraphqlQueryEnrouteDecorator enroute = Enroute diff --git a/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/__init__.py b/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/__init__.py index e59e1c305..8454af195 100644 --- a/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/__init__.py +++ b/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/__init__.py @@ -22,3 +22,7 @@ RestEnrouteDecorator, RestQueryEnrouteDecorator, ) +from .graphql import ( + GraphqlEnrouteDecorator, + GraphqlQueryEnrouteDecorator +) diff --git a/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/graphql.py b/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/graphql.py new file mode 100644 index 000000000..95624d0ea --- /dev/null +++ b/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/graphql.py @@ -0,0 +1,34 @@ +from abc import ( + ABC, +) +from typing import ( + Final, + Iterable, +) + +from .abc import ( + EnrouteDecorator, +) +from .kinds import ( + EnrouteDecoratorKind, +) + + +class GraphqlEnrouteDecorator(EnrouteDecorator, ABC): + """Rest Enroute class""" + + def __init__(self, url: str, method: str): + self.url = url + self.method = method + + def __iter__(self) -> Iterable: + yield from ( + self.url, + self.method, + ) + + +class GraphqlQueryEnrouteDecorator(GraphqlEnrouteDecorator): + """Rest Command Enroute class""" + + KIND: Final[EnrouteDecoratorKind] = EnrouteDecoratorKind.Graphql diff --git a/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/kinds.py b/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/kinds.py index 513947c0a..ea3bb3fab 100644 --- a/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/kinds.py +++ b/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/kinds.py @@ -10,6 +10,7 @@ class EnrouteDecoratorKind(Enum): Command = auto() Query = auto() Event = auto() + Graphql = auto() @property def pre_fn_name(self) -> str: diff --git a/packages/core/minos-microservice-networks/tests/services/graphql.py b/packages/core/minos-microservice-networks/tests/services/graphql.py new file mode 100644 index 000000000..594089bf5 --- /dev/null +++ b/packages/core/minos-microservice-networks/tests/services/graphql.py @@ -0,0 +1,14 @@ +from minos.networks import ( + Request, + Response, + enroute, +) + + +class GraphqlService: + @enroute.graphql(url="/graphql", method="POST") + def add_ticket(self, request: Request) -> Response: + return Response({ + "data": {}, + "errors": [] + }) diff --git a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py index ab1bfae50..c04cb393c 100644 --- a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py +++ b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py @@ -11,6 +11,7 @@ PeriodicEventEnrouteDecorator, RestCommandEnrouteDecorator, RestQueryEnrouteDecorator, + GraphqlQueryEnrouteDecorator, ) from tests.utils import ( FakeService, @@ -41,6 +42,9 @@ def test_get_all(self): }, "send_newsletter": {PeriodicEventEnrouteDecorator("@daily")}, "check_inactive_users": {PeriodicEventEnrouteDecorator("@daily")}, + "graphql_handler": {GraphqlQueryEnrouteDecorator("graphql/", "GET")}, + "graphql_handler2": {GraphqlQueryEnrouteDecorator("graphql-2/", "GET")}, + "graphql_handler3": {GraphqlQueryEnrouteDecorator("graphql-3/", "POST")}, } self.assertEqual(expected, observed) @@ -57,6 +61,18 @@ def test_get_rest_command_query(self): self.assertEqual(expected, observed) + def test_get_graphql(self): + analyzer = EnrouteAnalyzer(FakeService) + + observed = analyzer.get_graphql() + expected = { + "graphql_handler": {GraphqlQueryEnrouteDecorator("graphql/", "GET")}, + "graphql_handler2": {GraphqlQueryEnrouteDecorator("graphql-2/", "GET")}, + "graphql_handler3": {GraphqlQueryEnrouteDecorator("graphql-3/", "POST")}, + } + + self.assertEqual(expected, observed) + def test_get_broker_command_query_event(self): analyzer = EnrouteAnalyzer(FakeService) diff --git a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py index 3ac33cd15..ffd123128 100644 --- a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py +++ b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py @@ -7,6 +7,7 @@ PeriodicEventEnrouteDecorator, RestCommandEnrouteDecorator, RestQueryEnrouteDecorator, + GraphqlQueryEnrouteDecorator, enroute, ) @@ -20,6 +21,10 @@ def test_rest_query(self): decorator = enroute.rest.query(url="tickets/", method="GET") self.assertEqual(RestQueryEnrouteDecorator("tickets/", "GET"), decorator) + def test_graphql(self): + decorator = enroute.graphql(url="graphql/", method="POST") + self.assertEqual(GraphqlQueryEnrouteDecorator("graphql/", "POST"), decorator) + def test_rest_event_raises(self): with self.assertRaises(AttributeError): enroute.rest.event("CreateTicket") diff --git a/packages/core/minos-microservice-networks/tests/utils.py b/packages/core/minos-microservice-networks/tests/utils.py index 980260a2b..da55b788e 100644 --- a/packages/core/minos-microservice-networks/tests/utils.py +++ b/packages/core/minos-microservice-networks/tests/utils.py @@ -169,6 +169,18 @@ def bar(self, request: Request): """For testing purposes.""" return Response("bar") + @enroute.graphql(url="graphql/", method="GET") + def graphql_handler(self): + return + + @enroute.graphql(url="graphql-2/", method="GET") + def graphql_handler2(self): + return + + @enroute.graphql(url="graphql-3/", method="POST") + def graphql_handler3(self): + return + class FakeServiceWithGetEnroute: @staticmethod From e9036e39a1deb1a298c9bc860548dd2f64681b8a Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Thu, 3 Mar 2022 17:51:35 +0100 Subject: [PATCH 03/44] ISSUE #149 --- .../plugins/graphql_aiohttp/graphql_example.py | 3 +-- .../minos/plugins/graphql_aiohttp/handlers.py | 9 ++++++++- .../minos/plugins/graphql_aiohttp/main.py | 10 ++++++++++ .../minos/plugins/graphql_aiohttp/services.py | 17 +++++++---------- 4 files changed, 26 insertions(+), 13 deletions(-) create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/main.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py index 99bb89a39..c3eb4b24e 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py @@ -3,8 +3,7 @@ graphql, GraphQLSchema, GraphQLObjectType, GraphQLField, GraphQLString) -async def resolve_hello(obj, info): - await asyncio.sleep(3) +def resolve_hello(obj, info): return 'world' schema = GraphQLSchema( diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py index 573a751a4..8da0723f7 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py @@ -37,6 +37,9 @@ from .responses import ( RestResponse, ) +from graphql_server.aiohttp import GraphQLView + +from .graphql_example import schema logger = logging.getLogger(__name__) @@ -99,7 +102,8 @@ def get_app(self) -> web.Application: @cached_property def _app(self) -> web.Application: app = web.Application() - self._mount_routes(app) + #self._mount_routes(app) + self._mount_graphql(app) return app def _mount_routes(self, app: web.Application): @@ -114,6 +118,9 @@ def _mount_one_route(self, method: str, url: str, action: Callable, app: web.App handler = self.get_callback(action) app.router.add_route(method, url, handler) + def _mount_graphql(self, app: web.Application): + GraphQLView.attach(app, schema=schema, graphiql=True) + @staticmethod def get_callback( fn: Callable[[RestRequest], Union[Optional[RestResponse], Awaitable[Optional[RestResponse]]]] diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/main.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/main.py new file mode 100644 index 000000000..87695eafb --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/main.py @@ -0,0 +1,10 @@ +from aiohttp import web +from minos.plugins.graphql_aiohttp.services import RestService + + +def main(): + web.run_app(RestService().create_application()) + + +if __name__ == "__main__": + main() diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py index e9b01883e..fd9e6d1b5 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py @@ -4,7 +4,6 @@ from aiomisc.service.aiohttp import ( AIOHTTPService, ) - from .handlers import ( RestHandler, ) @@ -22,9 +21,12 @@ class RestService(AIOHTTPService): """ def __init__(self, **kwargs): - pass #self.handler = RestHandler.from_config(**kwargs) #super().__init__(**(kwargs | {"address": self.handler.host, "port": self.handler.port})) + super().__init__(**(kwargs | {"address": "localhost", "port": 7030})) + + def _mount_graphql(self, app: web.Application): + GraphQLView.attach(app, schema=schema, graphiql=True) async def create_application(self) -> web.Application: """Create the web application. @@ -32,13 +34,8 @@ async def create_application(self) -> web.Application: :return: A ``web.Application`` instance. """ app = web.Application() - - GraphQLView.attach(app, schema=schema, graphiql=True) + # self._mount_routes(app) + self._mount_graphql(app) return app - # return self.handler.get_app() # pragma: no cover - - -async def main(): - web.run_app(RestService.create_application()) - + #return self.handler.get_app() # pragma: no cover From 2aa561d6d5bc0c02a87277ccd4840b42c782e29a Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Thu, 3 Mar 2022 16:51:46 +0000 Subject: [PATCH 04/44] Restyled by black --- .../minos/networks/decorators/api.py | 2 +- .../networks/decorators/definitions/__init__.py | 5 +---- .../tests/services/graphql.py | 5 +---- .../plugins/graphql_aiohttp/graphql_example.py | 15 +++++---------- .../minos/plugins/graphql_aiohttp/handlers.py | 2 +- .../minos/plugins/graphql_aiohttp/services.py | 7 +++---- 6 files changed, 12 insertions(+), 24 deletions(-) diff --git a/packages/core/minos-microservice-networks/minos/networks/decorators/api.py b/packages/core/minos-microservice-networks/minos/networks/decorators/api.py index 7964f11f8..34e4721ef 100644 --- a/packages/core/minos-microservice-networks/minos/networks/decorators/api.py +++ b/packages/core/minos-microservice-networks/minos/networks/decorators/api.py @@ -9,7 +9,7 @@ PeriodicEventEnrouteDecorator, RestCommandEnrouteDecorator, RestQueryEnrouteDecorator, - GraphqlQueryEnrouteDecorator + GraphqlQueryEnrouteDecorator, ) diff --git a/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/__init__.py b/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/__init__.py index 8454af195..3b570038d 100644 --- a/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/__init__.py +++ b/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/__init__.py @@ -22,7 +22,4 @@ RestEnrouteDecorator, RestQueryEnrouteDecorator, ) -from .graphql import ( - GraphqlEnrouteDecorator, - GraphqlQueryEnrouteDecorator -) +from .graphql import GraphqlEnrouteDecorator, GraphqlQueryEnrouteDecorator diff --git a/packages/core/minos-microservice-networks/tests/services/graphql.py b/packages/core/minos-microservice-networks/tests/services/graphql.py index 594089bf5..b4cc7d4f4 100644 --- a/packages/core/minos-microservice-networks/tests/services/graphql.py +++ b/packages/core/minos-microservice-networks/tests/services/graphql.py @@ -8,7 +8,4 @@ class GraphqlService: @enroute.graphql(url="/graphql", method="POST") def add_ticket(self, request: Request) -> Response: - return Response({ - "data": {}, - "errors": [] - }) + return Response({"data": {}, "errors": []}) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py index c3eb4b24e..1cd976f3e 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py @@ -1,16 +1,11 @@ import asyncio -from graphql import ( - graphql, GraphQLSchema, GraphQLObjectType, GraphQLField, GraphQLString) +from graphql import graphql, GraphQLSchema, GraphQLObjectType, GraphQLField, GraphQLString def resolve_hello(obj, info): - return 'world' + return "world" + schema = GraphQLSchema( - query=GraphQLObjectType( - name='RootQueryType', - fields={ - 'hello': GraphQLField( - GraphQLString, - resolve=resolve_hello) - })) + query=GraphQLObjectType(name="RootQueryType", fields={"hello": GraphQLField(GraphQLString, resolve=resolve_hello)}) +) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py index 8da0723f7..38e33179b 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py @@ -102,7 +102,7 @@ def get_app(self) -> web.Application: @cached_property def _app(self) -> web.Application: app = web.Application() - #self._mount_routes(app) + # self._mount_routes(app) self._mount_graphql(app) return app diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py index fd9e6d1b5..064a54894 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py @@ -21,8 +21,8 @@ class RestService(AIOHTTPService): """ def __init__(self, **kwargs): - #self.handler = RestHandler.from_config(**kwargs) - #super().__init__(**(kwargs | {"address": self.handler.host, "port": self.handler.port})) + # self.handler = RestHandler.from_config(**kwargs) + # super().__init__(**(kwargs | {"address": self.handler.host, "port": self.handler.port})) super().__init__(**(kwargs | {"address": "localhost", "port": 7030})) def _mount_graphql(self, app: web.Application): @@ -37,5 +37,4 @@ async def create_application(self) -> web.Application: # self._mount_routes(app) self._mount_graphql(app) return app - #return self.handler.get_app() # pragma: no cover - + # return self.handler.get_app() # pragma: no cover From 67b40a91f0bde148f92c093952d969dcfd66113b Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Thu, 3 Mar 2022 16:51:47 +0000 Subject: [PATCH 05/44] Restyled by isort --- .../minos/networks/__init__.py | 4 ++-- .../minos/networks/decorators/__init__.py | 4 ++-- .../minos/networks/decorators/analyzers.py | 4 ++-- .../minos/networks/decorators/api.py | 2 +- .../minos/networks/decorators/definitions/__init__.py | 5 ++++- .../test_networks/test_decorators/test_analyzers.py | 2 +- .../tests/test_networks/test_decorators/test_api.py | 2 +- .../minos/plugins/graphql_aiohttp/graphql_example.py | 9 ++++++++- .../minos/plugins/graphql_aiohttp/handlers.py | 9 ++++++--- .../minos/plugins/graphql_aiohttp/main.py | 9 +++++++-- .../minos/plugins/graphql_aiohttp/services.py | 10 +++++++--- 11 files changed, 41 insertions(+), 19 deletions(-) diff --git a/packages/core/minos-microservice-networks/minos/networks/__init__.py b/packages/core/minos-microservice-networks/minos/networks/__init__.py index a6b81ea39..0a5b567f8 100644 --- a/packages/core/minos-microservice-networks/minos/networks/__init__.py +++ b/packages/core/minos-microservice-networks/minos/networks/__init__.py @@ -55,6 +55,8 @@ EnrouteBuilder, EnrouteDecorator, EnrouteDecoratorKind, + GraphqlEnrouteDecorator, + GraphqlQueryEnrouteDecorator, Handler, HandlerMeta, HandlerWrapper, @@ -63,8 +65,6 @@ RestCommandEnrouteDecorator, RestEnrouteDecorator, RestQueryEnrouteDecorator, - GraphqlEnrouteDecorator, - GraphqlQueryEnrouteDecorator, enroute, ) from .discovery import ( diff --git a/packages/core/minos-microservice-networks/minos/networks/decorators/__init__.py b/packages/core/minos-microservice-networks/minos/networks/decorators/__init__.py index 08529cf15..521edd8fc 100644 --- a/packages/core/minos-microservice-networks/minos/networks/decorators/__init__.py +++ b/packages/core/minos-microservice-networks/minos/networks/decorators/__init__.py @@ -23,11 +23,11 @@ CheckDecorator, EnrouteDecorator, EnrouteDecoratorKind, + GraphqlEnrouteDecorator, + GraphqlQueryEnrouteDecorator, PeriodicEnrouteDecorator, PeriodicEventEnrouteDecorator, RestCommandEnrouteDecorator, RestEnrouteDecorator, RestQueryEnrouteDecorator, - GraphqlEnrouteDecorator, - GraphqlQueryEnrouteDecorator, ) diff --git a/packages/core/minos-microservice-networks/minos/networks/decorators/analyzers.py b/packages/core/minos-microservice-networks/minos/networks/decorators/analyzers.py index ba95a1085..ea8f8e4a4 100644 --- a/packages/core/minos-microservice-networks/minos/networks/decorators/analyzers.py +++ b/packages/core/minos-microservice-networks/minos/networks/decorators/analyzers.py @@ -24,12 +24,12 @@ BrokerEventEnrouteDecorator, BrokerQueryEnrouteDecorator, EnrouteDecorator, + GraphqlEnrouteDecorator, + GraphqlQueryEnrouteDecorator, PeriodicEventEnrouteDecorator, RestCommandEnrouteDecorator, RestEnrouteDecorator, RestQueryEnrouteDecorator, - GraphqlEnrouteDecorator, - GraphqlQueryEnrouteDecorator, ) diff --git a/packages/core/minos-microservice-networks/minos/networks/decorators/api.py b/packages/core/minos-microservice-networks/minos/networks/decorators/api.py index 34e4721ef..aab9ae7dd 100644 --- a/packages/core/minos-microservice-networks/minos/networks/decorators/api.py +++ b/packages/core/minos-microservice-networks/minos/networks/decorators/api.py @@ -6,10 +6,10 @@ BrokerCommandEnrouteDecorator, BrokerEventEnrouteDecorator, BrokerQueryEnrouteDecorator, + GraphqlQueryEnrouteDecorator, PeriodicEventEnrouteDecorator, RestCommandEnrouteDecorator, RestQueryEnrouteDecorator, - GraphqlQueryEnrouteDecorator, ) diff --git a/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/__init__.py b/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/__init__.py index 3b570038d..20f0b6a83 100644 --- a/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/__init__.py +++ b/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/__init__.py @@ -10,6 +10,10 @@ from .checkers import ( CheckDecorator, ) +from .graphql import ( + GraphqlEnrouteDecorator, + GraphqlQueryEnrouteDecorator, +) from .kinds import ( EnrouteDecoratorKind, ) @@ -22,4 +26,3 @@ RestEnrouteDecorator, RestQueryEnrouteDecorator, ) -from .graphql import GraphqlEnrouteDecorator, GraphqlQueryEnrouteDecorator diff --git a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py index c04cb393c..a8fe39b6e 100644 --- a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py +++ b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py @@ -8,10 +8,10 @@ BrokerEventEnrouteDecorator, BrokerQueryEnrouteDecorator, EnrouteAnalyzer, + GraphqlQueryEnrouteDecorator, PeriodicEventEnrouteDecorator, RestCommandEnrouteDecorator, RestQueryEnrouteDecorator, - GraphqlQueryEnrouteDecorator, ) from tests.utils import ( FakeService, diff --git a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py index ffd123128..af8e1fd9e 100644 --- a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py +++ b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py @@ -4,10 +4,10 @@ BrokerCommandEnrouteDecorator, BrokerEventEnrouteDecorator, BrokerQueryEnrouteDecorator, + GraphqlQueryEnrouteDecorator, PeriodicEventEnrouteDecorator, RestCommandEnrouteDecorator, RestQueryEnrouteDecorator, - GraphqlQueryEnrouteDecorator, enroute, ) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py index 1cd976f3e..e17e52148 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py @@ -1,5 +1,12 @@ import asyncio -from graphql import graphql, GraphQLSchema, GraphQLObjectType, GraphQLField, GraphQLString + +from graphql import ( + GraphQLField, + GraphQLObjectType, + GraphQLSchema, + GraphQLString, + graphql, +) def resolve_hello(obj, info): diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py index 38e33179b..cad7ce722 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py @@ -20,6 +20,9 @@ from aiohttp import ( web, ) +from graphql_server.aiohttp import ( + GraphQLView, +) from minos.common import ( MinosConfig, @@ -31,15 +34,15 @@ ResponseException, ) +from .graphql_example import ( + schema, +) from .requests import ( RestRequest, ) from .responses import ( RestResponse, ) -from graphql_server.aiohttp import GraphQLView - -from .graphql_example import schema logger = logging.getLogger(__name__) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/main.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/main.py index 87695eafb..b751c179b 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/main.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/main.py @@ -1,5 +1,10 @@ -from aiohttp import web -from minos.plugins.graphql_aiohttp.services import RestService +from aiohttp import ( + web, +) + +from minos.plugins.graphql_aiohttp.services import ( + RestService, +) def main(): diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py index 064a54894..c1ff1919c 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py @@ -4,12 +4,16 @@ from aiomisc.service.aiohttp import ( AIOHTTPService, ) +from graphql_server.aiohttp import ( + GraphQLView, +) + +from .graphql_example import ( + schema, +) from .handlers import ( RestHandler, ) -from graphql_server.aiohttp import GraphQLView - -from .graphql_example import schema class RestService(AIOHTTPService): From cbd81baaaf72d335abf7e6d00f35ce3ead2c0c34 Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Fri, 4 Mar 2022 13:39:04 +0100 Subject: [PATCH 06/44] ISSUE #149 --- .../test_handlers.py | 4 ++-- .../test_requests.py | 4 ++-- .../test_services.py | 2 +- .../{test_rest_aiohttp => test_graphql_aiohttp}/utils.py | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename packages/plugins/minos-graphql-aiohttp/tests/test_plugins/{test_rest_aiohttp => test_graphql_aiohttp}/test_handlers.py (97%) rename packages/plugins/minos-graphql-aiohttp/tests/test_plugins/{test_rest_aiohttp => test_graphql_aiohttp}/test_requests.py (99%) rename packages/plugins/minos-graphql-aiohttp/tests/test_plugins/{test_rest_aiohttp => test_graphql_aiohttp}/test_services.py (96%) rename packages/plugins/minos-graphql-aiohttp/tests/test_plugins/{test_rest_aiohttp => test_graphql_aiohttp}/utils.py (100%) diff --git a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_handlers.py b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_handlers.py similarity index 97% rename from packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_handlers.py rename to packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_handlers.py index 78a174cf9..2616a6cff 100644 --- a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_handlers.py +++ b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_handlers.py @@ -24,12 +24,12 @@ Request, Response, ) -from minos.plugins.rest_aiohttp import ( +from minos.plugins.graphql_aiohttp import ( RestHandler, RestResponse, RestResponseException, ) -from tests.test_plugins.test_rest_aiohttp.utils import ( +from tests.test_plugins.test_graphql_aiohttp.utils import ( json_mocked_request, mocked_request, ) diff --git a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_requests.py b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_requests.py similarity index 99% rename from packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_requests.py rename to packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_requests.py index 0c824073f..af0d8ec7f 100644 --- a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_requests.py +++ b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_requests.py @@ -18,11 +18,11 @@ NotHasParamsException, Response, ) -from minos.plugins.rest_aiohttp import ( +from minos.plugins.graphql_aiohttp import ( RestRequest, RestResponse, ) -from tests.test_plugins.test_rest_aiohttp.utils import ( +from tests.test_plugins.test_graphql_aiohttp.utils import ( avro_mocked_request, bytes_mocked_request, form_mocked_request, diff --git a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_services.py b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_services.py similarity index 96% rename from packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_services.py rename to packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_services.py index 0f7c0188a..640f76fc4 100644 --- a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/test_services.py +++ b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_services.py @@ -7,7 +7,7 @@ from minos.common import ( MinosConfig, ) -from minos.plugins.rest_aiohttp import ( +from minos.plugins.graphql_aiohttp import ( RestService, ) from tests.utils import ( diff --git a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/utils.py b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/utils.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_rest_aiohttp/utils.py rename to packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/utils.py From 5e6c21cf056c2d637f3b18c4d6ad1b08d26136ef Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Tue, 8 Mar 2022 19:18:47 +0100 Subject: [PATCH 07/44] ISSUE #149 --- .../minos/plugins/graphql_aiohttp/__init__.py | 3 - .../minos/plugins/graphql_aiohttp/handlers.py | 24 ++- .../graphql_aiohttp/handlers/__init__.py | 0 .../plugins/graphql_aiohttp/handlers/abc.py | 100 +++++++++++++ .../graphql_aiohttp/handlers/graphiql.py | 10 ++ .../graphql_aiohttp/handlers/graphql.py | 11 ++ .../minos/plugins/graphql_aiohttp/schema.py | 10 ++ .../graphql_aiohttp/schemas/schema_hello.py | 16 ++ .../minos/plugins/graphql_aiohttp/services.py | 28 +++- .../graphql_aiohttp/templates/graphiql.html | 139 ++++++++++++++++++ .../plugins/minos-graphql-aiohttp/poetry.lock | 93 ++++++++---- .../minos-graphql-aiohttp/pyproject.toml | 5 +- 12 files changed, 397 insertions(+), 42 deletions(-) create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/__init__.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/abc.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphiql.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphql.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schema.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schemas/schema_hello.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/templates/graphiql.html diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py index 1a9008f48..2aee93245 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py @@ -5,9 +5,6 @@ from .exceptions import ( RestResponseException, ) -from .handlers import ( - RestHandler, -) from .requests import ( RestRequest, ) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py index cad7ce722..5b1c3f01c 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py @@ -17,12 +17,11 @@ Union, ) +import aiohttp_jinja2 +import jinja2 from aiohttp import ( web, ) -from graphql_server.aiohttp import ( - GraphQLView, -) from minos.common import ( MinosConfig, @@ -34,6 +33,8 @@ ResponseException, ) +from minos.plugins.graphql_aiohttp.handlers.graphiql import GraphiqlHandler +from minos.plugins.graphql_aiohttp.handlers.graphql import GraphqlHandler from .graphql_example import ( schema, ) @@ -105,8 +106,10 @@ def get_app(self) -> web.Application: @cached_property def _app(self) -> web.Application: app = web.Application() + await self.initialize_jinja2(app) + self.register_handlers(app) # self._mount_routes(app) - self._mount_graphql(app) + # self._mount_graphql(app) return app def _mount_routes(self, app: web.Application): @@ -121,8 +124,21 @@ def _mount_one_route(self, method: str, url: str, action: Callable, app: web.App handler = self.get_callback(action) app.router.add_route(method, url, handler) + """ def _mount_graphql(self, app: web.Application): GraphQLView.attach(app, schema=schema, graphiql=True) + """ + + def register_handlers(self, app: web.Application): + routes = [(r'/graphql', GraphqlHandler)] + + # if self.dev: + routes += [(r'/graphiql', GraphiqlHandler)] + + app.router.add_routes([web.view(route[0], route[1]) for route in routes]) + + async def initialize_jinja2(self, app: web.Application): + aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader('./templates')) @staticmethod def get_callback( diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/__init__.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/abc.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/abc.py new file mode 100644 index 000000000..628fbe4a7 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/abc.py @@ -0,0 +1,100 @@ +import asyncio +import logging +import urllib +import json +import aiohttp +from aiohttp import web +#from graphql.execution.executors.asyncio import AsyncioExecutor + + +class BaseHandler(web.View): + + @property + def fetch(self): + return self.app.fetch + + @property + def app(self): + return self.request.app + + async def options(self): + return web.Response( + status=204, + headers={ + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH', + 'Access-Control-Allow-Headers': 'x-requested-with,access-control-allow-origin,authorization,content-type', + }, + ) + + +class GQLBaseHandler(BaseHandler): + async def get(self): + return await self.post() + + async def post(self): + return await self.handle_graqhql() + + async def handle_graqhql(self): + status = 200 + result = await self.execute_graphql() + logging.debug( + 'GraphQL result data: %s errors: %s', + result.data, + result.errors, + #result.invalid, + ) + + if result and result.errors: + status = 500 + + #ex = ExecutionError(errors=result.errors) + #logging.debug('GraphQL Error: %s', ex) + + errors = result.errors + + if errors is None: + errors = [] + + return web.json_response( + {'data': result.data, 'errors': [str(err) for err in errors]}, + status=status, + headers={'Access-Control-Allow-Origin': '*'}, + ) + + async def execute_graphql(self): + graphql_req = await self.graphql_request + logging.debug('graphql request: %s', graphql_req) + context_value = graphql_req.get('context', {}) + variables = graphql_req.get('variables', {}) + + context_value['application'] = self.app + + #executor = AsyncioExecutor(loop=asyncio.get_event_loop()) + result = self.schema.execute( + graphql_req['query'], + #executor=executor, + #return_promise=True, + context_value=context_value, + variable_values=variables, + ) + + ref = self.request.headers.get('referer') + url_path = '' + + if ref: + url = urllib.parse.urlparse(ref) + url_path = url.path + + if result.errors: + if '/graphiql' not in url_path: + aiohttp.log.server_logger.error(f'Graphql query error: for query "{graphql_req}"') + + return result + + @property + async def graphql_request(self): + if self.request.method == 'GET': + return json.loads(self.request.query['q']) + + return await self.request.json() diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphiql.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphiql.py new file mode 100644 index 000000000..2066ad102 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphiql.py @@ -0,0 +1,10 @@ +import aiohttp_jinja2 +from .abc import BaseHandler + + +class GraphiqlHandler(BaseHandler): + + # Renderiza o GraphiQL + @aiohttp_jinja2.template('graphiql.html') + async def get(self): + return {'base_url': f'/graphql'} diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphql.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphql.py new file mode 100644 index 000000000..9746e4f15 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphql.py @@ -0,0 +1,11 @@ +import graphene + +from .abc import GQLBaseHandler +from minos.plugins.graphql_aiohttp.schema import AllQuery + + +class GraphqlHandler(GQLBaseHandler): + + @property + def schema(self): + return graphene.Schema(query=AllQuery) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schema.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schema.py new file mode 100644 index 000000000..9d1ee5a0e --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schema.py @@ -0,0 +1,10 @@ +from schemas.schema_hello import Query + + +class AllQuery( + Query, + # Schema1, + # Schema2, + # Schema3 +): + '''AllQuery''' diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schemas/schema_hello.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schemas/schema_hello.py new file mode 100644 index 000000000..b29703d77 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schemas/schema_hello.py @@ -0,0 +1,16 @@ +from graphene import ObjectType, String, Schema + + +class Query(ObjectType): + + hello = String(name=String(default_value="stranger")) + goodbye = String() + + def resolve_hello(root, info, name): + return f'Hello {name}!' + + def resolve_goodbye(root, info): + return 'See ya!' + + +schema = Schema(query=Query) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py index c1ff1919c..b2cc92071 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py @@ -1,19 +1,17 @@ +import aiohttp_jinja2 +import jinja2 from aiohttp import ( web, ) from aiomisc.service.aiohttp import ( AIOHTTPService, ) -from graphql_server.aiohttp import ( - GraphQLView, -) from .graphql_example import ( schema, ) -from .handlers import ( - RestHandler, -) +from .handlers.graphiql import GraphiqlHandler +from .handlers.graphql import GraphqlHandler class RestService(AIOHTTPService): @@ -28,9 +26,21 @@ def __init__(self, **kwargs): # self.handler = RestHandler.from_config(**kwargs) # super().__init__(**(kwargs | {"address": self.handler.host, "port": self.handler.port})) super().__init__(**(kwargs | {"address": "localhost", "port": 7030})) - + """ def _mount_graphql(self, app: web.Application): GraphQLView.attach(app, schema=schema, graphiql=True) + """ + + def register_handlers(self, app: web.Application): + routes = [(r'/graphql', GraphqlHandler)] + + # if self.dev: + routes += [(r'/graphiql', GraphiqlHandler)] + + app.router.add_routes([web.view(route[0], route[1]) for route in routes]) + + async def initialize_jinja2(self, app: web.Application): + aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader('./templates')) async def create_application(self) -> web.Application: """Create the web application. @@ -38,7 +48,9 @@ async def create_application(self) -> web.Application: :return: A ``web.Application`` instance. """ app = web.Application() + self.register_handlers(app) + await self.initialize_jinja2(app) # self._mount_routes(app) - self._mount_graphql(app) + #self._mount_graphql(app) return app # return self.handler.get_app() # pragma: no cover diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/templates/graphiql.html b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/templates/graphiql.html new file mode 100644 index 000000000..0a90e30e2 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/templates/graphiql.html @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + +
Loading...
+ + + diff --git a/packages/plugins/minos-graphql-aiohttp/poetry.lock b/packages/plugins/minos-graphql-aiohttp/poetry.lock index e4f51e8c9..231a93c28 100644 --- a/packages/plugins/minos-graphql-aiohttp/poetry.lock +++ b/packages/plugins/minos-graphql-aiohttp/poetry.lock @@ -18,6 +18,18 @@ yarl = ">=1.0,<2.0" [package.extras] speedups = ["aiodns", "brotli", "cchardet"] +[[package]] +name = "aiohttp-jinja2" +version = "1.5" +description = "jinja2 template renderer for aiohttp.web (http server for asyncio)" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiohttp = ">=3.6.3" +jinja2 = ">=3.0.0" + [[package]] name = "aiomisc" version = "15.6.8" @@ -73,6 +85,17 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "aniso8601" +version = "9.0.1" +description = "A library for parsing ISO 8601 strings." +category = "main" +optional = false +python-versions = "*" + +[package.extras] +dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"] + [[package]] name = "async-timeout" version = "4.0.2" @@ -303,36 +326,41 @@ category = "main" optional = false python-versions = ">=3.7" +[[package]] +name = "graphene" +version = "3.0" +description = "GraphQL Framework for Python" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +aniso8601 = ">=8,<10" +graphql-core = ">=3.1.2,<3.2.0" +graphql-relay = ">=3.0,<4" + +[package.extras] +dev = ["black (==19.10b0)", "flake8 (>=3.7,<4)", "pytest (>=5.3,<6)", "pytest-benchmark (>=3.2,<4)", "pytest-cov (>=2.8,<3)", "pytest-mock (>=2,<3)", "pytest-asyncio (>=0.10,<2)", "snapshottest (>=0.5,<1)", "coveralls (>=1.11,<2)", "promise (>=2.3,<3)", "mock (>=4.0,<5)", "pytz (==2021.1)", "iso8601 (>=0.1,<2)"] +test = ["pytest (>=5.3,<6)", "pytest-benchmark (>=3.2,<4)", "pytest-cov (>=2.8,<3)", "pytest-mock (>=2,<3)", "pytest-asyncio (>=0.10,<2)", "snapshottest (>=0.5,<1)", "coveralls (>=1.11,<2)", "promise (>=2.3,<3)", "mock (>=4.0,<5)", "pytz (==2021.1)", "iso8601 (>=0.1,<2)"] + [[package]] name = "graphql-core" -version = "3.2.0" +version = "3.1.7" description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." category = "main" optional = false python-versions = ">=3.6,<4" [[package]] -name = "graphql-server" -version = "3.0.0b5" -description = "GraphQL Server tools for powering your server" +name = "graphql-relay" +version = "3.1.5" +description = "Relay library for graphql-core" category = "main" optional = false -python-versions = "*" +python-versions = ">=3.6,<4" [package.dependencies] -aiohttp = {version = ">=3.8,<4", optional = true, markers = "extra == \"aiohttp\""} -graphql-core = ">=3.2,<3.3" -typing-extensions = ">=4,<5" - -[package.extras] -aiohttp = ["aiohttp (>=3.8,<4)"] -all = ["graphql-core (>=3.2,<3.3)", "typing-extensions (>=4,<5)", "flask (>=1,<2)", "sanic (>=20.3,<21)", "webob (>=1.8.7,<2)", "aiohttp (>=3.8,<4)", "quart (>=0.6.15,<0.15)"] -dev = ["graphql-core (>=3.2,<3.3)", "typing-extensions (>=4,<5)", "flask (>=1,<2)", "sanic (>=20.3,<21)", "webob (>=1.8.7,<2)", "aiohttp (>=3.8,<4)", "quart (>=0.6.15,<0.15)", "flake8 (>=4,<5)", "isort (>=5,<6)", "black (>=19.10b0)", "mypy (>=0.931,<1)", "check-manifest (>=0.47,<1)", "pytest (>=6.2,<6.3)", "pytest-asyncio (>=0.16,<1)", "pytest-cov (>=3,<4)", "aiohttp (>=3.8,<4)", "Jinja2 (>=2.11,<3)"] -flask = ["flask (>=1,<2)"] -quart = ["quart (>=0.6.15,<0.15)"] -sanic = ["sanic (>=20.3,<21)"] -test = ["graphql-core (>=3.2,<3.3)", "typing-extensions (>=4,<5)", "flask (>=1,<2)", "sanic (>=20.3,<21)", "webob (>=1.8.7,<2)", "aiohttp (>=3.8,<4)", "quart (>=0.6.15,<0.15)", "pytest (>=6.2,<6.3)", "pytest-asyncio (>=0.16,<1)", "pytest-cov (>=3,<4)", "aiohttp (>=3.8,<4)", "Jinja2 (>=2.11,<3)"] -webob = ["webob (>=1.8.7,<2)"] +graphql-core = ">=3.1,<3.2" [[package]] name = "identify" @@ -403,7 +431,7 @@ plugins = ["setuptools"] name = "jinja2" version = "3.0.3" description = "A very fast and expressive template engine." -category = "dev" +category = "main" optional = false python-versions = ">=3.6" @@ -437,7 +465,7 @@ mistune = "0.8.4" name = "markupsafe" version = "2.1.0" description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" +category = "main" optional = false python-versions = ">=3.7" @@ -888,7 +916,7 @@ python-versions = ">=3.7" name = "typing-extensions" version = "4.1.1" description = "Backported and Experimental Type Hints for Python 3.6+" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -950,7 +978,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "0dbb3f5d394a3212b60abcc7c2b56b0babee5106f012b46671236628363623bc" +content-hash = "bdba872b9f221db81478baf76a21d4d23e88691a09a3aab49967bbceb6e42685" [metadata.files] aiohttp = [ @@ -1027,6 +1055,10 @@ aiohttp = [ {file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"}, {file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"}, ] +aiohttp-jinja2 = [ + {file = "aiohttp-jinja2-1.5.tar.gz", hash = "sha256:7c3ba5eac060b691f4e50534af2d79fca2a75712ebd2b25e6fcb1295859f910b"}, + {file = "aiohttp_jinja2-1.5-py3-none-any.whl", hash = "sha256:b55c0ed167b0cc4b6d6a50fb2299a44beb5dc4aec9df21305b91a5484694cf74"}, +] aiomisc = [ {file = "aiomisc-15.6.8-py3-none-any.whl", hash = "sha256:b65f7f8e50d9791bbb745253e814862880b8a35bf21e4eea545ada9daf89836c"}, {file = "aiomisc-15.6.8.tar.gz", hash = "sha256:09024d1f544de98b152dad8f7eb7d02c64457aaaaf03bd6c76a20b73328a3a74"}, @@ -1043,6 +1075,10 @@ alabaster = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] +aniso8601 = [ + {file = "aniso8601-9.0.1-py2.py3-none-any.whl", hash = "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f"}, + {file = "aniso8601-9.0.1.tar.gz", hash = "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973"}, +] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, @@ -1291,12 +1327,17 @@ frozenlist = [ {file = "frozenlist-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:772965f773757a6026dea111a15e6e2678fbd6216180f82a48a40b27de1ee2ab"}, {file = "frozenlist-1.3.0.tar.gz", hash = "sha256:ce6f2ba0edb7b0c1d8976565298ad2deba6f8064d2bebb6ffce2ca896eb35b0b"}, ] +graphene = [ + {file = "graphene-3.0-py2.py3-none-any.whl", hash = "sha256:57ce5ee7c9dc194224a1df96e4f7cb48d31eae96c791091d059f8f3d4d131390"}, + {file = "graphene-3.0.tar.gz", hash = "sha256:af48066e152200a071aac1e0b703954c717ec7268720ba190a0c91d9bcb0a122"}, +] graphql-core = [ - {file = "graphql-core-3.2.0.tar.gz", hash = "sha256:86e2a0be008bfde19ef78388de8a725a1d942a9190ca431c24a60837973803ce"}, - {file = "graphql_core-3.2.0-py3-none-any.whl", hash = "sha256:0dda7e63676f119bb3d814621190fedad72fda07a8e9ab780bedd9f1957c6dc6"}, + {file = "graphql-core-3.1.7.tar.gz", hash = "sha256:62ec192150ccecd9a18cfb79e3e72eb7d1fd68fb594ef19c40099b6deec8ef0c"}, + {file = "graphql_core-3.1.7-py3-none-any.whl", hash = "sha256:9b460f60320be01c7f3b1766cf3e406933003008055079b9d983b8f3988f4400"}, ] -graphql-server = [ - {file = "graphql-server-3.0.0b5.tar.gz", hash = "sha256:85c12c421e14f339883865d75fe316f626f3f875c70f8c366a93d50c5b1c8a23"}, +graphql-relay = [ + {file = "graphql-relay-3.1.5.tar.gz", hash = "sha256:127f40913f11cb847452ef794931261aae3b222eaaf976f7c8430298534e54d3"}, + {file = "graphql_relay-3.1.5-py3-none-any.whl", hash = "sha256:d1455316182e1017d06f0b4d4a2ce8bafad38253a6c7c5666fc80d5d516ee5c8"}, ] identify = [ {file = "identify-2.4.11-py2.py3-none-any.whl", hash = "sha256:fd906823ed1db23c7a48f9b176a1d71cb8abede1e21ebe614bac7bdd688d9213"}, diff --git a/packages/plugins/minos-graphql-aiohttp/pyproject.toml b/packages/plugins/minos-graphql-aiohttp/pyproject.toml index 338ba801e..9d5a2d17a 100644 --- a/packages/plugins/minos-graphql-aiohttp/pyproject.toml +++ b/packages/plugins/minos-graphql-aiohttp/pyproject.toml @@ -34,7 +34,10 @@ python = "^3.9" minos-microservice-common = "^0.5.0" minos-microservice-networks = "^0.5.0" aiohttp = "^3.8.1" -graphql-server = {version = "^3.0.0-beta.5", extras = ["aiohttp"]} +aiohttp-jinja2 = "^1.5" +async-timeout = "^4.0.2" +graphene = "^3.0" +Jinja2 = "^3.0.3" [tool.poetry.dev-dependencies] minos-microservice-common = { path = "../../core/minos-microservice-common", develop = true } From 8f296635a645f6865e501c25600173f94539210e Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Tue, 8 Mar 2022 19:37:18 +0100 Subject: [PATCH 08/44] ISSUE #149 --- .../plugins/graphql_aiohttp/graphql_example.py | 18 ------------------ .../minos/plugins/graphql_aiohttp/handlers.py | 7 +++---- .../graphql_aiohttp/handlers/__init__.py | 6 ++++++ .../graphql_aiohttp/handlers/graphiql.py | 2 -- .../minos/plugins/graphql_aiohttp/services.py | 8 +++----- 5 files changed, 12 insertions(+), 29 deletions(-) delete mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py deleted file mode 100644 index e17e52148..000000000 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/graphql_example.py +++ /dev/null @@ -1,18 +0,0 @@ -import asyncio - -from graphql import ( - GraphQLField, - GraphQLObjectType, - GraphQLSchema, - GraphQLString, - graphql, -) - - -def resolve_hello(obj, info): - return "world" - - -schema = GraphQLSchema( - query=GraphQLObjectType(name="RootQueryType", fields={"hello": GraphQLField(GraphQLString, resolve=resolve_hello)}) -) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py index 5b1c3f01c..b56c32f65 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py @@ -33,10 +33,9 @@ ResponseException, ) -from minos.plugins.graphql_aiohttp.handlers.graphiql import GraphiqlHandler -from minos.plugins.graphql_aiohttp.handlers.graphql import GraphqlHandler -from .graphql_example import ( - schema, +from .handlers import ( + GraphiqlHandler, + GraphqlHandler ) from .requests import ( RestRequest, diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/__init__.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/__init__.py index e69de29bb..1e49dde44 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/__init__.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/__init__.py @@ -0,0 +1,6 @@ +from .graphql import ( + GraphqlHandler, +) +from .graphiql import ( + GraphiqlHandler, +) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphiql.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphiql.py index 2066ad102..4e82cd7c3 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphiql.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphiql.py @@ -3,8 +3,6 @@ class GraphiqlHandler(BaseHandler): - - # Renderiza o GraphiQL @aiohttp_jinja2.template('graphiql.html') async def get(self): return {'base_url': f'/graphql'} diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py index b2cc92071..ce26b9858 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py @@ -6,12 +6,10 @@ from aiomisc.service.aiohttp import ( AIOHTTPService, ) - -from .graphql_example import ( - schema, +from .handlers import ( + GraphiqlHandler, + GraphqlHandler ) -from .handlers.graphiql import GraphiqlHandler -from .handlers.graphql import GraphqlHandler class RestService(AIOHTTPService): From bedc99753faf183e447b1ac45d125ff3c1db26c9 Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Fri, 11 Mar 2022 10:21:27 +0100 Subject: [PATCH 09/44] ISSUE #149 --- .../minos/plugins/graphql_aiohttp/__init__.py | 6 - .../minos/plugins/graphql_aiohttp/handlers.py | 194 -------- .../plugins/graphql_aiohttp/handlers/abc.py | 29 +- .../graphql_aiohttp/handlers/graphql.py | 11 +- .../minos/plugins/graphql_aiohttp/requests.py | 234 ---------- .../plugins/graphql_aiohttp/responses.py | 117 ----- .../minos/plugins/graphql_aiohttp/schema.py | 10 - .../graphql_aiohttp/schemas/schema_hello.py | 16 - .../star_wars_example/__init__.py | 8 + .../graphql_aiohttp/star_wars_example/data.py | 147 ++++++ .../star_wars_example/schema.py | 274 +++++++++++ .../plugins/minos-graphql-aiohttp/poetry.lock | 59 +-- .../minos-graphql-aiohttp/pyproject.toml | 3 +- .../test_graphql_aiohttp/test_handlers.py | 131 ------ .../test_graphql_aiohttp/test_requests.py | 441 ------------------ .../test_graphql_aiohttp/test_services.py | 48 -- 16 files changed, 452 insertions(+), 1276 deletions(-) delete mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py delete mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/requests.py delete mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/responses.py delete mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schema.py delete mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schemas/schema_hello.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/__init__.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/data.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/schema.py delete mode 100644 packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_handlers.py delete mode 100644 packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_requests.py delete mode 100644 packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_services.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py index 2aee93245..d1a5f0caf 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py @@ -5,12 +5,6 @@ from .exceptions import ( RestResponseException, ) -from .requests import ( - RestRequest, -) -from .responses import ( - RestResponse, -) from .services import ( RestService, ) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py deleted file mode 100644 index b56c32f65..000000000 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers.py +++ /dev/null @@ -1,194 +0,0 @@ -from __future__ import ( - annotations, -) - -import logging -from functools import ( - cached_property, - wraps, -) -from inspect import ( - isawaitable, -) -from typing import ( - Awaitable, - Callable, - Optional, - Union, -) - -import aiohttp_jinja2 -import jinja2 -from aiohttp import ( - web, -) - -from minos.common import ( - MinosConfig, - MinosSetup, -) -from minos.networks import ( - REQUEST_USER_CONTEXT_VAR, - EnrouteBuilder, - ResponseException, -) - -from .handlers import ( - GraphiqlHandler, - GraphqlHandler -) -from .requests import ( - RestRequest, -) -from .responses import ( - RestResponse, -) - -logger = logging.getLogger(__name__) - - -class RestHandler(MinosSetup): - """Rest Handler class.""" - - def __init__(self, host: str, port: int, endpoints: dict[(str, str), Callable], **kwargs): - super().__init__(**kwargs) - self._host = host - self._port = port - self._endpoints = endpoints - - @property - def endpoints(self) -> dict[(str, str), Callable]: - """Endpoints getter. - - :return: A dictionary value. - """ - return self._endpoints - - @classmethod - def _from_config(cls, *args, config: MinosConfig, **kwargs) -> RestHandler: - host = config.rest.host - port = config.rest.port - endpoints = cls._endpoints_from_config(config) - - return cls(host=host, port=port, endpoints=endpoints, **kwargs) - - @staticmethod - def _endpoints_from_config(config: MinosConfig, **kwargs) -> dict[(str, str), Callable]: - builder = EnrouteBuilder(*config.services, middleware=config.middleware) - decorators = builder.get_rest_command_query(config=config, **kwargs) - endpoints = {(decorator.url, decorator.method): fn for decorator, fn in decorators.items()} - return endpoints - - @property - def host(self) -> str: - """Get the rest host. - - :return: A ``str`` object. - """ - return self._host - - @property - def port(self) -> int: - """Get the rest port. - - :return: An integer value. - """ - return self._port - - def get_app(self) -> web.Application: - """Return rest application instance. - - :return: A `web.Application` instance. - """ - return self._app - - @cached_property - def _app(self) -> web.Application: - app = web.Application() - await self.initialize_jinja2(app) - self.register_handlers(app) - # self._mount_routes(app) - # self._mount_graphql(app) - return app - - def _mount_routes(self, app: web.Application): - """Load routes from config file.""" - for (url, method), fn in self._endpoints.items(): - self._mount_one_route(method, url, fn, app) - - # Load default routes - self._mount_system_health(app) - - def _mount_one_route(self, method: str, url: str, action: Callable, app: web.Application) -> None: - handler = self.get_callback(action) - app.router.add_route(method, url, handler) - - """ - def _mount_graphql(self, app: web.Application): - GraphQLView.attach(app, schema=schema, graphiql=True) - """ - - def register_handlers(self, app: web.Application): - routes = [(r'/graphql', GraphqlHandler)] - - # if self.dev: - routes += [(r'/graphiql', GraphiqlHandler)] - - app.router.add_routes([web.view(route[0], route[1]) for route in routes]) - - async def initialize_jinja2(self, app: web.Application): - aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader('./templates')) - - @staticmethod - def get_callback( - fn: Callable[[RestRequest], Union[Optional[RestResponse], Awaitable[Optional[RestResponse]]]] - ) -> Callable[[web.Request], Awaitable[web.Response]]: - """Get the handler function to be used by the ``aiohttp`` Controller. - - :param fn: The action function. - :return: A wrapper function around the given one that is compatible with the ``aiohttp`` Controller. - """ - - @wraps(fn) - async def _wrapper(request: web.Request) -> web.Response: - logger.info(f"Dispatching '{request!s}' from '{request.remote!s}'...") - - request = RestRequest(request) - token = REQUEST_USER_CONTEXT_VAR.set(request.user) - - try: - response = fn(request) - if isawaitable(response): - response = await response - - if not isinstance(response, RestResponse): - response = RestResponse.from_response(response) - - content = await response.content() - content_type = response.content_type - status = response.status - - return web.Response(body=content, content_type=content_type, status=status) - - except ResponseException as exc: - logger.warning(f"Raised an application exception: {exc!s}") - return web.Response(text=str(exc), status=exc.status) - except Exception as exc: - logger.exception(f"Raised a system exception: {exc!r}") - raise web.HTTPInternalServerError() - finally: - REQUEST_USER_CONTEXT_VAR.reset(token) - - return _wrapper - - def _mount_system_health(self, app: web.Application): - """Mount System Health Route.""" - app.router.add_get("/system/health", self._system_health_handler) - - @staticmethod - async def _system_health_handler(request: web.Request) -> web.Response: - """System Health Route Handler. - :return: A `web.json_response` response. - """ - logger.info(f"Dispatching '{request!s}' from '{request.remote!s}'...") - return web.json_response({"host": request.host}) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/abc.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/abc.py index 628fbe4a7..2b200ef00 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/abc.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/abc.py @@ -1,11 +1,12 @@ -import asyncio import logging import urllib import json import aiohttp from aiohttp import web -#from graphql.execution.executors.asyncio import AsyncioExecutor - +from graphql import graphql +from minos.plugins.graphql_aiohttp.star_wars_example import ( + star_wars_schema, +) class BaseHandler(web.View): @@ -42,42 +43,34 @@ async def handle_graqhql(self): 'GraphQL result data: %s errors: %s', result.data, result.errors, - #result.invalid, ) if result and result.errors: status = 500 - #ex = ExecutionError(errors=result.errors) - #logging.debug('GraphQL Error: %s', ex) - errors = result.errors if errors is None: errors = [] return web.json_response( - {'data': result.data, 'errors': [str(err) for err in errors]}, + {'data': result.data, 'errors': [err.message for err in errors]}, status=status, headers={'Access-Control-Allow-Origin': '*'}, ) async def execute_graphql(self): graphql_req = await self.graphql_request - logging.debug('graphql request: %s', graphql_req) context_value = graphql_req.get('context', {}) variables = graphql_req.get('variables', {}) context_value['application'] = self.app - - #executor = AsyncioExecutor(loop=asyncio.get_event_loop()) - result = self.schema.execute( - graphql_req['query'], - #executor=executor, - #return_promise=True, - context_value=context_value, - variable_values=variables, - ) + source = graphql_req['query'] + result = await graphql(schema=star_wars_schema, + source=source, + context_value=context_value, + variable_values=variables, + ) ref = self.request.headers.get('referer') url_path = '' diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphql.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphql.py index 9746e4f15..78a51aa7c 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphql.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphql.py @@ -1,11 +1,14 @@ -import graphene +from graphql.utilities import ( + print_schema +) from .abc import GQLBaseHandler -from minos.plugins.graphql_aiohttp.schema import AllQuery - +from minos.plugins.graphql_aiohttp.star_wars_example import ( + star_wars_schema, +) class GraphqlHandler(GQLBaseHandler): @property def schema(self): - return graphene.Schema(query=AllQuery) + return print_schema(star_wars_schema) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/requests.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/requests.py deleted file mode 100644 index 0b99c93b0..000000000 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/requests.py +++ /dev/null @@ -1,234 +0,0 @@ -from __future__ import ( - annotations, -) - -import warnings -from collections import ( - defaultdict, -) -from collections.abc import ( - Callable, - Iterable, -) -from itertools import ( - chain, -) -from typing import ( - Any, - Optional, - Union, -) -from urllib.parse import ( - parse_qsl, -) -from uuid import ( - UUID, -) - -from aiohttp.web import Request as AioHttpRequest -from cached_property import ( - cached_property, -) - -from minos.common import ( - AvroDataDecoder, - AvroSchemaDecoder, - MinosAvroProtocol, - import_module, -) -from minos.networks import ( - NotHasParamsException, - Request, -) - - -class RestRequest(Request): - """Rest Request class.""" - - __slots__ = "raw" - - def __init__(self, raw: AioHttpRequest, *args, **kwargs): - super().__init__(*args, **kwargs) - self.raw = raw - - @property - def raw_request(self) -> AioHttpRequest: - """Get the raw request within the instance. - - :return: An ``aiohttp.web.Request`` instance. - """ - warnings.warn( - f"'{RestRequest.__name__}s.raw_request' is deprecated in favor of '{RestRequest.__name__}.raw'.", - DeprecationWarning, - ) - return self.raw - - def __eq__(self, other: RestRequest) -> bool: - return type(self) == type(other) and self.raw == other.raw - - def __repr__(self) -> str: - return f"{type(self).__name__}({self.raw!r})" - - @cached_property - def user(self) -> Optional[UUID]: - """ - Returns the UUID of the user making the Request. - """ - if "user" not in self.headers: - return None - return UUID(self.headers["user"]) - - @property - def headers(self) -> dict[str, str]: - """Get the headers of the request. - - :return: A dictionary in which keys are ``str`` instances and values are ``str`` instances. - """ - # noinspection PyTypeChecker - return self.raw.headers - - @property - def has_content(self) -> bool: - """Check if the request has content. - - :return: ``True`` if it has content or ``False`` otherwise. - """ - return self.raw.body_exists - - async def _content(self, type_: Optional[Union[type, str]] = None, **kwargs) -> Any: - if "model_type" in kwargs: - warnings.warn("The 'model_type' argument is deprecated. Use 'type_' instead", DeprecationWarning) - if type_ is None: - type_ = kwargs["model_type"] - - data = await self._content_parser() - return self._build(data, type_) - - @cached_property - def _content_parser(self) -> Callable: - mapper = { - "application/json": self._raw_json, - "application/x-www-form-urlencoded": self._raw_form, - "avro/binary": self._raw_avro, - "text/plain": self._raw_text, - "application/octet-stream": self._raw_bytes, - } - - if self.content_type not in mapper: - raise ValueError( - f"The given Content-Type ({self.content_type!r}) is not supported for automatic content parsing yet." - ) - - return mapper[self.content_type] - - async def _raw_json(self) -> Any: - return await self.raw.json() - - async def _raw_form(self) -> dict[str, Any]: - form = await self.raw.json(loads=parse_qsl) - return self._parse_multi_dict(form) - - async def _raw_avro(self) -> Any: - data = MinosAvroProtocol.decode(await self._raw_bytes()) - schema = MinosAvroProtocol.decode_schema(await self._raw_bytes()) - - type_ = AvroSchemaDecoder(schema).build() - return AvroDataDecoder(type_).build(data) - - async def _raw_text(self) -> str: - return await self.raw.text() - - async def _raw_bytes(self) -> bytes: - return await self.raw.read() - - @property - def content_type(self) -> str: - """Get the content type. - - :return: A ``str`` value. - """ - return self.raw.content_type - - @property - def has_params(self) -> bool: - """Check if the request has params. - - :return: ``True`` if it has params or ``False`` otherwise. - """ - sentinel = object() - return next(chain(self._raw_url_params, self._raw_query_params), sentinel) is not sentinel - - async def _params(self, type_: Optional[Union[type, str]] = None, **kwargs) -> dict[str, Any]: - data = self._parse_multi_dict(chain(self._raw_url_params, self._raw_query_params)) - return self._build(data, type_) - - async def url_params(self, type_: Optional[Union[type, str]] = None, **kwargs) -> Any: - """Get the url params. - - :param type_: Optional ``type`` or ``str`` (classname) that defines the request content type. - :param kwargs: Additional named arguments. - :return: A dictionary instance. - """ - if not self.has_url_params: - raise NotHasParamsException(f"{self!r} has not url params.") - - data = self._parse_multi_dict(self._raw_url_params) - return self._build(data, type_) - - @property - def has_url_params(self) -> bool: - """Check if the request has url params. - - :return: ``True`` if it has url params or ``False`` otherwise. - """ - sentinel = object() - return next(iter(self._raw_url_params), sentinel) is not sentinel - - @property - def _raw_url_params(self): - return self.raw.rel_url.query.items() # pragma: no cover - - async def query_params(self, type_: Optional[Union[type, str]] = None, **kwargs) -> Any: - """Get the query params. - - - :param type_: Optional ``type`` or ``str`` (classname) that defines the request content type. - :param kwargs: Additional named arguments. - :return: A dictionary instance. - """ - if not self.has_query_params: - raise NotHasParamsException(f"{self!r} has not query params.") - - data = self._parse_multi_dict(self._raw_query_params) - return self._build(data, type_) - - @property - def has_query_params(self) -> bool: - # noinspection GrazieInspection - """Check if the request has query params. - - :return: ``True`` if it has query params or ``False`` otherwise. - """ - sentinel = object() - return next(iter(self._raw_query_params), sentinel) is not sentinel - - @property - def _raw_query_params(self): - return self.raw.match_info.items() # pragma: no cover - - @staticmethod - def _build(data: Any, type_: Union[type, str]) -> Any: - if type_ is None: - return data - - if isinstance(type_, str): - type_ = import_module(type_) - - return AvroDataDecoder(type_).build(data) - - @staticmethod - def _parse_multi_dict(raw: Iterable[str, Any]) -> dict[str, Any]: - args = defaultdict(list) - for k, v in raw: - args[k].append(v) - return {k: v if len(v) > 1 else v[0] for k, v in args.items()} diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/responses.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/responses.py deleted file mode 100644 index ca082d103..000000000 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/responses.py +++ /dev/null @@ -1,117 +0,0 @@ -from __future__ import ( - annotations, -) - -from collections.abc import ( - Callable, -) -from typing import ( - Any, - Awaitable, - Optional, -) -from urllib.parse import ( - urlencode, -) - -from cached_property import ( - cached_property, -) -from orjson import ( - orjson, -) - -from minos.common import ( - AvroDataEncoder, - AvroSchemaEncoder, - MinosAvroProtocol, - TypeHintBuilder, -) -from minos.networks import ( - Response, -) - - -class RestResponse(Response): - """Rest Response class.""" - - def __init__(self, *args, content_type: str = "application/json", **kwargs): - super().__init__(*args, **kwargs) - self.content_type = content_type - - @classmethod - def from_response(cls, response: Optional[Response]) -> RestResponse: - """Build a new ``RestRequest`` from another response. - - :param response: The base response. - :return: A ``RestResponse`` instance. - """ - if isinstance(response, RestResponse): - return response - - if response is None: - return RestResponse() - - return RestResponse(response._data) - - # noinspection PyUnusedLocal - async def content(self, **kwargs) -> Optional[bytes]: - """Raw response content. - - :param kwargs: Additional named arguments. - :return: The raw content as a ``bytes`` instance. - """ - if not self.has_content: - return None - return await self._content_parser() - - @cached_property - def _content_parser(self) -> Callable[[], Awaitable[bytes]]: - mapper = { - "application/json": self._raw_json, - "application/x-www-form-urlencoded": self._raw_form, - "avro/binary": self._raw_avro, - "text/plain": self._raw_text, - "application/octet-stream": self._raw_bytes, - } - - if self.content_type not in mapper: - return self._raw_bytes - - return mapper[self.content_type] - - async def _raw_json(self) -> bytes: - return orjson.dumps(self._raw_data) - - async def _raw_form(self) -> bytes: - return urlencode(self._raw_data).encode() - - async def _raw_avro(self) -> bytes: - type_ = TypeHintBuilder(self._data).build() - schema = AvroSchemaEncoder(type_).build() - data = AvroDataEncoder(self._data).build() - - return MinosAvroProtocol.encode(data, schema) - - async def _raw_text(self) -> bytes: - if not isinstance(self._raw_data, str): - raise ValueError( - f"Given 'Content-Type' ({self.content_type!r}) is not supported for the given data: {self._raw_data!r}." - ) - return self._raw_data.encode() - - async def _raw_bytes(self) -> bytes: - if not isinstance(self._raw_data, bytes): - raise ValueError( - f"Given 'Content-Type' ({self.content_type!r}) is not supported for the given data: {self._raw_data!r}." - ) - return self._raw_data - - # noinspection PyUnusedLocal - @cached_property - def _raw_data(self) -> Any: - """Raw response content. - - :return: A list of raw items. - """ - return AvroDataEncoder(self._data).build() diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schema.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schema.py deleted file mode 100644 index 9d1ee5a0e..000000000 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schema.py +++ /dev/null @@ -1,10 +0,0 @@ -from schemas.schema_hello import Query - - -class AllQuery( - Query, - # Schema1, - # Schema2, - # Schema3 -): - '''AllQuery''' diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schemas/schema_hello.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schemas/schema_hello.py deleted file mode 100644 index b29703d77..000000000 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/schemas/schema_hello.py +++ /dev/null @@ -1,16 +0,0 @@ -from graphene import ObjectType, String, Schema - - -class Query(ObjectType): - - hello = String(name=String(default_value="stranger")) - goodbye = String() - - def resolve_hello(root, info, name): - return f'Hello {name}!' - - def resolve_goodbye(root, info): - return 'See ya!' - - -schema = Schema(query=Query) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/__init__.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/__init__.py new file mode 100644 index 000000000..d06b7603f --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/__init__.py @@ -0,0 +1,8 @@ +__author__ = "Minos Framework Devs" +__email__ = "hey@minos.run" +__version__ = "0.6.0" + +from .schema import ( + star_wars_schema, +) + diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/data.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/data.py new file mode 100644 index 000000000..167d2a326 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/data.py @@ -0,0 +1,147 @@ +"""This defines a basic set of data for our Star Wars Schema. + +This data is hard coded for the sake of the demo, but you could imagine fetching this +data from a backend service rather than from hardcoded JSON objects in a more complex +demo. +""" + +from typing import Awaitable, Collection, Dict, Iterator, Optional + +__all__ = ["get_droid", "get_friends", "get_hero", "get_human", "get_secret_backstory"] + +# These are classes which correspond to the schema. +# They represent the shape of the data visited during field resolution. + + +class Character: + id: str + name: str + friends: Collection[str] + appearsIn: Collection[str] + + +# noinspection PyPep8Naming +class Human(Character): + type = "Human" + homePlanet: str + + # noinspection PyShadowingBuiltins + def __init__(self, id, name, friends, appearsIn, homePlanet): + self.id, self.name = id, name + self.friends, self.appearsIn = friends, appearsIn + self.homePlanet = homePlanet + + +# noinspection PyPep8Naming +class Droid(Character): + type = "Droid" + primaryFunction: str + + # noinspection PyShadowingBuiltins + def __init__(self, id, name, friends, appearsIn, primaryFunction): + self.id, self.name = id, name + self.friends, self.appearsIn = friends, appearsIn + self.primaryFunction = primaryFunction + + +luke = Human( + id="1000", + name="Luke Skywalker", + friends=["1002", "1003", "2000", "2001"], + appearsIn=[4, 5, 6], + homePlanet="Tatooine", +) + +vader = Human( + id="1001", + name="Darth Vader", + friends=["1004"], + appearsIn=[4, 5, 6], + homePlanet="Tatooine", +) + +han = Human( + id="1002", + name="Han Solo", + friends=["1000", "1003", "2001"], + appearsIn=[4, 5, 6], + homePlanet=None, +) + +leia = Human( + id="1003", + name="Leia Organa", + friends=["1000", "1002", "2000", "2001"], + appearsIn=[4, 5, 6], + homePlanet="Alderaan", +) + +tarkin = Human( + id="1004", name="Wilhuff Tarkin", friends=["1001"], appearsIn=[4], homePlanet=None +) + +human_data: Dict[str, Human] = { + "1000": luke, + "1001": vader, + "1002": han, + "1003": leia, + "1004": tarkin, +} + +threepio = Droid( + id="2000", + name="C-3PO", + friends=["1000", "1002", "1003", "2001"], + appearsIn=[4, 5, 6], + primaryFunction="Protocol", +) + +artoo = Droid( + id="2001", + name="R2-D2", + friends=["1000", "1002", "1003"], + appearsIn=[4, 5, 6], + primaryFunction="Astromech", +) + +droid_data: Dict[str, Droid] = {"2000": threepio, "2001": artoo} + + +# noinspection PyShadowingBuiltins +async def get_character(id: str) -> Optional[Character]: + """Helper function to get a character by ID.""" + # We use an async function just to illustrate that GraphQL-core supports it. + return human_data.get(id) or droid_data.get(id) + + +def get_friends(character: Character) -> Iterator[Awaitable[Optional[Character]]]: + """Allows us to query for a character's friends.""" + # Notice that GraphQL-core accepts iterators of awaitables. + return map(get_character, character.friends) + + +def get_hero(episode: int) -> Character: + """Allows us to fetch the undisputed hero of the trilogy, R2-D2.""" + if episode == 5: + # Luke is the hero of Episode V. + return luke + # Artoo is the hero otherwise. + return artoo + + +# noinspection PyShadowingBuiltins +def get_human(id: str) -> Optional[Human]: + """Allows us to query for the human with the given id.""" + return human_data.get(id) + + +# noinspection PyShadowingBuiltins +def get_droid(id: str) -> Optional[Droid]: + """Allows us to query for the droid with the given id.""" + return droid_data.get(id) + + +# noinspection PyUnusedLocal +def get_secret_backstory(character: Character) -> str: + """Raise an error when attempting to get the secret backstory.""" + raise RuntimeError("secretBackstory is secret.") diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/schema.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/schema.py new file mode 100644 index 000000000..5c6adc946 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/schema.py @@ -0,0 +1,274 @@ +"""Star Wars GraphQL schema + +This is designed to be an end-to-end test, demonstrating the full GraphQL stack. + +We will create a GraphQL schema that describes the major characters in the original +Star Wars trilogy. + +NOTE: This may contain spoilers for the original Star Wars trilogy. + +Using our shorthand to describe type systems, the type system for our Star Wars example +is:: + + enum Episode { NEW_HOPE, EMPIRE, JEDI } + + interface Character { + id: String! + name: String + friends: [Character] + appearsIn: [Episode] + } + + type Human implements Character { + id: String! + name: String + friends: [Character] + appearsIn: [Episode] + homePlanet: String + } + + type Droid implements Character { + id: String! + name: String + friends: [Character] + appearsIn: [Episode] + primaryFunction: String + } + + type Query { + hero(episode: Episode): Character + human(id: String!): Human + droid(id: String!): Droid + } +""" +import asyncio + +from graphql.type import ( + GraphQLArgument, + GraphQLEnumType, + GraphQLEnumValue, + GraphQLField, + GraphQLInterfaceType, + GraphQLList, + GraphQLNonNull, + GraphQLObjectType, + GraphQLSchema, + GraphQLString, +) + +from .data import ( + get_droid, + get_friends, + get_hero, + get_human, + get_secret_backstory, +) + +__all__ = ["star_wars_schema"] + +# We begin by setting up our schema. + +# The original trilogy consists of three movies. +# +# This implements the following type system shorthand: +# enum Episode { NEW_HOPE, EMPIRE, JEDI } + +episode_enum = GraphQLEnumType( + "Episode", + { + "NEW_HOPE": GraphQLEnumValue(4, description="Released in 1977."), + "EMPIRE": GraphQLEnumValue(5, description="Released in 1980."), + "JEDI": GraphQLEnumValue(6, description="Released in 1983."), + }, + description="One of the films in the Star Wars Trilogy", +) + +# Characters in the Star Wars trilogy are either humans or droids. +# +# This implements the following type system shorthand: +# interface Character { +# id: String! +# name: String +# friends: [Character] +# appearsIn: [Episode] +# secretBackstory: String + +human_type: GraphQLObjectType +droid_type: GraphQLObjectType + +character_interface: GraphQLInterfaceType = GraphQLInterfaceType( + "Character", + lambda: { + "id": GraphQLField( + GraphQLNonNull(GraphQLString), description="The id of the character." + ), + "name": GraphQLField(GraphQLString, description="The name of the character."), + "friends": GraphQLField( + GraphQLList(character_interface), + description="The friends of the character," + " or an empty list if they have none.", + ), + "appearsIn": GraphQLField( + GraphQLList(episode_enum), description="Which movies they appear in." + ), + "secretBackstory": GraphQLField( + GraphQLString, description="All secrets about their past." + ), + }, + resolve_type=lambda character, _info, _type: { + "Human": human_type.name, + "Droid": droid_type.name, + }[character.type], + description="A character in the Star Wars Trilogy", +) + +# We define our human type, which implements the character interface. +# +# This implements the following type system shorthand: +# type Human : Character { +# id: String! +# name: String +# friends: [Character] +# appearsIn: [Episode] +# secretBackstory: String +# } + +human_type = GraphQLObjectType( + "Human", + lambda: { + "id": GraphQLField( + GraphQLNonNull(GraphQLString), description="The id of the human." + ), + "name": GraphQLField(GraphQLString, description="The name of the human."), + "friends": GraphQLField( + GraphQLList(character_interface), + description="The friends of the human," + " or an empty list if they have none.", + resolve=lambda human, _info: get_friends(human), + ), + "appearsIn": GraphQLField( + GraphQLList(episode_enum), description="Which movies they appear in." + ), + "homePlanet": GraphQLField( + GraphQLString, + description="The home planet of the human, or null if unknown.", + ), + "secretBackstory": GraphQLField( + GraphQLString, + resolve=lambda human, _info: get_secret_backstory(human), + description="Where are they from and how they came to be who they are.", + ), + }, + interfaces=[character_interface], + description="A humanoid creature in the Star Wars universe.", +) + +# The other type of character in Star Wars is a droid. +# +# This implements the following type system shorthand: +# type Droid : Character { +# id: String! +# name: String +# friends: [Character] +# appearsIn: [Episode] +# secretBackstory: String +# primaryFunction: String +# } + +droid_type = GraphQLObjectType( + "Droid", + lambda: { + "id": GraphQLField( + GraphQLNonNull(GraphQLString), description="The id of the droid." + ), + "name": GraphQLField(GraphQLString, description="The name of the droid."), + "friends": GraphQLField( + GraphQLList(character_interface), + description="The friends of the droid," + " or an empty list if they have none.", + resolve=lambda droid, _info: get_friends(droid), + ), + "appearsIn": GraphQLField( + GraphQLList(episode_enum), description="Which movies they appear in." + ), + "secretBackstory": GraphQLField( + GraphQLString, + resolve=lambda droid, _info: get_secret_backstory(droid), + description="Construction date and the name of the designer.", + ), + "primaryFunction": GraphQLField( + GraphQLString, description="The primary function of the droid." + ), + }, + interfaces=[character_interface], + description="A mechanical creature in the Star Wars universe.", +) + +# This is the type that will be the root of our query, and the +# entry point into our schema. It gives us the ability to fetch +# objects by their IDs, as well as to fetch the undisputed hero +# of the Star Wars trilogy, R2-D2, directly. +# +# This implements the following type system shorthand: +# type Query { +# hero(episode: Episode): Character +# human(id: String!): Human +# droid(id: String!): Droid +# } + +# noinspection PyShadowingBuiltins +query_type = GraphQLObjectType( + "Query", + lambda: { + "hero": GraphQLField( + character_interface, + args={ + "episode": GraphQLArgument( + episode_enum, + description=( + "If omitted, returns the hero of the whole saga." + " If provided, returns the hero of that particular episode." + ), + ) + }, + resolve=lambda _source, _info, episode=None: get_hero(episode), + ), + "human": GraphQLField( + human_type, + args={ + "id": GraphQLArgument( + GraphQLNonNull(GraphQLString), description="id of the human" + ) + }, + resolve=lambda _source, _info, id: get_human(id), + ), + "droid": GraphQLField( + droid_type, + args={ + "id": GraphQLArgument( + GraphQLNonNull(GraphQLString), description="id of the droid" + ) + }, + resolve=lambda _source, _info, id: get_droid(id), + ), + }, +) + + +async def resolve_hello(obj, info): + await asyncio.sleep(3) + return 'world' + +mutation_type = GraphQLObjectType( + "Mutation", + fields={ + 'hello': GraphQLField( + GraphQLString, + resolve=resolve_hello) + } +) + +# Finally, we construct our schema (whose starting query type is the query +# type we defined above) and export it. + +star_wars_schema = GraphQLSchema(query=query_type, mutation=mutation_type, types=[human_type, droid_type]) diff --git a/packages/plugins/minos-graphql-aiohttp/poetry.lock b/packages/plugins/minos-graphql-aiohttp/poetry.lock index 231a93c28..d17b27ac0 100644 --- a/packages/plugins/minos-graphql-aiohttp/poetry.lock +++ b/packages/plugins/minos-graphql-aiohttp/poetry.lock @@ -85,17 +85,6 @@ category = "dev" optional = false python-versions = "*" -[[package]] -name = "aniso8601" -version = "9.0.1" -description = "A library for parsing ISO 8601 strings." -category = "main" -optional = false -python-versions = "*" - -[package.extras] -dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"] - [[package]] name = "async-timeout" version = "4.0.2" @@ -326,42 +315,14 @@ category = "main" optional = false python-versions = ">=3.7" -[[package]] -name = "graphene" -version = "3.0" -description = "GraphQL Framework for Python" -category = "main" -optional = false -python-versions = "*" - -[package.dependencies] -aniso8601 = ">=8,<10" -graphql-core = ">=3.1.2,<3.2.0" -graphql-relay = ">=3.0,<4" - -[package.extras] -dev = ["black (==19.10b0)", "flake8 (>=3.7,<4)", "pytest (>=5.3,<6)", "pytest-benchmark (>=3.2,<4)", "pytest-cov (>=2.8,<3)", "pytest-mock (>=2,<3)", "pytest-asyncio (>=0.10,<2)", "snapshottest (>=0.5,<1)", "coveralls (>=1.11,<2)", "promise (>=2.3,<3)", "mock (>=4.0,<5)", "pytz (==2021.1)", "iso8601 (>=0.1,<2)"] -test = ["pytest (>=5.3,<6)", "pytest-benchmark (>=3.2,<4)", "pytest-cov (>=2.8,<3)", "pytest-mock (>=2,<3)", "pytest-asyncio (>=0.10,<2)", "snapshottest (>=0.5,<1)", "coveralls (>=1.11,<2)", "promise (>=2.3,<3)", "mock (>=4.0,<5)", "pytz (==2021.1)", "iso8601 (>=0.1,<2)"] - [[package]] name = "graphql-core" -version = "3.1.7" +version = "3.2.0" description = "GraphQL implementation for Python, a port of GraphQL.js, the JavaScript reference implementation for GraphQL." category = "main" optional = false python-versions = ">=3.6,<4" -[[package]] -name = "graphql-relay" -version = "3.1.5" -description = "Relay library for graphql-core" -category = "main" -optional = false -python-versions = ">=3.6,<4" - -[package.dependencies] -graphql-core = ">=3.1,<3.2" - [[package]] name = "identify" version = "2.4.11" @@ -978,7 +939,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "bdba872b9f221db81478baf76a21d4d23e88691a09a3aab49967bbceb6e42685" +content-hash = "e7b55f719918838ac8d73e1ee1cf1df328f9c6d62e7bb835c897f447c755a214" [metadata.files] aiohttp = [ @@ -1075,10 +1036,6 @@ alabaster = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] -aniso8601 = [ - {file = "aniso8601-9.0.1-py2.py3-none-any.whl", hash = "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f"}, - {file = "aniso8601-9.0.1.tar.gz", hash = "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973"}, -] async-timeout = [ {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, @@ -1327,17 +1284,9 @@ frozenlist = [ {file = "frozenlist-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:772965f773757a6026dea111a15e6e2678fbd6216180f82a48a40b27de1ee2ab"}, {file = "frozenlist-1.3.0.tar.gz", hash = "sha256:ce6f2ba0edb7b0c1d8976565298ad2deba6f8064d2bebb6ffce2ca896eb35b0b"}, ] -graphene = [ - {file = "graphene-3.0-py2.py3-none-any.whl", hash = "sha256:57ce5ee7c9dc194224a1df96e4f7cb48d31eae96c791091d059f8f3d4d131390"}, - {file = "graphene-3.0.tar.gz", hash = "sha256:af48066e152200a071aac1e0b703954c717ec7268720ba190a0c91d9bcb0a122"}, -] graphql-core = [ - {file = "graphql-core-3.1.7.tar.gz", hash = "sha256:62ec192150ccecd9a18cfb79e3e72eb7d1fd68fb594ef19c40099b6deec8ef0c"}, - {file = "graphql_core-3.1.7-py3-none-any.whl", hash = "sha256:9b460f60320be01c7f3b1766cf3e406933003008055079b9d983b8f3988f4400"}, -] -graphql-relay = [ - {file = "graphql-relay-3.1.5.tar.gz", hash = "sha256:127f40913f11cb847452ef794931261aae3b222eaaf976f7c8430298534e54d3"}, - {file = "graphql_relay-3.1.5-py3-none-any.whl", hash = "sha256:d1455316182e1017d06f0b4d4a2ce8bafad38253a6c7c5666fc80d5d516ee5c8"}, + {file = "graphql-core-3.2.0.tar.gz", hash = "sha256:86e2a0be008bfde19ef78388de8a725a1d942a9190ca431c24a60837973803ce"}, + {file = "graphql_core-3.2.0-py3-none-any.whl", hash = "sha256:0dda7e63676f119bb3d814621190fedad72fda07a8e9ab780bedd9f1957c6dc6"}, ] identify = [ {file = "identify-2.4.11-py2.py3-none-any.whl", hash = "sha256:fd906823ed1db23c7a48f9b176a1d71cb8abede1e21ebe614bac7bdd688d9213"}, diff --git a/packages/plugins/minos-graphql-aiohttp/pyproject.toml b/packages/plugins/minos-graphql-aiohttp/pyproject.toml index 9d5a2d17a..cce0f4fdf 100644 --- a/packages/plugins/minos-graphql-aiohttp/pyproject.toml +++ b/packages/plugins/minos-graphql-aiohttp/pyproject.toml @@ -35,9 +35,8 @@ minos-microservice-common = "^0.5.0" minos-microservice-networks = "^0.5.0" aiohttp = "^3.8.1" aiohttp-jinja2 = "^1.5" -async-timeout = "^4.0.2" -graphene = "^3.0" Jinja2 = "^3.0.3" +graphql-core = "^3.2.0" [tool.poetry.dev-dependencies] minos-microservice-common = { path = "../../core/minos-microservice-common", develop = true } diff --git a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_handlers.py b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_handlers.py deleted file mode 100644 index 2616a6cff..000000000 --- a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_handlers.py +++ /dev/null @@ -1,131 +0,0 @@ -import unittest -from unittest.mock import ( - AsyncMock, -) -from uuid import ( - uuid4, -) - -from aiohttp import ( - web, -) -from aiohttp.web_exceptions import ( - HTTPInternalServerError, -) -from orjson import ( - orjson, -) - -from minos.common.testing import ( - PostgresAsyncTestCase, -) -from minos.networks import ( - REQUEST_USER_CONTEXT_VAR, - Request, - Response, -) -from minos.plugins.graphql_aiohttp import ( - RestHandler, - RestResponse, - RestResponseException, -) -from tests.test_plugins.test_graphql_aiohttp.utils import ( - json_mocked_request, - mocked_request, -) -from tests.utils import ( - BASE_PATH, -) - -APPLICATION_JSON = "application/json" - - -class _Cls: - @staticmethod - async def _fn(request: Request) -> Response: - return RestResponse(await request.content()) - - @staticmethod - async def _fn_status(request: Request) -> Response: - return RestResponse(status=await request.content()) - - @staticmethod - async def _fn_none(request: Request): - return - - @staticmethod - async def _fn_raises_response(request: Request) -> Response: - raise RestResponseException("") - - @staticmethod - async def _fn_raises_exception(request: Request) -> Response: - raise ValueError - - -class TestRestHandler(PostgresAsyncTestCase): - CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" - - def setUp(self) -> None: - super().setUp() - self.handler = RestHandler.from_config(config=self.config) - - def test_from_config(self): - self.assertIsInstance(self.handler, RestHandler) - self.assertEqual({("/order", "GET"), ("/ticket", "POST")}, set(self.handler.endpoints.keys())) - - def test_from_config_raises(self): - with self.assertRaises(Exception): - RestHandler.from_config() - - def test_get_app(self): - self.assertIsInstance(self.handler.get_app(), web.Application) - - async def test_get_callback(self): - handler = self.handler.get_callback(_Cls._fn) - response = await handler(json_mocked_request({"foo": "bar"})) - self.assertIsInstance(response, web.Response) - self.assertEqual(orjson.dumps({"foo": "bar"}), response.body) - self.assertEqual(APPLICATION_JSON, response.content_type) - - async def test_get_callback_status(self): - handler = self.handler.get_callback(_Cls._fn_status) - response = await handler(json_mocked_request(203)) - self.assertIsInstance(response, web.Response) - self.assertEqual(None, response.body) - self.assertEqual(APPLICATION_JSON, response.content_type) - self.assertEqual(203, response.status) - - async def test_get_callback_none(self): - handler = self.handler.get_callback(_Cls._fn_none) - response = await handler(mocked_request()) - self.assertIsInstance(response, web.Response) - self.assertEqual(None, response.text) - self.assertEqual(APPLICATION_JSON, response.content_type) - - async def test_get_callback_raises_response(self): - handler = self.handler.get_callback(_Cls._fn_raises_response) - response = await handler(json_mocked_request({"foo": "bar"})) - self.assertEqual(400, response.status) - - async def test_get_callback_raises_exception(self): - handler = self.handler.get_callback(_Cls._fn_raises_exception) - with self.assertRaises(HTTPInternalServerError): - await handler(json_mocked_request({"foo": "bar"})) - - async def test_get_callback_with_user(self): - user = uuid4() - - async def _fn(request) -> None: - self.assertEqual(user, request.user) - self.assertEqual(user, REQUEST_USER_CONTEXT_VAR.get()) - - mock = AsyncMock(side_effect=_fn) - - handler = self.handler.get_callback(mock) - await handler(json_mocked_request({"foo": "bar"}, user=user)) - - self.assertEqual(1, mock.call_count) - - -if __name__ == "__main__": - unittest.main() diff --git a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_requests.py b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_requests.py deleted file mode 100644 index af0d8ec7f..000000000 --- a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_requests.py +++ /dev/null @@ -1,441 +0,0 @@ -import unittest -import warnings -from uuid import ( - uuid4, -) - -from orjson import ( - orjson, -) - -from minos.common import ( - MinosAvroProtocol, - ModelType, - classname, -) -from minos.networks import ( - NotHasContentException, - NotHasParamsException, - Response, -) -from minos.plugins.graphql_aiohttp import ( - RestRequest, - RestResponse, -) -from tests.test_plugins.test_graphql_aiohttp.utils import ( - avro_mocked_request, - bytes_mocked_request, - form_mocked_request, - json_mocked_request, - mocked_request, - text_mocked_request, -) -from tests.utils import ( - FakeModel, -) - -APPLICATION_JSON = "application/json" -APPLICATION_URLENCODED = "application/x-www-form-urlencoded" -APPLICATION_OCTET_STREAM = "application/octet-stream" -TEXT_PLAIN = "text/plain" -AVRO_BINARY = "avro/binary" -IMAGE_PNG = "image/png" - - -class TestRestRequest(unittest.IsolatedAsyncioTestCase): - def test_raw(self): - raw = mocked_request() - request = RestRequest(raw) - self.assertEqual(raw, request.raw) - - def test_raw_request(self): - raw = mocked_request() - request = RestRequest(raw) - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - # noinspection PyDeprecation - self.assertEqual(request.raw, request.raw_request) - - def test_repr(self): - raw = mocked_request() - request = RestRequest(raw) - self.assertEqual(f"RestRequest({raw!r})", repr(request)) - - def test_eq_true(self): - request = mocked_request() - self.assertEqual(RestRequest(request), RestRequest(request)) - - def test_eq_false(self): - self.assertNotEqual(RestRequest(mocked_request()), RestRequest(mocked_request())) - - def test_headers(self): - raw = mocked_request(headers={"something": "123"}) - request = RestRequest(raw) - self.assertEqual({"something": "123"}, request.headers) - - def test_user(self): - uuid = uuid4() - raw = mocked_request(user=uuid) - request = RestRequest(raw) - user = request.user - self.assertEqual(uuid, user) - - def test_user_unset(self): - raw = mocked_request() - request = RestRequest(raw) - self.assertEqual(None, request.user) - - -class TestRestRequestContent(unittest.IsolatedAsyncioTestCase): - def test_has_content_false(self): - raw = mocked_request() - request = RestRequest(raw) - self.assertEqual(False, request.has_content) - - def test_has_content_true(self): - raw = json_mocked_request(123) - request = RestRequest(raw) - self.assertEqual(True, request.has_content) - - async def test_empty_raises(self): - raw = mocked_request() - request = RestRequest(raw) - with self.assertRaises(NotHasContentException): - await request.content() - - async def test_json_int(self): - expected = 56 - - raw = json_mocked_request(56) - request = RestRequest(raw) - observed = await request.content() - - self.assertEqual(expected, observed) - - async def test_json_list(self): - expected = [{"doors": 3, "color": "blue", "owner": None}, {"doors": 5, "color": "red", "owner": None}] - - raw = json_mocked_request( - [{"doors": 3, "color": "blue", "owner": None}, {"doors": 5, "color": "red", "owner": None}] - ) - request = RestRequest(raw) - observed = await request.content() - - self.assertEqual(expected, observed) - - async def test_json_dict(self): - expected = {"doors": 3, "color": "blue", "owner": None} - - raw = json_mocked_request({"doors": 3, "color": "blue", "owner": None}) - request = RestRequest(raw) - observed = await request.content() - - self.assertEqual(expected, observed) - - async def test_form(self): - expected = {"foo": "foo1", "bar": ["bar1", "bar2"]} - - raw = form_mocked_request({"foo": "foo1", "bar": ["bar1", "bar2"]}) - request = RestRequest(raw) - observed = await request.content() - - self.assertEqual(expected, observed) - - async def test_avro_int(self): - expected = 56 - - raw = avro_mocked_request(56, "int") - request = RestRequest(raw) - observed = await request.content() - - self.assertEqual(expected, observed) - - async def test_avro_uuid(self): - expected = uuid4() - - raw = avro_mocked_request(expected, {"type": "string", "logicalType": "uuid"}) - request = RestRequest(raw) - observed = await request.content() - - self.assertEqual(expected, observed) - - async def test_avro_dto(self): - expected = ModelType.build("FakeModel", {"text": str})("foobar") - - raw = avro_mocked_request( - {"text": "foobar"}, {"name": "FakeModel", "type": "record", "fields": [{"name": "text", "type": "string"}]} - ) - request = RestRequest(raw) - observed = await request.content() - - self.assertEqual(expected, observed) - - async def test_avro_model(self): - expected = FakeModel("foobar") - - raw = avro_mocked_request(expected.avro_data, expected.avro_schema) - request = RestRequest(raw) - observed = await request.content() - - self.assertEqual(expected, observed) - - async def test_text(self): - expected = "foobar" - - raw = text_mocked_request("foobar") - request = RestRequest(raw) - observed = await request.content() - - self.assertEqual(expected, observed) - - async def test_bytes(self): - expected = bytes("foobar", "utf-8") - - raw = bytes_mocked_request(bytes("foobar", "utf-8")) - request = RestRequest(raw) - observed = await request.content() - - self.assertEqual(expected, observed) - - async def test_raises(self): - raw = mocked_request(content_type="foo/bar", data="foobar".encode()) - request = RestRequest(raw) - with self.assertRaises(ValueError): - await request.content() - - async def test_with_type(self): - # noinspection PyPep8Naming - car = ModelType.build("Car", {"doors": int, "color": str, "owner": type(None)}) - expected = [car(3, "blue", None), car(5, "red", None)] - - raw = json_mocked_request([{"doors": "3", "color": "blue"}, {"doors": "5", "color": "red"}]) - request = RestRequest(raw) - observed = await request.content(type_=list[car]) - - self.assertEqual(expected, observed) - - async def test_with_type_classname(self): - expected = 3 - - raw = json_mocked_request("3") - request = RestRequest(raw) - observed = await request.content(type_=classname(int)) - - self.assertEqual(expected, observed) - - async def test_with_model_type(self): - expected = 3 - - raw = json_mocked_request("3") - request = RestRequest(raw) - with warnings.catch_warnings(): - warnings.simplefilter("ignore", DeprecationWarning) - observed = await request.content(model_type=classname(int)) - - self.assertEqual(expected, observed) - - -class TestRestRequestParams(unittest.IsolatedAsyncioTestCase): - def test_has_url_params_false(self): - raw = mocked_request() - request = RestRequest(raw) - self.assertEqual(False, request.has_url_params) - - def test_has_url_params_true(self): - raw = mocked_request(url_params=[("foo", "1"), ("bar", "2"), ("foo", "3")]) - request = RestRequest(raw) - self.assertEqual(True, request.has_url_params) - - async def test_url_params(self): - expected = {"bar": "2", "foo": ["1", "3"]} - - raw = mocked_request(url_params=[("foo", "1"), ("bar", "2"), ("foo", "3")]) - request = RestRequest(raw) - observed = await request.url_params() - - self.assertEqual(expected, observed) - - async def test_url_params_with_type(self): - # noinspection PyPep8Naming - params = ModelType.build("Params", {"foo": list[int], "bar": int}) - expected = params(foo=[1, 3], bar=2) - - raw = mocked_request(url_params=[("foo", "1"), ("bar", "2"), ("foo", "3")]) - request = RestRequest(raw) - observed = await request.url_params(type_=params) - - self.assertEqual(expected, observed) - - async def test_url_params_raises(self): - raw = mocked_request() - request = RestRequest(raw) - with self.assertRaises(NotHasParamsException): - await request.url_params() - - def test_has_query_params_false(self): - raw = mocked_request() - request = RestRequest(raw) - self.assertEqual(False, request.has_query_params) - - def test_has_query_params_true(self): - raw = mocked_request(query_params=[("one", "1"), ("two", "2"), ("one", "3")]) - request = RestRequest(raw) - self.assertEqual(True, request.has_query_params) - - async def test_query_params(self): - expected = {"one": ["1", "3"], "two": "2"} - - raw = mocked_request(query_params=[("one", "1"), ("two", "2"), ("one", "3")]) - request = RestRequest(raw) - observed = await request.query_params() - - self.assertEqual(expected, observed) - - async def test_query_params_with_type(self): - # noinspection PyPep8Naming - params = ModelType.build("Params", {"one": list[int], "two": int}) - expected = params(one=[1, 3], two=2) - - raw = mocked_request(query_params=[("one", "1"), ("two", "2"), ("one", "3")]) - request = RestRequest(raw) - observed = await request.query_params(type_=params) - - self.assertEqual(expected, observed) - - async def test_query_params_raises(self): - raw = mocked_request() - request = RestRequest(raw) - with self.assertRaises(NotHasParamsException): - await request.query_params() - - def test_has_params_false(self): - raw = mocked_request() - request = RestRequest(raw) - self.assertEqual(False, request.has_params) - - def test_has_params_true(self): - raw = mocked_request(url_params=[("foo", "1"), ("bar", "2"), ("foo", "3")]) - request = RestRequest(raw) - self.assertEqual(True, request.has_params) - - raw = mocked_request(query_params=[("one", "1"), ("two", "2"), ("one", "3")]) - request = RestRequest(raw) - self.assertEqual(True, request.has_params) - - raw = mocked_request( - url_params=[("foo", "1"), ("bar", "2"), ("foo", "3")], - query_params=[("one", "1"), ("two", "2"), ("one", "3")], - ) - request = RestRequest(raw) - self.assertEqual(True, request.has_params) - - async def test_params(self): - expected = {"bar": "2", "foo": ["1", "3"], "one": ["1", "3"], "two": "2"} - - raw = mocked_request( - url_params=[("foo", "1"), ("bar", "2"), ("foo", "3")], - query_params=[("one", "1"), ("two", "2"), ("one", "3")], - ) - request = RestRequest(raw) - observed = await request.params() - - self.assertEqual(expected, observed) - - async def test_params_with_type(self): - # noinspection PyPep8Naming - params = ModelType.build("Params", {"foo": list[int], "bar": int, "one": list[int], "two": int}) - expected = params(foo=[1, 3], bar=2, one=[1, 3], two=2) - - raw = mocked_request( - url_params=[("foo", "1"), ("bar", "2"), ("foo", "3")], - query_params=[("one", "1"), ("two", "2"), ("one", "3")], - ) - request = RestRequest(raw) - observed = await request.params(type_=params) - - self.assertEqual(expected, observed) - - async def test_params_raises(self): - raw = mocked_request() - request = RestRequest(raw) - with self.assertRaises(NotHasParamsException): - await request.params() - - -class TestRestResponse(unittest.IsolatedAsyncioTestCase): - async def test_from_response(self): - base = Response("foobar") - - response = RestResponse.from_response(base) - - self.assertEqual(orjson.dumps("foobar"), await response.content()) - self.assertEqual(APPLICATION_JSON, response.content_type) - - def test_from_response_already(self): - base = RestResponse() - - response = RestResponse.from_response(base) - - self.assertEqual(base, response) - - def test_from_response_empty(self): - response = RestResponse.from_response(None) - - self.assertEqual(RestResponse(), response) - - async def test_empty(self): - response = RestResponse() - self.assertEqual(None, await response.content()) - self.assertEqual(APPLICATION_JSON, response.content_type) - - async def test_content_json(self): - data = [FakeModel("foo"), FakeModel("bar")] - response = RestResponse(data) - self.assertEqual(orjson.dumps([item.avro_data for item in data]), await response.content()) - self.assertEqual(APPLICATION_JSON, response.content_type) - - async def test_content_form(self): - data = {"foo": "bar", "one": "two"} - response = RestResponse(data, content_type="application/x-www-form-urlencoded") - self.assertEqual("foo=bar&one=two".encode(), await response.content()) - self.assertEqual(APPLICATION_URLENCODED, response.content_type) - - async def test_content_bytes(self): - data = "foobar".encode() - response = RestResponse(data, content_type=APPLICATION_OCTET_STREAM) - self.assertEqual("foobar".encode(), await response.content()) - self.assertEqual(APPLICATION_OCTET_STREAM, response.content_type) - - async def test_content_bytes_raises(self): - data = 56 - response = RestResponse(data, content_type=APPLICATION_OCTET_STREAM) - with self.assertRaises(ValueError): - await response.content() - - async def test_content_text(self): - data = "foobar" - response = RestResponse(data, content_type=TEXT_PLAIN) - self.assertEqual("foobar".encode(), await response.content()) - self.assertEqual(TEXT_PLAIN, response.content_type) - - async def test_content_text_raises(self): - data = 56 - response = RestResponse(data, content_type=TEXT_PLAIN) - with self.assertRaises(ValueError): - await response.content() - - async def test_content_avro(self): - data = "foobar" - response = RestResponse(data, content_type=AVRO_BINARY) - self.assertEqual(data, MinosAvroProtocol.decode(await response.content())) - self.assertEqual(AVRO_BINARY, response.content_type) - - async def test_content_image(self): - data = bytes("image", "utf-8") - response = RestResponse(data, content_type=IMAGE_PNG) - self.assertEqual(data, await response.content()) - self.assertEqual(IMAGE_PNG, response.content_type) - - -if __name__ == "__main__": - unittest.main() diff --git a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_services.py b/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_services.py deleted file mode 100644 index 640f76fc4..000000000 --- a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/test_services.py +++ /dev/null @@ -1,48 +0,0 @@ -import unittest - -from aiohttp.test_utils import ( - AioHTTPTestCase, -) - -from minos.common import ( - MinosConfig, -) -from minos.plugins.graphql_aiohttp import ( - RestService, -) -from tests.utils import ( - BASE_PATH, -) - - -class TestRestService(AioHTTPTestCase): - CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" - - async def get_application(self): - """ - Override the get_app method to return your application. - """ - config = MinosConfig(self.CONFIG_FILE_PATH) - rest_interface = RestService(config=config) - - return await rest_interface.create_application() - - async def test_methods(self): - url = "/order" - resp = await self.client.request("GET", url) - assert resp.status == 200 - text = await resp.text() - assert "get_order" in text - - url = "/ticket" - resp = await self.client.request("POST", url) - assert resp.status == 200 - text = await resp.text() - assert "ticket_added" in text - - resp = await self.client.request("GET", "/system/health") - assert resp.status == 200 - - -if __name__ == "__main__": - unittest.main() From f35543e0fc96240af1883c8e04114cfcfd79539b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Fri, 11 Mar 2022 12:23:37 +0100 Subject: [PATCH 10/44] ISSUE #149 * Rename `graphql_aiohttp` as `graphql`. * Add `GraphQlEnrouteDecorator` and `GraphQlHttpRouter`. --- .../minos/plugins/graphql/__init__.py | 11 +++ .../minos/plugins/graphql/decorators.py | 50 ++++++++++++ .../exceptions.py | 0 .../handlers/__init__.py | 6 +- .../handlers/abc.py | 57 +++++++------ .../plugins/graphql/handlers/graphiql.py | 11 +++ .../handlers/graphql.py | 9 ++- .../{graphql_aiohttp => graphql}/main.py | 0 .../minos/plugins/graphql/routers.py | 81 +++++++++++++++++++ .../{graphql_aiohttp => graphql}/services.py | 12 +-- .../star_wars_example/__init__.py | 1 - .../star_wars_example/data.py | 12 ++- .../star_wars_example/schema.py | 65 ++++----------- .../templates/graphiql.html | 0 .../minos/plugins/graphql_aiohttp/__init__.py | 10 --- .../graphql_aiohttp/handlers/graphiql.py | 8 -- 16 files changed, 225 insertions(+), 108 deletions(-) create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/__init__.py create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/decorators.py rename packages/plugins/minos-graphql-aiohttp/minos/plugins/{graphql_aiohttp => graphql}/exceptions.py (100%) rename packages/plugins/minos-graphql-aiohttp/minos/plugins/{graphql_aiohttp => graphql}/handlers/__init__.py (73%) rename packages/plugins/minos-graphql-aiohttp/minos/plugins/{graphql_aiohttp => graphql}/handlers/abc.py (55%) create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/graphiql.py rename packages/plugins/minos-graphql-aiohttp/minos/plugins/{graphql_aiohttp => graphql}/handlers/graphql.py (80%) rename packages/plugins/minos-graphql-aiohttp/minos/plugins/{graphql_aiohttp => graphql}/main.py (100%) create mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/routers.py rename packages/plugins/minos-graphql-aiohttp/minos/plugins/{graphql_aiohttp => graphql}/services.py (88%) rename packages/plugins/minos-graphql-aiohttp/minos/plugins/{graphql_aiohttp => graphql}/star_wars_example/__init__.py (99%) rename packages/plugins/minos-graphql-aiohttp/minos/plugins/{graphql_aiohttp => graphql}/star_wars_example/data.py (95%) rename packages/plugins/minos-graphql-aiohttp/minos/plugins/{graphql_aiohttp => graphql}/star_wars_example/schema.py (77%) rename packages/plugins/minos-graphql-aiohttp/minos/plugins/{graphql_aiohttp => graphql}/templates/graphiql.html (100%) delete mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py delete mode 100644 packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphiql.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/__init__.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/__init__.py new file mode 100644 index 000000000..af4f2bf06 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/__init__.py @@ -0,0 +1,11 @@ +__author__ = "Minos Framework Devs" +__email__ = "hey@minos.run" +__version__ = "0.6.0" + +from .decorators import ( + GraphQlEnroute, +) + + +def _register_enroute(): + GraphQlEnroute.register() diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/decorators.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/decorators.py new file mode 100644 index 000000000..ff512ed4e --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/decorators.py @@ -0,0 +1,50 @@ +from abc import ( + ABC, +) +from collections.abc import ( + Iterable, +) +from typing import ( + Final, +) + +from minos.networks import ( + EnrouteDecorator, + EnrouteDecoratorKind, + enroute, +) + + +class GraphQlEnrouteDecorator(EnrouteDecorator, ABC): + """GraphQl Enroute class""" + + def __init__(self, topic: str): + self.topic = topic + + def __iter__(self) -> Iterable: + yield from (self.topic,) + + +class GraphQlCommandEnrouteDecorator(GraphQlEnrouteDecorator): + """GraphQl Command Enroute class""" + + KIND: Final[EnrouteDecoratorKind] = EnrouteDecoratorKind.Command + + +class GraphQlQueryEnrouteDecorator(GraphQlEnrouteDecorator): + """GraphQl Query Enroute class""" + + KIND: Final[EnrouteDecoratorKind] = EnrouteDecoratorKind.Query + + +class GraphQlEnroute: + """GraphQl Enroute class""" + + command = GraphQlCommandEnrouteDecorator + query = GraphQlQueryEnrouteDecorator + + @classmethod + def register(cls): + """TODO""" + # noinspection PyProtectedMember + enroute._register_sub_enroute("graphql", cls) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/exceptions.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/exceptions.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/exceptions.py rename to packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/exceptions.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/__init__.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/__init__.py similarity index 73% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/__init__.py rename to packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/__init__.py index 1e49dde44..6a3937e81 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/__init__.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/__init__.py @@ -1,6 +1,6 @@ +from .graphiql import ( + GraphiqlHandler, +) from .graphql import ( GraphqlHandler, ) -from .graphiql import ( - GraphiqlHandler, -) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/abc.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/abc.py similarity index 55% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/abc.py rename to packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/abc.py index 2b200ef00..18dd60455 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/abc.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/abc.py @@ -1,15 +1,21 @@ +import json import logging import urllib -import json + import aiohttp -from aiohttp import web -from graphql import graphql +from aiohttp import ( + web, +) +from graphql import ( + graphql, +) + from minos.plugins.graphql_aiohttp.star_wars_example import ( star_wars_schema, ) -class BaseHandler(web.View): +class BaseHandler(web.View): @property def fetch(self): return self.app.fetch @@ -22,9 +28,9 @@ async def options(self): return web.Response( status=204, headers={ - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH', - 'Access-Control-Allow-Headers': 'x-requested-with,access-control-allow-origin,authorization,content-type', + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH", + "Access-Control-Allow-Headers": "x-requested-with,access-control-allow-origin,authorization,content-type", }, ) @@ -40,7 +46,7 @@ async def handle_graqhql(self): status = 200 result = await self.execute_graphql() logging.debug( - 'GraphQL result data: %s errors: %s', + "GraphQL result data: %s errors: %s", result.data, result.errors, ) @@ -54,40 +60,41 @@ async def handle_graqhql(self): errors = [] return web.json_response( - {'data': result.data, 'errors': [err.message for err in errors]}, + {"data": result.data, "errors": [err.message for err in errors]}, status=status, - headers={'Access-Control-Allow-Origin': '*'}, + headers={"Access-Control-Allow-Origin": "*"}, ) async def execute_graphql(self): graphql_req = await self.graphql_request - context_value = graphql_req.get('context', {}) - variables = graphql_req.get('variables', {}) - - context_value['application'] = self.app - source = graphql_req['query'] - result = await graphql(schema=star_wars_schema, - source=source, - context_value=context_value, - variable_values=variables, - ) + context_value = graphql_req.get("context", {}) + variables = graphql_req.get("variables", {}) + + context_value["application"] = self.app + source = graphql_req["query"] + result = await graphql( + schema=star_wars_schema, + source=source, + context_value=context_value, + variable_values=variables, + ) - ref = self.request.headers.get('referer') - url_path = '' + ref = self.request.headers.get("referer") + url_path = "" if ref: url = urllib.parse.urlparse(ref) url_path = url.path if result.errors: - if '/graphiql' not in url_path: + if "/graphiql" not in url_path: aiohttp.log.server_logger.error(f'Graphql query error: for query "{graphql_req}"') return result @property async def graphql_request(self): - if self.request.method == 'GET': - return json.loads(self.request.query['q']) + if self.request.method == "GET": + return json.loads(self.request.query["q"]) return await self.request.json() diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/graphiql.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/graphiql.py new file mode 100644 index 000000000..3cf938e43 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/graphiql.py @@ -0,0 +1,11 @@ +import aiohttp_jinja2 + +from .abc import ( + BaseHandler, +) + + +class GraphiqlHandler(BaseHandler): + @aiohttp_jinja2.template("graphiql.html") + async def get(self): + return {"base_url": f"/graphql"} diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphql.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/graphql.py similarity index 80% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphql.py rename to packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/graphql.py index 78a51aa7c..2e747a72e 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphql.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/graphql.py @@ -1,14 +1,17 @@ from graphql.utilities import ( - print_schema + print_schema, ) -from .abc import GQLBaseHandler from minos.plugins.graphql_aiohttp.star_wars_example import ( star_wars_schema, ) -class GraphqlHandler(GQLBaseHandler): +from .abc import ( + GQLBaseHandler, +) + +class GraphqlHandler(GQLBaseHandler): @property def schema(self): return print_schema(star_wars_schema) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/main.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/main.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/main.py rename to packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/main.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/routers.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/routers.py new file mode 100644 index 000000000..4eee7eee7 --- /dev/null +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/routers.py @@ -0,0 +1,81 @@ +from __future__ import ( + annotations, +) + +from typing import ( + Callable, +) + +from minos.common import ( + MinosConfig, +) +from minos.networks import ( + EnrouteBuilder, + HttpEnrouteDecorator, + HttpRequest, + HttpResponse, + HttpRouter, +) + +from .decorators import ( + GraphQlEnrouteDecorator, +) + + +class GraphQlHttpRouter(HttpRouter): + """TODO""" + + def __init__(self, schema, *args, **kwargs): + super().__init__(*args, **kwargs) + self._schema = schema + + @staticmethod + def _from_config(config: MinosConfig, **kwargs) -> dict[(str, str), Callable]: + builder = EnrouteBuilder(*config.services, middleware=config.middleware) + routes = builder.get_all(config=config, **kwargs) + + graphql_routes = { + GraphQlEnrouteDecorator( + "CreateProduct", request=GraphQLField(GraphQLObjectType), response=GraphQLField(GraphQLObjectType) + ): "fn1", + GraphQlEnrouteDecorator( + "UpdateProduct", request=GraphQLField(GraphQLObjectType), response=GraphQLField(GraphQLObjectType) + ): "fn2", + } + + schema = GraphQLSchema( + query=GraphQLObjectType( + name="RootQueryType", + fields={ + "CreateProduct": GraphQLField( + GraphQLString, args=GraphQLField(GraphQLObjectType), response=GraphQLField(GraphQLObjectType) + ), + "UpdateProduct": GraphQLField( + GraphQLString, args=GraphQLField(GraphQLObjectType), response=GraphQLField(GraphQLObjectType) + ), + }, + ) + ) + + return cls(schema) + + @property + def routes(self) -> dict[HttpEnrouteDecorator, Callable]: + """TODO""" + return { + HttpEnrouteDecorator("/graphql", "POST"): self.handle_post, + HttpEnrouteDecorator("/graphql", "GET"): self.handle_get, + } + + async def handle_post(self, request: HttpRequest) -> HttpResponse: + """TODO""" + graphql_query = await request.content() + + content = await self._schema(graphql_query) + + return HttpResponse(content) + + async def handle_get(self, request: HttpRequest) -> HttpResponse: + """TODO""" + content = self._schema.json() + return HttpResponse(content) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/services.py similarity index 88% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py rename to packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/services.py index ce26b9858..e8bca0f69 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/services.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/services.py @@ -6,9 +6,10 @@ from aiomisc.service.aiohttp import ( AIOHTTPService, ) + from .handlers import ( GraphiqlHandler, - GraphqlHandler + GraphqlHandler, ) @@ -24,21 +25,22 @@ def __init__(self, **kwargs): # self.handler = RestHandler.from_config(**kwargs) # super().__init__(**(kwargs | {"address": self.handler.host, "port": self.handler.port})) super().__init__(**(kwargs | {"address": "localhost", "port": 7030})) + """ def _mount_graphql(self, app: web.Application): GraphQLView.attach(app, schema=schema, graphiql=True) """ def register_handlers(self, app: web.Application): - routes = [(r'/graphql', GraphqlHandler)] + routes = [(r"/graphql", GraphqlHandler)] # if self.dev: - routes += [(r'/graphiql', GraphiqlHandler)] + routes += [(r"/graphiql", GraphiqlHandler)] app.router.add_routes([web.view(route[0], route[1]) for route in routes]) async def initialize_jinja2(self, app: web.Application): - aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader('./templates')) + aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader("./templates")) async def create_application(self) -> web.Application: """Create the web application. @@ -49,6 +51,6 @@ async def create_application(self) -> web.Application: self.register_handlers(app) await self.initialize_jinja2(app) # self._mount_routes(app) - #self._mount_graphql(app) + # self._mount_graphql(app) return app # return self.handler.get_app() # pragma: no cover diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/__init__.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/star_wars_example/__init__.py similarity index 99% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/__init__.py rename to packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/star_wars_example/__init__.py index d06b7603f..5bcaa3cb0 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/__init__.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/star_wars_example/__init__.py @@ -5,4 +5,3 @@ from .schema import ( star_wars_schema, ) - diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/data.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/star_wars_example/data.py similarity index 95% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/data.py rename to packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/star_wars_example/data.py index 167d2a326..65aa5f650 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/data.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/star_wars_example/data.py @@ -5,7 +5,13 @@ demo. """ -from typing import Awaitable, Collection, Dict, Iterator, Optional +from typing import ( + Awaitable, + Collection, + Dict, + Iterator, + Optional, +) __all__ = ["get_droid", "get_friends", "get_hero", "get_human", "get_secret_backstory"] @@ -76,9 +82,7 @@ def __init__(self, id, name, friends, appearsIn, primaryFunction): homePlanet="Alderaan", ) -tarkin = Human( - id="1004", name="Wilhuff Tarkin", friends=["1001"], appearsIn=[4], homePlanet=None -) +tarkin = Human(id="1004", name="Wilhuff Tarkin", friends=["1001"], appearsIn=[4], homePlanet=None) human_data: Dict[str, Human] = { "1000": luke, diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/schema.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/star_wars_example/schema.py similarity index 77% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/schema.py rename to packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/star_wars_example/schema.py index 5c6adc946..b6d9068aa 100644 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/star_wars_example/schema.py +++ b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/star_wars_example/schema.py @@ -99,21 +99,14 @@ character_interface: GraphQLInterfaceType = GraphQLInterfaceType( "Character", lambda: { - "id": GraphQLField( - GraphQLNonNull(GraphQLString), description="The id of the character." - ), + "id": GraphQLField(GraphQLNonNull(GraphQLString), description="The id of the character."), "name": GraphQLField(GraphQLString, description="The name of the character."), "friends": GraphQLField( GraphQLList(character_interface), - description="The friends of the character," - " or an empty list if they have none.", - ), - "appearsIn": GraphQLField( - GraphQLList(episode_enum), description="Which movies they appear in." - ), - "secretBackstory": GraphQLField( - GraphQLString, description="All secrets about their past." + description="The friends of the character," " or an empty list if they have none.", ), + "appearsIn": GraphQLField(GraphQLList(episode_enum), description="Which movies they appear in."), + "secretBackstory": GraphQLField(GraphQLString, description="All secrets about their past."), }, resolve_type=lambda character, _info, _type: { "Human": human_type.name, @@ -136,19 +129,14 @@ human_type = GraphQLObjectType( "Human", lambda: { - "id": GraphQLField( - GraphQLNonNull(GraphQLString), description="The id of the human." - ), + "id": GraphQLField(GraphQLNonNull(GraphQLString), description="The id of the human."), "name": GraphQLField(GraphQLString, description="The name of the human."), "friends": GraphQLField( GraphQLList(character_interface), - description="The friends of the human," - " or an empty list if they have none.", + description="The friends of the human," " or an empty list if they have none.", resolve=lambda human, _info: get_friends(human), ), - "appearsIn": GraphQLField( - GraphQLList(episode_enum), description="Which movies they appear in." - ), + "appearsIn": GraphQLField(GraphQLList(episode_enum), description="Which movies they appear in."), "homePlanet": GraphQLField( GraphQLString, description="The home planet of the human, or null if unknown.", @@ -178,27 +166,20 @@ droid_type = GraphQLObjectType( "Droid", lambda: { - "id": GraphQLField( - GraphQLNonNull(GraphQLString), description="The id of the droid." - ), + "id": GraphQLField(GraphQLNonNull(GraphQLString), description="The id of the droid."), "name": GraphQLField(GraphQLString, description="The name of the droid."), "friends": GraphQLField( GraphQLList(character_interface), - description="The friends of the droid," - " or an empty list if they have none.", + description="The friends of the droid," " or an empty list if they have none.", resolve=lambda droid, _info: get_friends(droid), ), - "appearsIn": GraphQLField( - GraphQLList(episode_enum), description="Which movies they appear in." - ), + "appearsIn": GraphQLField(GraphQLList(episode_enum), description="Which movies they appear in."), "secretBackstory": GraphQLField( GraphQLString, resolve=lambda droid, _info: get_secret_backstory(droid), description="Construction date and the name of the designer.", ), - "primaryFunction": GraphQLField( - GraphQLString, description="The primary function of the droid." - ), + "primaryFunction": GraphQLField(GraphQLString, description="The primary function of the droid."), }, interfaces=[character_interface], description="A mechanical creature in the Star Wars universe.", @@ -235,20 +216,12 @@ ), "human": GraphQLField( human_type, - args={ - "id": GraphQLArgument( - GraphQLNonNull(GraphQLString), description="id of the human" - ) - }, + args={"id": GraphQLArgument(GraphQLNonNull(GraphQLString), description="id of the human")}, resolve=lambda _source, _info, id: get_human(id), ), "droid": GraphQLField( droid_type, - args={ - "id": GraphQLArgument( - GraphQLNonNull(GraphQLString), description="id of the droid" - ) - }, + args={"id": GraphQLArgument(GraphQLNonNull(GraphQLString), description="id of the droid")}, resolve=lambda _source, _info, id: get_droid(id), ), }, @@ -257,16 +230,10 @@ async def resolve_hello(obj, info): await asyncio.sleep(3) - return 'world' + return "world" -mutation_type = GraphQLObjectType( - "Mutation", - fields={ - 'hello': GraphQLField( - GraphQLString, - resolve=resolve_hello) - } -) + +mutation_type = GraphQLObjectType("Mutation", fields={"hello": GraphQLField(GraphQLString, resolve=resolve_hello)}) # Finally, we construct our schema (whose starting query type is the query # type we defined above) and export it. diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/templates/graphiql.html b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/templates/graphiql.html similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/templates/graphiql.html rename to packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/templates/graphiql.html diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py deleted file mode 100644 index d1a5f0caf..000000000 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -__author__ = "Minos Framework Devs" -__email__ = "hey@minos.run" -__version__ = "0.6.0" - -from .exceptions import ( - RestResponseException, -) -from .services import ( - RestService, -) diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphiql.py b/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphiql.py deleted file mode 100644 index 4e82cd7c3..000000000 --- a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql_aiohttp/handlers/graphiql.py +++ /dev/null @@ -1,8 +0,0 @@ -import aiohttp_jinja2 -from .abc import BaseHandler - - -class GraphiqlHandler(BaseHandler): - @aiohttp_jinja2.template('graphiql.html') - async def get(self): - return {'base_url': f'/graphql'} From 26d5929576632aaa2afe6ae4d1c7683cb3e12018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Fri, 11 Mar 2022 12:24:03 +0100 Subject: [PATCH 11/44] ISSUE #149 * Move to a plugin --- .../decorators/definitions/graphql.py | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 packages/core/minos-microservice-networks/minos/networks/decorators/definitions/graphql.py diff --git a/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/graphql.py b/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/graphql.py deleted file mode 100644 index 95624d0ea..000000000 --- a/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/graphql.py +++ /dev/null @@ -1,34 +0,0 @@ -from abc import ( - ABC, -) -from typing import ( - Final, - Iterable, -) - -from .abc import ( - EnrouteDecorator, -) -from .kinds import ( - EnrouteDecoratorKind, -) - - -class GraphqlEnrouteDecorator(EnrouteDecorator, ABC): - """Rest Enroute class""" - - def __init__(self, url: str, method: str): - self.url = url - self.method = method - - def __iter__(self) -> Iterable: - yield from ( - self.url, - self.method, - ) - - -class GraphqlQueryEnrouteDecorator(GraphqlEnrouteDecorator): - """Rest Command Enroute class""" - - KIND: Final[EnrouteDecoratorKind] = EnrouteDecoratorKind.Graphql From 873b5c8d5189888acc16d723201667e33edbc841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Fri, 11 Mar 2022 12:27:35 +0100 Subject: [PATCH 12/44] ISSUE #149 * Rename `minos-graphql-aiohttp` as `minos-router-aiohttp`. --- .../.editorconfig | 0 .../AUTHORS.md | 0 .../HISTORY.md | 0 .../{minos-graphql-aiohttp => minos-router-graphql}/LICENSE | 0 .../{minos-graphql-aiohttp => minos-router-graphql}/Makefile | 0 .../{minos-graphql-aiohttp => minos-router-graphql}/README.md | 2 +- .../RUNTHETESTS.md | 0 .../{minos-graphql-aiohttp => minos-router-graphql}/SETUP.md | 0 .../docs/Makefile | 0 .../docs/authors.rst | 0 .../docs/conf.py | 0 .../docs/history.rst | 0 .../docs/index.rst | 0 .../docs/make.bat | 0 .../docs/readme.rst | 0 .../docs/runthetests.rst | 0 .../docs/usage.rst | 0 .../minos/plugins/graphql/__init__.py | 0 .../minos/plugins/graphql/decorators.py | 0 .../minos/plugins/graphql/exceptions.py | 0 .../minos/plugins/graphql/handlers/__init__.py | 0 .../minos/plugins/graphql/handlers/abc.py | 0 .../minos/plugins/graphql/handlers/graphiql.py | 0 .../minos/plugins/graphql/handlers/graphql.py | 0 .../minos/plugins/graphql/main.py | 0 .../minos/plugins/graphql/routers.py | 0 .../minos/plugins/graphql/services.py | 0 .../minos/plugins/graphql/star_wars_example/__init__.py | 0 .../minos/plugins/graphql/star_wars_example/data.py | 0 .../minos/plugins/graphql/star_wars_example/schema.py | 0 .../minos/plugins/graphql/templates/graphiql.html | 0 .../poetry.lock | 0 .../poetry.toml | 0 .../pyproject.toml | 4 ++-- .../{minos-graphql-aiohttp => minos-router-graphql}/setup.cfg | 0 .../tests/__init__.py | 0 .../tests/services/commands.py | 0 .../tests/services/queries.py | 0 .../tests/test_config.yml | 0 .../tests/test_plugins/test_graphql_aiohttp/utils.py | 0 .../tests/utils.py | 0 41 files changed, 3 insertions(+), 3 deletions(-) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/.editorconfig (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/AUTHORS.md (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/HISTORY.md (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/LICENSE (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/Makefile (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/README.md (98%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/RUNTHETESTS.md (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/SETUP.md (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/docs/Makefile (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/docs/authors.rst (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/docs/conf.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/docs/history.rst (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/docs/index.rst (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/docs/make.bat (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/docs/readme.rst (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/docs/runthetests.rst (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/docs/usage.rst (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/minos/plugins/graphql/__init__.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/minos/plugins/graphql/decorators.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/minos/plugins/graphql/exceptions.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/minos/plugins/graphql/handlers/__init__.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/minos/plugins/graphql/handlers/abc.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/minos/plugins/graphql/handlers/graphiql.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/minos/plugins/graphql/handlers/graphql.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/minos/plugins/graphql/main.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/minos/plugins/graphql/routers.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/minos/plugins/graphql/services.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/minos/plugins/graphql/star_wars_example/__init__.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/minos/plugins/graphql/star_wars_example/data.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/minos/plugins/graphql/star_wars_example/schema.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/minos/plugins/graphql/templates/graphiql.html (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/poetry.lock (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/poetry.toml (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/pyproject.toml (93%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/setup.cfg (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/tests/__init__.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/tests/services/commands.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/tests/services/queries.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/tests/test_config.yml (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/tests/test_plugins/test_graphql_aiohttp/utils.py (100%) rename packages/plugins/{minos-graphql-aiohttp => minos-router-graphql}/tests/utils.py (100%) diff --git a/packages/plugins/minos-graphql-aiohttp/.editorconfig b/packages/plugins/minos-router-graphql/.editorconfig similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/.editorconfig rename to packages/plugins/minos-router-graphql/.editorconfig diff --git a/packages/plugins/minos-graphql-aiohttp/AUTHORS.md b/packages/plugins/minos-router-graphql/AUTHORS.md similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/AUTHORS.md rename to packages/plugins/minos-router-graphql/AUTHORS.md diff --git a/packages/plugins/minos-graphql-aiohttp/HISTORY.md b/packages/plugins/minos-router-graphql/HISTORY.md similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/HISTORY.md rename to packages/plugins/minos-router-graphql/HISTORY.md diff --git a/packages/plugins/minos-graphql-aiohttp/LICENSE b/packages/plugins/minos-router-graphql/LICENSE similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/LICENSE rename to packages/plugins/minos-router-graphql/LICENSE diff --git a/packages/plugins/minos-graphql-aiohttp/Makefile b/packages/plugins/minos-router-graphql/Makefile similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/Makefile rename to packages/plugins/minos-router-graphql/Makefile diff --git a/packages/plugins/minos-graphql-aiohttp/README.md b/packages/plugins/minos-router-graphql/README.md similarity index 98% rename from packages/plugins/minos-graphql-aiohttp/README.md rename to packages/plugins/minos-router-graphql/README.md index 3be59ccff..6aaf6f343 100644 --- a/packages/plugins/minos-graphql-aiohttp/README.md +++ b/packages/plugins/minos-router-graphql/README.md @@ -2,7 +2,7 @@ Minos logo

-## minos-graphql-aiohttp +## minos-router-graphql [![PyPI Latest Release](https://img.shields.io/pypi/v/minos-broker-kafka.svg)](https://pypi.org/project/minos-broker-kafka/) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/minos-framework/minos-python/pages%20build%20and%20deployment?label=docs)](https://minos-framework.github.io/minos-python) diff --git a/packages/plugins/minos-graphql-aiohttp/RUNTHETESTS.md b/packages/plugins/minos-router-graphql/RUNTHETESTS.md similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/RUNTHETESTS.md rename to packages/plugins/minos-router-graphql/RUNTHETESTS.md diff --git a/packages/plugins/minos-graphql-aiohttp/SETUP.md b/packages/plugins/minos-router-graphql/SETUP.md similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/SETUP.md rename to packages/plugins/minos-router-graphql/SETUP.md diff --git a/packages/plugins/minos-graphql-aiohttp/docs/Makefile b/packages/plugins/minos-router-graphql/docs/Makefile similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/docs/Makefile rename to packages/plugins/minos-router-graphql/docs/Makefile diff --git a/packages/plugins/minos-graphql-aiohttp/docs/authors.rst b/packages/plugins/minos-router-graphql/docs/authors.rst similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/docs/authors.rst rename to packages/plugins/minos-router-graphql/docs/authors.rst diff --git a/packages/plugins/minos-graphql-aiohttp/docs/conf.py b/packages/plugins/minos-router-graphql/docs/conf.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/docs/conf.py rename to packages/plugins/minos-router-graphql/docs/conf.py diff --git a/packages/plugins/minos-graphql-aiohttp/docs/history.rst b/packages/plugins/minos-router-graphql/docs/history.rst similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/docs/history.rst rename to packages/plugins/minos-router-graphql/docs/history.rst diff --git a/packages/plugins/minos-graphql-aiohttp/docs/index.rst b/packages/plugins/minos-router-graphql/docs/index.rst similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/docs/index.rst rename to packages/plugins/minos-router-graphql/docs/index.rst diff --git a/packages/plugins/minos-graphql-aiohttp/docs/make.bat b/packages/plugins/minos-router-graphql/docs/make.bat similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/docs/make.bat rename to packages/plugins/minos-router-graphql/docs/make.bat diff --git a/packages/plugins/minos-graphql-aiohttp/docs/readme.rst b/packages/plugins/minos-router-graphql/docs/readme.rst similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/docs/readme.rst rename to packages/plugins/minos-router-graphql/docs/readme.rst diff --git a/packages/plugins/minos-graphql-aiohttp/docs/runthetests.rst b/packages/plugins/minos-router-graphql/docs/runthetests.rst similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/docs/runthetests.rst rename to packages/plugins/minos-router-graphql/docs/runthetests.rst diff --git a/packages/plugins/minos-graphql-aiohttp/docs/usage.rst b/packages/plugins/minos-router-graphql/docs/usage.rst similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/docs/usage.rst rename to packages/plugins/minos-router-graphql/docs/usage.rst diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/__init__.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/__init__.py rename to packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/decorators.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/decorators.py rename to packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/exceptions.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/exceptions.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/exceptions.py rename to packages/plugins/minos-router-graphql/minos/plugins/graphql/exceptions.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/__init__.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/__init__.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/__init__.py rename to packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/__init__.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/abc.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/abc.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/abc.py rename to packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/abc.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/graphiql.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/graphiql.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/graphiql.py rename to packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/graphiql.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/graphql.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/graphql.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/handlers/graphql.py rename to packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/graphql.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/main.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/main.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/main.py rename to packages/plugins/minos-router-graphql/minos/plugins/graphql/main.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/routers.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/routers.py rename to packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/services.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/services.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/services.py rename to packages/plugins/minos-router-graphql/minos/plugins/graphql/services.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/star_wars_example/__init__.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/__init__.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/star_wars_example/__init__.py rename to packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/__init__.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/star_wars_example/data.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/data.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/star_wars_example/data.py rename to packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/data.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/star_wars_example/schema.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/schema.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/star_wars_example/schema.py rename to packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/schema.py diff --git a/packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/templates/graphiql.html b/packages/plugins/minos-router-graphql/minos/plugins/graphql/templates/graphiql.html similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/minos/plugins/graphql/templates/graphiql.html rename to packages/plugins/minos-router-graphql/minos/plugins/graphql/templates/graphiql.html diff --git a/packages/plugins/minos-graphql-aiohttp/poetry.lock b/packages/plugins/minos-router-graphql/poetry.lock similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/poetry.lock rename to packages/plugins/minos-router-graphql/poetry.lock diff --git a/packages/plugins/minos-graphql-aiohttp/poetry.toml b/packages/plugins/minos-router-graphql/poetry.toml similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/poetry.toml rename to packages/plugins/minos-router-graphql/poetry.toml diff --git a/packages/plugins/minos-graphql-aiohttp/pyproject.toml b/packages/plugins/minos-router-graphql/pyproject.toml similarity index 93% rename from packages/plugins/minos-graphql-aiohttp/pyproject.toml rename to packages/plugins/minos-router-graphql/pyproject.toml index cce0f4fdf..2fd5a3fa3 100644 --- a/packages/plugins/minos-graphql-aiohttp/pyproject.toml +++ b/packages/plugins/minos-router-graphql/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] -name = "minos-graphql-aiohttp" +name = "minos-router-graphql" version = "0.6.0" -description = "The rest plugin of the Minos Framework." +description = "The graphql plugin of the Minos Framework." readme = "README.md" repository = "https://github.com/minos-framework/minos-python" homepage = "http://www.minos.run/" diff --git a/packages/plugins/minos-graphql-aiohttp/setup.cfg b/packages/plugins/minos-router-graphql/setup.cfg similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/setup.cfg rename to packages/plugins/minos-router-graphql/setup.cfg diff --git a/packages/plugins/minos-graphql-aiohttp/tests/__init__.py b/packages/plugins/minos-router-graphql/tests/__init__.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/tests/__init__.py rename to packages/plugins/minos-router-graphql/tests/__init__.py diff --git a/packages/plugins/minos-graphql-aiohttp/tests/services/commands.py b/packages/plugins/minos-router-graphql/tests/services/commands.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/tests/services/commands.py rename to packages/plugins/minos-router-graphql/tests/services/commands.py diff --git a/packages/plugins/minos-graphql-aiohttp/tests/services/queries.py b/packages/plugins/minos-router-graphql/tests/services/queries.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/tests/services/queries.py rename to packages/plugins/minos-router-graphql/tests/services/queries.py diff --git a/packages/plugins/minos-graphql-aiohttp/tests/test_config.yml b/packages/plugins/minos-router-graphql/tests/test_config.yml similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/tests/test_config.yml rename to packages/plugins/minos-router-graphql/tests/test_config.yml diff --git a/packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/utils.py b/packages/plugins/minos-router-graphql/tests/test_plugins/test_graphql_aiohttp/utils.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/tests/test_plugins/test_graphql_aiohttp/utils.py rename to packages/plugins/minos-router-graphql/tests/test_plugins/test_graphql_aiohttp/utils.py diff --git a/packages/plugins/minos-graphql-aiohttp/tests/utils.py b/packages/plugins/minos-router-graphql/tests/utils.py similarity index 100% rename from packages/plugins/minos-graphql-aiohttp/tests/utils.py rename to packages/plugins/minos-router-graphql/tests/utils.py From 2216e1985c1212af94144c69eef2d7d0cecfdc4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Fri, 11 Mar 2022 12:40:36 +0100 Subject: [PATCH 13/44] ISSUE #149 * Add test_something. --- .../plugins/minos-router-graphql/poetry.lock | 50 ++++----- .../tests/test_graphql/__init__.py | 0 .../tests/test_graphql/test_something.py | 15 +++ .../test_graphql_aiohttp/utils.py | 100 ------------------ .../minos-router-graphql/tests/utils.py | 31 ++++++ 5 files changed, 71 insertions(+), 125 deletions(-) create mode 100644 packages/plugins/minos-router-graphql/tests/test_graphql/__init__.py create mode 100644 packages/plugins/minos-router-graphql/tests/test_graphql/test_something.py delete mode 100644 packages/plugins/minos-router-graphql/tests/test_plugins/test_graphql_aiohttp/utils.py diff --git a/packages/plugins/minos-router-graphql/poetry.lock b/packages/plugins/minos-router-graphql/poetry.lock index d17b27ac0..836ad91c6 100644 --- a/packages/plugins/minos-router-graphql/poetry.lock +++ b/packages/plugins/minos-router-graphql/poetry.lock @@ -32,7 +32,7 @@ jinja2 = ">=3.0.0" [[package]] name = "aiomisc" -version = "15.6.8" +version = "15.7.2" description = "aiomisc - miscellaneous utils for asyncio" category = "main" optional = false @@ -270,7 +270,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "fastavro" -version = "1.4.9" +version = "1.4.10" description = "Fast read/write of AVRO files" category = "main" optional = false @@ -440,7 +440,7 @@ python-versions = "*" [[package]] name = "minos-microservice-common" -version = "0.5.2" +version = "0.5.3" description = "The common core of the Minos Framework" category = "main" optional = false @@ -463,7 +463,7 @@ url = "../../core/minos-microservice-common" [[package]] name = "minos-microservice-networks" -version = "0.5.2" +version = "0.5.3" description = "The networks core of the Minos Framework" category = "main" optional = false @@ -896,7 +896,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "virtualenv" -version = "20.13.2" +version = "20.13.3" description = "Virtual Python Environment builder" category = "dev" optional = false @@ -1021,8 +1021,8 @@ aiohttp-jinja2 = [ {file = "aiohttp_jinja2-1.5-py3-none-any.whl", hash = "sha256:b55c0ed167b0cc4b6d6a50fb2299a44beb5dc4aec9df21305b91a5484694cf74"}, ] aiomisc = [ - {file = "aiomisc-15.6.8-py3-none-any.whl", hash = "sha256:b65f7f8e50d9791bbb745253e814862880b8a35bf21e4eea545ada9daf89836c"}, - {file = "aiomisc-15.6.8.tar.gz", hash = "sha256:09024d1f544de98b152dad8f7eb7d02c64457aaaaf03bd6c76a20b73328a3a74"}, + {file = "aiomisc-15.7.2-py3-none-any.whl", hash = "sha256:e0b00145866b5c258a23e2c270bee2910f2562b078f6159d786a522699e8cfeb"}, + {file = "aiomisc-15.7.2.tar.gz", hash = "sha256:fcc4517fa49056ec11ecd3ec5d341c554914c0a2d295c127b70583bab1ecab5b"}, ] aiopg = [ {file = "aiopg-1.3.3-py3-none-any.whl", hash = "sha256:2842dd8741460eeef940032dcb577bfba4d4115205dd82a73ce13b3271f5bf0a"}, @@ -1198,22 +1198,22 @@ docutils = [ {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] fastavro = [ - {file = "fastavro-1.4.9-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:2e64a77c529b638e89a879ff0211debfab5b2d114c26a2af29c81f6b013f395a"}, - {file = "fastavro-1.4.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fc9c95b7c1d59c5a2d29be21075870a122152cf927d84587dafc96da6b2ac3d"}, - {file = "fastavro-1.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:927fd6148a8dd9646c129c0a0e8571aea829abc3cba04a3d5a4010a866934f4c"}, - {file = "fastavro-1.4.9-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:000b70c5109a61bdbfddeb2821a506de8f5333f243c608cbced61d44657d6c2f"}, - {file = "fastavro-1.4.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37a77a1b5347a06416e236c77027c750aaeda29ef8189aa456eb2a2571274b43"}, - {file = "fastavro-1.4.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce2c7747ce958115388872db0756d3eeb0d796084eea9b46dc3758ef32c4d952"}, - {file = "fastavro-1.4.9-cp37-cp37m-win_amd64.whl", hash = "sha256:d6ccb77604903a0308316e696bb65a8943361af5f757d10985689656c9bce6ed"}, - {file = "fastavro-1.4.9-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:9a6ada2d6e133a2319438248c2e023b6735747b249c5a79d5f08f9d431e5d226"}, - {file = "fastavro-1.4.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7537e4df7782b03b9761e9338cef9fc7bfcc41100ab93c36c5c60fa568e724a"}, - {file = "fastavro-1.4.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a9cd6d8471beb4020b4126fd04150ed7295f74ae7234d0dc9205b55c193851e"}, - {file = "fastavro-1.4.9-cp38-cp38-win_amd64.whl", hash = "sha256:fa9d8b47e0533c84152332ad491bb63bbae76a8a7a0df1caa821e0cbebf0fb70"}, - {file = "fastavro-1.4.9-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:3759bdc77009ee1e2e76fc9f58b951c05c00a8600ef9ddbff59fee3cb0c9e235"}, - {file = "fastavro-1.4.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b98ef2bdb123b95945aa6d69d6a7d79f211df3274b2dd7786da7852ddec964d0"}, - {file = "fastavro-1.4.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32b804aa6920d80c0e94e1180d480f28f56c4b901849bd80ed180851752b5ce6"}, - {file = "fastavro-1.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:f9b04acaf06b16218b47985e92d8daa98c1116d58f3cff81a5b3cf39cef9afc0"}, - {file = "fastavro-1.4.9.tar.gz", hash = "sha256:be3fec387eb2cdc9627060b5ae0690542c687dddc951b63fa11203553769ae5e"}, + {file = "fastavro-1.4.10-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:f225c81e869b3cefef6f2b478cd452693181ed7e645be3cea4d82024354ecaa0"}, + {file = "fastavro-1.4.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7669302c9124b7cd6c1bdff80c77116b2290c984814077fb7d01d7389586054"}, + {file = "fastavro-1.4.10-cp310-cp310-win_amd64.whl", hash = "sha256:995525bdfbdfef205ea148b5bc6a9fe5ccf921931123c39d9aad75a2b661681e"}, + {file = "fastavro-1.4.10-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:64244c53f1e4853184c2f7383d0332e1dcb34c38c05e6613530ade0378e8acfc"}, + {file = "fastavro-1.4.10-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c60965da6dc7a91e00ccd84d84797fad746555f44e8a816c4cc460fb231c44fe"}, + {file = "fastavro-1.4.10-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10eb25378f37dc00c10e83c4c0442c1a6d1b871f74a6dfdfc12b6447962bbdd0"}, + {file = "fastavro-1.4.10-cp37-cp37m-win_amd64.whl", hash = "sha256:d5719adf6045fc743de5fa738d561a81e58dc782c94f1b16cb21b5dd6253e7fd"}, + {file = "fastavro-1.4.10-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:24c4a1a8cc92e135ecfcd9cbd1f6cfa088cbc74d78c18e02a609cb11fa33778d"}, + {file = "fastavro-1.4.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0934490b0c3bcfd6bcbacbcb5144c6b5e4298cda209fbb17c856adf5405127dd"}, + {file = "fastavro-1.4.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a23763d73412c077aee401a0368c64cbc23859e26711dbae78a3cf0227f65165"}, + {file = "fastavro-1.4.10-cp38-cp38-win_amd64.whl", hash = "sha256:09f1dfdd8192ae09e0f477d1f024d8054fccdb099ad495d2a796bcee3cadebd1"}, + {file = "fastavro-1.4.10-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:0c6695753fa3035bbd0fa5cb21bf1b5dad39483c669b32ca0bb55fb07c1ccc87"}, + {file = "fastavro-1.4.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35f960dbba04647d8d6d5616f879622d2a1e8a84eb2d2e02a883a22e0803463a"}, + {file = "fastavro-1.4.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9660878ca01e3dbbee12385c5902a2b6b12ecbb5af4733d1026175a14b1ef67f"}, + {file = "fastavro-1.4.10-cp39-cp39-win_amd64.whl", hash = "sha256:64cbd386e408b3bcb2de53b1f847163073eb0d0d0338db65f76051c6ba9a9bc1"}, + {file = "fastavro-1.4.10.tar.gz", hash = "sha256:a24f9dd803c44bfb599476b000f9bd0088f7ac2401e6c20818f38d8af12785a0"}, ] filelock = [ {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, @@ -1718,8 +1718,8 @@ urllib3 = [ {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] virtualenv = [ - {file = "virtualenv-20.13.2-py2.py3-none-any.whl", hash = "sha256:e7b34c9474e6476ee208c43a4d9ac1510b041c68347eabfe9a9ea0c86aa0a46b"}, - {file = "virtualenv-20.13.2.tar.gz", hash = "sha256:01f5f80744d24a3743ce61858123488e91cb2dd1d3bdf92adaf1bba39ffdedf0"}, + {file = "virtualenv-20.13.3-py2.py3-none-any.whl", hash = "sha256:dd448d1ded9f14d1a4bfa6bfc0c5b96ae3be3f2d6c6c159b23ddcfd701baa021"}, + {file = "virtualenv-20.13.3.tar.gz", hash = "sha256:e9dd1a1359d70137559034c0f5433b34caf504af2dc756367be86a5a32967134"}, ] yarl = [ {file = "yarl-1.7.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95"}, diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/__init__.py b/packages/plugins/minos-router-graphql/tests/test_graphql/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_something.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_something.py new file mode 100644 index 000000000..41c4b4605 --- /dev/null +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_something.py @@ -0,0 +1,15 @@ +import unittest + +from minos.plugins.graphql import ( + GraphQlEnroute, +) + + +class TestSomething(unittest.TestCase): + + def test_true(self): + self.assertTrue(GraphQlEnroute) + + +if __name__ == '__main__': + unittest.main() diff --git a/packages/plugins/minos-router-graphql/tests/test_plugins/test_graphql_aiohttp/utils.py b/packages/plugins/minos-router-graphql/tests/test_plugins/test_graphql_aiohttp/utils.py deleted file mode 100644 index 2cb3e5466..000000000 --- a/packages/plugins/minos-router-graphql/tests/test_plugins/test_graphql_aiohttp/utils.py +++ /dev/null @@ -1,100 +0,0 @@ -import json -from typing import ( - Any, - Optional, - Union, -) -from unittest.mock import ( - AsyncMock, -) -from urllib.parse import ( - urlencode, -) -from uuid import ( - UUID, -) - -from aiohttp import ( - test_utils, - web, -) -from aiohttp.streams import ( - EmptyStreamReader, -) -from multidict import ( - MultiDict, -) - -from minos.common import ( - MinosAvroProtocol, -) - - -def json_mocked_request(data: Any, **kwargs) -> web.Request: - """For testng purposes.""" - return mocked_request(json.dumps(data).encode(), content_type="application/json", **kwargs) - - -def form_mocked_request(data: dict[str, Any], **kwargs) -> web.Request: - """For testng purposes.""" - return mocked_request( - urlencode(data, doseq=True).encode(), content_type="application/x-www-form-urlencoded", **kwargs - ) - - -def avro_mocked_request(data: Any, schema: Any, **kwargs) -> web.Request: - """For testng purposes.""" - return mocked_request(MinosAvroProtocol.encode(data, schema), content_type="avro/binary", **kwargs) - - -def text_mocked_request(data: str, **kwargs) -> web.Request: - """For testng purposes.""" - return mocked_request(data.encode(), content_type="text/plain", **kwargs) - - -def bytes_mocked_request(data: bytes, **kwargs) -> web.Request: - """For testng purposes.""" - return mocked_request(data, content_type="application/octet-stream", **kwargs) - - -def mocked_request( - data: Optional[bytes] = None, - headers: Optional[dict[str, str]] = None, - user: Optional[Union[str, UUID]] = None, - content_type: Optional[str] = None, - method: str = "POST", - path: str = "localhost", - url_params: Optional[list[tuple[str, str]]] = None, - query_params: Optional[list[tuple[str, str]]] = None, -) -> web.Request: - """For testing purposes""" - if headers is None: - headers = dict() - else: - headers = headers.copy() - - if user is not None: - headers["user"] = str(user) - - if content_type is not None: - headers["Content-Type"] = content_type - - kwargs = { - "method": method, - "path": path, - "headers": headers, - } - if data is None: - kwargs["payload"] = EmptyStreamReader() - - request = test_utils.make_mocked_request(**kwargs) - request.read = AsyncMock(return_value=data) - - if url_params is not None: - # noinspection PyProtectedMember - request._rel_url = request._rel_url.with_query(url_params) - - if query_params is not None: - request._match_info = MultiDict(map(lambda kv: (str(kv[0]), str(kv[1])), query_params)) - - return request diff --git a/packages/plugins/minos-router-graphql/tests/utils.py b/packages/plugins/minos-router-graphql/tests/utils.py index d7424f6ce..51ecad270 100644 --- a/packages/plugins/minos-router-graphql/tests/utils.py +++ b/packages/plugins/minos-router-graphql/tests/utils.py @@ -11,6 +11,11 @@ from minos.common import ( DeclarativeModel, ) +from minos.networks import ( + Request, + Response, + enroute, +) BASE_PATH = Path(__file__).parent CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" @@ -25,3 +30,29 @@ class FakeModel(DeclarativeModel): def __lt__(self, other: Any) -> bool: # noinspection PyBroadException return isinstance(other, type(self)) and self.data < other.data + + +class FakeCommandService: + """For testng purposes.""" + + # noinspection PyUnusedLocal + @enroute.rest.command(url="/order", method="GET") + def get_order_rest(self, request: Request) -> Response: + """For testng purposes.""" + + return Response("get_order") + + @enroute.broker.command("GetOrder") + def get_order_command(self, request: Request) -> Response: + """For testng purposes.""" + return Response("get_order_command") + + @enroute.graphql.command( + "create_product", + # args={"request": GraphQLArgument(GraphQlObject({"name": GraphQLString, "surname": GraphQLString}))}, + # response=GrapqlUUID + ) + def get_hero(self, request: Request) -> Response: + """For testng purposes.""" + uuid = request.content() + return Response(uuid) From 9e749179ce85f29d25c670be6a6c8e0803bfbd8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Fri, 11 Mar 2022 12:42:16 +0100 Subject: [PATCH 14/44] ISSUE #149 * Revert commit. --- .../minos/networks/__init__.py | 2 -- .../minos/networks/decorators/analyzers.py | 10 ---------- .../networks/decorators/definitions/kinds.py | 1 - .../tests/services/graphql.py | 11 ----------- .../test_decorators/test_analyzers.py | 15 --------------- .../test_networks/test_decorators/test_api.py | 4 ---- .../minos-microservice-networks/tests/utils.py | 12 ------------ 7 files changed, 55 deletions(-) delete mode 100644 packages/core/minos-microservice-networks/tests/services/graphql.py diff --git a/packages/core/minos-microservice-networks/minos/networks/__init__.py b/packages/core/minos-microservice-networks/minos/networks/__init__.py index 01abec743..4ac359614 100644 --- a/packages/core/minos-microservice-networks/minos/networks/__init__.py +++ b/packages/core/minos-microservice-networks/minos/networks/__init__.py @@ -55,8 +55,6 @@ EnrouteBuilder, EnrouteDecorator, EnrouteDecoratorKind, - GraphqlEnrouteDecorator, - GraphqlQueryEnrouteDecorator, Handler, HandlerMeta, HandlerWrapper, diff --git a/packages/core/minos-microservice-networks/minos/networks/decorators/analyzers.py b/packages/core/minos-microservice-networks/minos/networks/decorators/analyzers.py index bfbcb8c18..a839e8e47 100644 --- a/packages/core/minos-microservice-networks/minos/networks/decorators/analyzers.py +++ b/packages/core/minos-microservice-networks/minos/networks/decorators/analyzers.py @@ -24,8 +24,6 @@ BrokerEventEnrouteDecorator, BrokerQueryEnrouteDecorator, EnrouteDecorator, - GraphqlEnrouteDecorator, - GraphqlQueryEnrouteDecorator, PeriodicEventEnrouteDecorator, RestCommandEnrouteDecorator, RestEnrouteDecorator, @@ -52,14 +50,6 @@ def get_rest_command_query(self) -> dict[str, set[RestEnrouteDecorator]]: # noinspection PyTypeChecker return self._get_items({RestCommandEnrouteDecorator, RestQueryEnrouteDecorator}) - def get_graphql(self) -> dict[str, set[GraphqlEnrouteDecorator]]: - """Returns rest's command and query values. - - :return: A mapping with functions as keys and a sets of decorators as values. - """ - # noinspection PyTypeChecker - return self._get_items({GraphqlQueryEnrouteDecorator}) - def get_broker_command_query_event(self) -> dict[str, set[BrokerEnrouteDecorator]]: """Returns broker's command, query and event values. diff --git a/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/kinds.py b/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/kinds.py index ea3bb3fab..513947c0a 100644 --- a/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/kinds.py +++ b/packages/core/minos-microservice-networks/minos/networks/decorators/definitions/kinds.py @@ -10,7 +10,6 @@ class EnrouteDecoratorKind(Enum): Command = auto() Query = auto() Event = auto() - Graphql = auto() @property def pre_fn_name(self) -> str: diff --git a/packages/core/minos-microservice-networks/tests/services/graphql.py b/packages/core/minos-microservice-networks/tests/services/graphql.py deleted file mode 100644 index b4cc7d4f4..000000000 --- a/packages/core/minos-microservice-networks/tests/services/graphql.py +++ /dev/null @@ -1,11 +0,0 @@ -from minos.networks import ( - Request, - Response, - enroute, -) - - -class GraphqlService: - @enroute.graphql(url="/graphql", method="POST") - def add_ticket(self, request: Request) -> Response: - return Response({"data": {}, "errors": []}) diff --git a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py index a8fe39b6e..ca600dbf3 100644 --- a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py +++ b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py @@ -42,9 +42,6 @@ def test_get_all(self): }, "send_newsletter": {PeriodicEventEnrouteDecorator("@daily")}, "check_inactive_users": {PeriodicEventEnrouteDecorator("@daily")}, - "graphql_handler": {GraphqlQueryEnrouteDecorator("graphql/", "GET")}, - "graphql_handler2": {GraphqlQueryEnrouteDecorator("graphql-2/", "GET")}, - "graphql_handler3": {GraphqlQueryEnrouteDecorator("graphql-3/", "POST")}, } self.assertEqual(expected, observed) @@ -61,18 +58,6 @@ def test_get_rest_command_query(self): self.assertEqual(expected, observed) - def test_get_graphql(self): - analyzer = EnrouteAnalyzer(FakeService) - - observed = analyzer.get_graphql() - expected = { - "graphql_handler": {GraphqlQueryEnrouteDecorator("graphql/", "GET")}, - "graphql_handler2": {GraphqlQueryEnrouteDecorator("graphql-2/", "GET")}, - "graphql_handler3": {GraphqlQueryEnrouteDecorator("graphql-3/", "POST")}, - } - - self.assertEqual(expected, observed) - def test_get_broker_command_query_event(self): analyzer = EnrouteAnalyzer(FakeService) diff --git a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py index 3d96d926d..a988d7cac 100644 --- a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py +++ b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py @@ -21,10 +21,6 @@ def test_rest_query(self): decorator = enroute.rest.query(path="tickets/", method="GET") self.assertEqual(RestQueryEnrouteDecorator("tickets/", "GET"), decorator) - def test_graphql(self): - decorator = enroute.graphql(url="graphql/", method="POST") - self.assertEqual(GraphqlQueryEnrouteDecorator("graphql/", "POST"), decorator) - def test_rest_event_raises(self): with self.assertRaises(AttributeError): enroute.rest.event("CreateTicket") diff --git a/packages/core/minos-microservice-networks/tests/utils.py b/packages/core/minos-microservice-networks/tests/utils.py index 1c2d5b3bb..95e20aae3 100644 --- a/packages/core/minos-microservice-networks/tests/utils.py +++ b/packages/core/minos-microservice-networks/tests/utils.py @@ -174,18 +174,6 @@ def bar(self, request: Request): """For testing purposes.""" return Response("bar") - @enroute.graphql(url="graphql/", method="GET") - def graphql_handler(self): - return - - @enroute.graphql(url="graphql-2/", method="GET") - def graphql_handler2(self): - return - - @enroute.graphql(url="graphql-3/", method="POST") - def graphql_handler3(self): - return - class FakeServiceWithGetEnroute: @staticmethod From 0687c2e8d50720450dd82a8d804673449ac358a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Fri, 11 Mar 2022 12:43:47 +0100 Subject: [PATCH 15/44] ISSUE #149 * Revert commit (2). --- .../tests/test_networks/test_decorators/test_analyzers.py | 1 - .../tests/test_networks/test_decorators/test_api.py | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py index ca600dbf3..ab1bfae50 100644 --- a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py +++ b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_analyzers.py @@ -8,7 +8,6 @@ BrokerEventEnrouteDecorator, BrokerQueryEnrouteDecorator, EnrouteAnalyzer, - GraphqlQueryEnrouteDecorator, PeriodicEventEnrouteDecorator, RestCommandEnrouteDecorator, RestQueryEnrouteDecorator, diff --git a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py index a988d7cac..075c7c5f3 100644 --- a/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py +++ b/packages/core/minos-microservice-networks/tests/test_networks/test_decorators/test_api.py @@ -4,7 +4,6 @@ BrokerCommandEnrouteDecorator, BrokerEventEnrouteDecorator, BrokerQueryEnrouteDecorator, - GraphqlQueryEnrouteDecorator, PeriodicEventEnrouteDecorator, RestCommandEnrouteDecorator, RestQueryEnrouteDecorator, From bb32bbdbbe2fbc2fb00f366a3f67921acdb5f852 Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Mon, 14 Mar 2022 14:25:50 +0100 Subject: [PATCH 16/44] ISSUE #149 --- .../minos/plugins/graphql/__init__.py | 6 +++ .../minos/plugins/graphql/decorators.py | 19 ++++++-- .../minos/plugins/graphql/main.py | 2 +- .../minos/plugins/graphql/routers.py | 42 ++++++------------ .../minos/plugins/graphql/schema_builder.py | 44 +++++++++++++++++++ .../tests/services/commands.py | 33 ++++---------- .../tests/services/queries.py | 19 ++++---- .../tests/test_graphql/test_decorators.py | 39 ++++++++++++++++ .../tests/test_graphql/test_routers.py | 27 ++++++++++++ .../tests/test_graphql/test_schema_builder.py | 19 ++++++++ .../tests/test_graphql/test_something.py | 15 ------- .../minos-router-graphql/tests/utils.py | 31 ++++++++----- 12 files changed, 204 insertions(+), 92 deletions(-) create mode 100644 packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py create mode 100644 packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py create mode 100644 packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py create mode 100644 packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py delete mode 100644 packages/plugins/minos-router-graphql/tests/test_graphql/test_something.py diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py index af4f2bf06..3ffd178ce 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py @@ -5,6 +5,12 @@ from .decorators import ( GraphQlEnroute, ) +from .routers import ( + GraphQlHttpRouter, +) +from .schema_builder import ( + GraphQLSchemaBuilder, +) def _register_enroute(): diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py index ff512ed4e..8c806873d 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py @@ -13,16 +13,29 @@ EnrouteDecoratorKind, enroute, ) +from collections.abc import ( + Collection, +) +from collections import ( + defaultdict, +) class GraphQlEnrouteDecorator(EnrouteDecorator, ABC): """GraphQl Enroute class""" - def __init__(self, topic: str): - self.topic = topic + def __init__(self, name: str, argument, output): + self.name = name + self.argument = argument + self.output = output def __iter__(self) -> Iterable: - yield from (self.topic,) + yield from (self.name,) + + def _validate_not_redefined(self, decorators: Collection[EnrouteDecorator]) -> None: + mapper = defaultdict(set) + for decorator in decorators: + mapper[tuple(decorator)].add(decorator) class GraphQlCommandEnrouteDecorator(GraphQlEnrouteDecorator): diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/main.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/main.py index b751c179b..36c8eb2cf 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/main.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/main.py @@ -2,7 +2,7 @@ web, ) -from minos.plugins.graphql_aiohttp.services import ( +from minos.plugins.graphql.services import ( RestService, ) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py index 4eee7eee7..0110ebb1a 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py @@ -6,6 +6,7 @@ Callable, ) +from graphql import GraphQLSchema from minos.common import ( MinosConfig, ) @@ -15,47 +16,32 @@ HttpRequest, HttpResponse, HttpRouter, -) - -from .decorators import ( - GraphQlEnrouteDecorator, + EnrouteDecoratorKind, ) class GraphQlHttpRouter(HttpRouter): """TODO""" - def __init__(self, schema, *args, **kwargs): + def __init__(self, schema: GraphQLSchema, *args, **kwargs): super().__init__(*args, **kwargs) self._schema = schema - @staticmethod - def _from_config(config: MinosConfig, **kwargs) -> dict[(str, str), Callable]: + @classmethod + def _from_config(cls, config: MinosConfig, **kwargs) -> GraphQlHttpRouter: builder = EnrouteBuilder(*config.services, middleware=config.middleware) routes = builder.get_all(config=config, **kwargs) - graphql_routes = { - GraphQlEnrouteDecorator( - "CreateProduct", request=GraphQLField(GraphQLObjectType), response=GraphQLField(GraphQLObjectType) - ): "fn1", - GraphQlEnrouteDecorator( - "UpdateProduct", request=GraphQLField(GraphQLObjectType), response=GraphQLField(GraphQLObjectType) - ): "fn2", - } + queries = [] + mutations = [] + for route in routes: + if route.KIND == EnrouteDecoratorKind.Query: + queries.append(route) - schema = GraphQLSchema( - query=GraphQLObjectType( - name="RootQueryType", - fields={ - "CreateProduct": GraphQLField( - GraphQLString, args=GraphQLField(GraphQLObjectType), response=GraphQLField(GraphQLObjectType) - ), - "UpdateProduct": GraphQLField( - GraphQLString, args=GraphQLField(GraphQLObjectType), response=GraphQLField(GraphQLObjectType) - ), - }, - ) - ) + if route.KIND == EnrouteDecoratorKind.Command: + mutations.append(route) + from minos.plugins.graphql import GraphQLSchemaBuilder + schema = GraphQLSchemaBuilder.build(queries=queries, mutations=mutations) return cls(schema) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py new file mode 100644 index 000000000..5a5f5962b --- /dev/null +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py @@ -0,0 +1,44 @@ +from __future__ import ( + annotations, +) + +from graphql import GraphQLSchema, GraphQLObjectType, GraphQLField + +from minos.plugins.graphql.decorators import ( + GraphQlQueryEnrouteDecorator, + GraphQlCommandEnrouteDecorator, +) + + +class GraphQLSchemaBuilder: + def __init__(self, *args, **kwargs): + self.schema = GraphQLSchema(**kwargs) + + @classmethod + def build(cls, queries: list, mutations: list) -> GraphQLSchema: + params = dict() + if len(queries) > 0: + params["query"] = GraphQLSchemaBuilder._build_queries(queries) + if len(mutations) > 0: + params["mutation"] = GraphQLSchemaBuilder._build_mutations(mutations) + return cls(**params).schema + + @classmethod + def _build_queries(cls, queries): + fields = {item.name: cls._build_query(item) for item in queries} + + return GraphQLObjectType("Query", fields=fields) + + @staticmethod + def _build_query(item: GraphQlQueryEnrouteDecorator): + return {item.name: GraphQLField(item.argument, resolve=item)} + + @classmethod + def _build_mutations(cls, mutations): + fields = {item.name: cls._build_mutation(item) for item in mutations} + + return GraphQLObjectType("Mutation", fields=fields) + + @staticmethod + def _build_mutation(item: GraphQlCommandEnrouteDecorator): + return {item.name: GraphQLField(item.argument, resolve=item)} diff --git a/packages/plugins/minos-router-graphql/tests/services/commands.py b/packages/plugins/minos-router-graphql/tests/services/commands.py index 7f9713073..71f2a2943 100644 --- a/packages/plugins/minos-router-graphql/tests/services/commands.py +++ b/packages/plugins/minos-router-graphql/tests/services/commands.py @@ -1,3 +1,4 @@ +from graphql import GraphQLField, GraphQLString from minos.networks import ( BrokerResponse, Request, @@ -7,30 +8,14 @@ class CommandService: - @enroute.rest.command(url="/order", method="GET") - def get_order_rest(self, request: Request) -> Response: - return Response("get_order") + @enroute.graphql.command(name="order-command", argument=GraphQLField(GraphQLString), output=GraphQLString) + def create_order(self, request: Request): + """For testng purposes.""" - @enroute.broker.command("GetOrder") - def get_order_command(self, request: Request) -> Response: - return BrokerResponse("get_order") + return "eu38hj32-889283-j2jjb5kl" - @enroute.broker.command("AddOrder") - def add_order(self, request: Request) -> Response: - return BrokerResponse("add_order") + @enroute.graphql.command(name="ticket-command", argument=GraphQLField(GraphQLString), output=GraphQLString) + def create_ticket(self, request: Request): + """For testng purposes.""" - @enroute.broker.command("DeleteOrder") - def delete_order(self, request: Request) -> Response: - return BrokerResponse("delete_order") - - @enroute.broker.command("UpdateOrder") - def update_order(self, request: Request) -> Response: - return BrokerResponse("update_order") - - @enroute.broker.event("TicketAdded") - def ticket_added(self, request: Request) -> str: - return "command_service_ticket_added" - - @enroute.periodic.event("@daily") - def recompute_something(self, request: Request) -> None: - """For testing purposes.""" + return "zdw4gg4g-gser44gkl-jh4j3h4h" diff --git a/packages/plugins/minos-router-graphql/tests/services/queries.py b/packages/plugins/minos-router-graphql/tests/services/queries.py index 726f70b12..54b94e2a0 100644 --- a/packages/plugins/minos-router-graphql/tests/services/queries.py +++ b/packages/plugins/minos-router-graphql/tests/services/queries.py @@ -1,3 +1,4 @@ +from graphql import GraphQLField, GraphQLString from minos.networks import ( Request, Response, @@ -6,14 +7,14 @@ class QueryService: - @enroute.rest.query(url="/ticket", method="POST") - def add_ticket(self, request: Request) -> Response: - return Response("ticket_added") + @enroute.graphql.query(name="order-query", argument=GraphQLField(GraphQLString), output=GraphQLString) + def get_order(self, request: Request): + """For testng purposes.""" - @enroute.broker.event("TicketAdded") - def ticket_added(self, request: Request): - return "query_service_ticket_added" + return "eu38hj32-889283-j2jjb5kl" - @enroute.broker.event("TicketDeleted") - def ticket_deleted(self, request: Request): - return "ticket_deleted" + @enroute.graphql.query(name="ticket-query", argument=GraphQLField(GraphQLString), output=GraphQLString) + def get_ticket(self, request: Request): + """For testng purposes.""" + + return "zdw4gg4g-gser44gkl-jh4j3h4h" diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py new file mode 100644 index 000000000..1568a6597 --- /dev/null +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py @@ -0,0 +1,39 @@ +import unittest + +from graphql import GraphQLField, GraphQLString +from minos.networks import ( + EnrouteAnalyzer, +) +from minos.common import ( + classname, +) + +from minos.plugins.graphql.decorators import GraphQlQueryEnrouteDecorator +from tests.utils import ( + FakeCommandService, + FakeQueryService, +) +from minos.plugins.graphql import ( + GraphQlEnroute, +) + + +class TestSomething(unittest.TestCase): + + def test_decorated_str(self): + analyzer = EnrouteAnalyzer(classname(FakeQueryService)) + self.assertEqual(FakeQueryService, analyzer.decorated) + + def test_get_all_queries(self): + analyzer = EnrouteAnalyzer(FakeQueryService) + observed = analyzer.get_all() + + expected = { + "get_order": {GraphQlQueryEnrouteDecorator(name="order", argument=GraphQLField(GraphQLString), output=GraphQLString)}, + } + + self.assertEqual(expected, observed) + + +if __name__ == '__main__': + unittest.main() diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py new file mode 100644 index 000000000..4674c92a9 --- /dev/null +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py @@ -0,0 +1,27 @@ +import unittest + +from minos.common import MinosConfig + +from minos.plugins.graphql import ( + GraphQlEnroute, + GraphQlHttpRouter, +) +from tests.utils import ( + BASE_PATH, +) +from minos.common.testing import ( + PostgresAsyncTestCase, +) + + +class TestSomething(unittest.TestCase): + CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" + _config = MinosConfig(CONFIG_FILE_PATH) + + def test_true(self): + router = GraphQlHttpRouter.from_config(config=self._config) + self.assertTrue(GraphQlEnroute) + + +if __name__ == '__main__': + unittest.main() diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py new file mode 100644 index 000000000..bc9d7fc9a --- /dev/null +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py @@ -0,0 +1,19 @@ +import unittest + +from minos.plugins.graphql import ( + GraphQlEnroute, GraphQLSchemaBuilder, +) + + +class TestSomething(unittest.TestCase): + + def test_true(self): + mutations = [GraphQlCommandEnrouteDecorator('order-command'), GraphQlCommandEnrouteDecorator('ticket-command')] + queries = [GraphQlQueryEnrouteDecorator('order-query'), GraphQlQueryEnrouteDecorator('ticket-query')] + + schema = GraphQLSchemaBuilder.build(queries=queries, mutations=mutations) + self.assertTrue(GraphQlEnroute) + + +if __name__ == '__main__': + unittest.main() diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_something.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_something.py deleted file mode 100644 index 41c4b4605..000000000 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_something.py +++ /dev/null @@ -1,15 +0,0 @@ -import unittest - -from minos.plugins.graphql import ( - GraphQlEnroute, -) - - -class TestSomething(unittest.TestCase): - - def test_true(self): - self.assertTrue(GraphQlEnroute) - - -if __name__ == '__main__': - unittest.main() diff --git a/packages/plugins/minos-router-graphql/tests/utils.py b/packages/plugins/minos-router-graphql/tests/utils.py index 51ecad270..d2d98e20f 100644 --- a/packages/plugins/minos-router-graphql/tests/utils.py +++ b/packages/plugins/minos-router-graphql/tests/utils.py @@ -8,6 +8,7 @@ Any, ) +from graphql import GraphQLObjectType, GraphQLField, GraphQLString, GraphQLArgument from minos.common import ( DeclarativeModel, ) @@ -35,24 +36,30 @@ def __lt__(self, other: Any) -> bool: class FakeCommandService: """For testng purposes.""" - # noinspection PyUnusedLocal - @enroute.rest.command(url="/order", method="GET") - def get_order_rest(self, request: Request) -> Response: - """For testng purposes.""" - - return Response("get_order") - - @enroute.broker.command("GetOrder") + #@enroute.broker.command("GetOrder") def get_order_command(self, request: Request) -> Response: """For testng purposes.""" return Response("get_order_command") - + """ @enroute.graphql.command( - "create_product", - # args={"request": GraphQLArgument(GraphQlObject({"name": GraphQLString, "surname": GraphQLString}))}, - # response=GrapqlUUID + args={"request": GraphQLArgument(GraphQlObject({"name": GraphQLString, "surname": GraphQLString}))}, + response=GrapqlUUID ) + """ def get_hero(self, request: Request) -> Response: """For testng purposes.""" uuid = request.content() return Response(uuid) + + + +class FakeQueryService: + """For testng purposes.""" + + # noinspection PyUnusedLocal + @enroute.graphql.query(name="order", argument=GraphQLField(GraphQLString), output=GraphQLString) + def get_order(self, request: Request): + """For testng purposes.""" + + return "eu38hj32-889283-j2jjb5kl" + From 018077cc52512d40bb2380d016e210515c53514d Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Mon, 14 Mar 2022 20:40:58 +0100 Subject: [PATCH 17/44] ISSUE #149 --- .../minos/plugins/graphql/decorators.py | 15 +- .../minos/plugins/graphql/exceptions.py | 7 - .../plugins/graphql/handlers/__init__.py | 6 - .../minos/plugins/graphql/handlers/abc.py | 100 -------- .../plugins/graphql/handlers/graphiql.py | 11 - .../minos/plugins/graphql/handlers/graphql.py | 17 -- .../minos/plugins/graphql/main.py | 15 -- .../minos/plugins/graphql/routers.py | 69 +++-- .../minos/plugins/graphql/schema_builder.py | 84 ++++-- .../minos/plugins/graphql/services.py | 56 ---- .../graphql/star_wars_example/__init__.py | 7 - .../plugins/graphql/star_wars_example/data.py | 151 ----------- .../graphql/star_wars_example/schema.py | 241 ------------------ .../plugins/graphql/templates/graphiql.html | 139 ---------- .../tests/services/commands.py | 10 +- .../tests/services/queries.py | 14 +- .../tests/test_graphql/test_decorators.py | 28 +- .../tests/test_graphql/test_routers.py | 19 +- .../tests/test_graphql/test_schema_builder.py | 89 ++++++- .../minos-router-graphql/tests/utils.py | 14 +- 20 files changed, 255 insertions(+), 837 deletions(-) delete mode 100644 packages/plugins/minos-router-graphql/minos/plugins/graphql/exceptions.py delete mode 100644 packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/__init__.py delete mode 100644 packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/abc.py delete mode 100644 packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/graphiql.py delete mode 100644 packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/graphql.py delete mode 100644 packages/plugins/minos-router-graphql/minos/plugins/graphql/main.py delete mode 100644 packages/plugins/minos-router-graphql/minos/plugins/graphql/services.py delete mode 100644 packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/__init__.py delete mode 100644 packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/data.py delete mode 100644 packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/schema.py delete mode 100644 packages/plugins/minos-router-graphql/minos/plugins/graphql/templates/graphiql.html diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py index 8c806873d..c0243708a 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py @@ -1,7 +1,11 @@ from abc import ( ABC, ) +from collections import ( + defaultdict, +) from collections.abc import ( + Collection, Iterable, ) from typing import ( @@ -13,12 +17,6 @@ EnrouteDecoratorKind, enroute, ) -from collections.abc import ( - Collection, -) -from collections import ( - defaultdict, -) class GraphQlEnrouteDecorator(EnrouteDecorator, ABC): @@ -32,11 +30,6 @@ def __init__(self, name: str, argument, output): def __iter__(self) -> Iterable: yield from (self.name,) - def _validate_not_redefined(self, decorators: Collection[EnrouteDecorator]) -> None: - mapper = defaultdict(set) - for decorator in decorators: - mapper[tuple(decorator)].add(decorator) - class GraphQlCommandEnrouteDecorator(GraphQlEnrouteDecorator): """GraphQl Command Enroute class""" diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/exceptions.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/exceptions.py deleted file mode 100644 index e32f39495..000000000 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/exceptions.py +++ /dev/null @@ -1,7 +0,0 @@ -from minos.networks import ( - ResponseException, -) - - -class RestResponseException(ResponseException): - """Rest Response Exception class.""" diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/__init__.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/__init__.py deleted file mode 100644 index 6a3937e81..000000000 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .graphiql import ( - GraphiqlHandler, -) -from .graphql import ( - GraphqlHandler, -) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/abc.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/abc.py deleted file mode 100644 index 18dd60455..000000000 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/abc.py +++ /dev/null @@ -1,100 +0,0 @@ -import json -import logging -import urllib - -import aiohttp -from aiohttp import ( - web, -) -from graphql import ( - graphql, -) - -from minos.plugins.graphql_aiohttp.star_wars_example import ( - star_wars_schema, -) - - -class BaseHandler(web.View): - @property - def fetch(self): - return self.app.fetch - - @property - def app(self): - return self.request.app - - async def options(self): - return web.Response( - status=204, - headers={ - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH", - "Access-Control-Allow-Headers": "x-requested-with,access-control-allow-origin,authorization,content-type", - }, - ) - - -class GQLBaseHandler(BaseHandler): - async def get(self): - return await self.post() - - async def post(self): - return await self.handle_graqhql() - - async def handle_graqhql(self): - status = 200 - result = await self.execute_graphql() - logging.debug( - "GraphQL result data: %s errors: %s", - result.data, - result.errors, - ) - - if result and result.errors: - status = 500 - - errors = result.errors - - if errors is None: - errors = [] - - return web.json_response( - {"data": result.data, "errors": [err.message for err in errors]}, - status=status, - headers={"Access-Control-Allow-Origin": "*"}, - ) - - async def execute_graphql(self): - graphql_req = await self.graphql_request - context_value = graphql_req.get("context", {}) - variables = graphql_req.get("variables", {}) - - context_value["application"] = self.app - source = graphql_req["query"] - result = await graphql( - schema=star_wars_schema, - source=source, - context_value=context_value, - variable_values=variables, - ) - - ref = self.request.headers.get("referer") - url_path = "" - - if ref: - url = urllib.parse.urlparse(ref) - url_path = url.path - - if result.errors: - if "/graphiql" not in url_path: - aiohttp.log.server_logger.error(f'Graphql query error: for query "{graphql_req}"') - - return result - - @property - async def graphql_request(self): - if self.request.method == "GET": - return json.loads(self.request.query["q"]) - - return await self.request.json() diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/graphiql.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/graphiql.py deleted file mode 100644 index 3cf938e43..000000000 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/graphiql.py +++ /dev/null @@ -1,11 +0,0 @@ -import aiohttp_jinja2 - -from .abc import ( - BaseHandler, -) - - -class GraphiqlHandler(BaseHandler): - @aiohttp_jinja2.template("graphiql.html") - async def get(self): - return {"base_url": f"/graphql"} diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/graphql.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/graphql.py deleted file mode 100644 index 2e747a72e..000000000 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers/graphql.py +++ /dev/null @@ -1,17 +0,0 @@ -from graphql.utilities import ( - print_schema, -) - -from minos.plugins.graphql_aiohttp.star_wars_example import ( - star_wars_schema, -) - -from .abc import ( - GQLBaseHandler, -) - - -class GraphqlHandler(GQLBaseHandler): - @property - def schema(self): - return print_schema(star_wars_schema) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/main.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/main.py deleted file mode 100644 index 36c8eb2cf..000000000 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/main.py +++ /dev/null @@ -1,15 +0,0 @@ -from aiohttp import ( - web, -) - -from minos.plugins.graphql.services import ( - RestService, -) - - -def main(): - web.run_app(RestService().create_application()) - - -if __name__ == "__main__": - main() diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py index 0110ebb1a..329dfb8e5 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py @@ -2,11 +2,17 @@ annotations, ) +import json from typing import ( Callable, ) -from graphql import GraphQLSchema +from graphql import ( + GraphQLSchema, + graphql, + print_schema, +) + from minos.common import ( MinosConfig, ) @@ -16,7 +22,6 @@ HttpRequest, HttpResponse, HttpRouter, - EnrouteDecoratorKind, ) @@ -32,16 +37,11 @@ def _from_config(cls, config: MinosConfig, **kwargs) -> GraphQlHttpRouter: builder = EnrouteBuilder(*config.services, middleware=config.middleware) routes = builder.get_all(config=config, **kwargs) - queries = [] - mutations = [] - for route in routes: - if route.KIND == EnrouteDecoratorKind.Query: - queries.append(route) + from minos.plugins.graphql import ( + GraphQLSchemaBuilder, + ) - if route.KIND == EnrouteDecoratorKind.Command: - mutations.append(route) - from minos.plugins.graphql import GraphQLSchemaBuilder - schema = GraphQLSchemaBuilder.build(queries=queries, mutations=mutations) + schema = GraphQLSchemaBuilder.build(routes) return cls(schema) @@ -50,18 +50,51 @@ def routes(self) -> dict[HttpEnrouteDecorator, Callable]: """TODO""" return { HttpEnrouteDecorator("/graphql", "POST"): self.handle_post, - HttpEnrouteDecorator("/graphql", "GET"): self.handle_get, + HttpEnrouteDecorator("/graphql/schema", "GET"): self.handle_schema_get, } async def handle_post(self, request: HttpRequest) -> HttpResponse: """TODO""" - graphql_query = await request.content() + status = 200 + result = await self.execute_graphql() + + if result and result.errors: + status = 500 + + errors = result.errors + + if errors is None: + errors = [] - content = await self._schema(graphql_query) + return HttpResponse( + {"data": result.data, "errors": [err.message for err in errors]}, + status=status, + headers={"Access-Control-Allow-Origin": "*"}, + ) + + async def execute_graphql(self): + graphql_req = await self.graphql_request + context_value = graphql_req.get("context", {}) + variables = graphql_req.get("variables", {}) + + # context_value["application"] = self.app + source = graphql_req["query"] + result = await graphql( + schema=self._schema, + source=source, + context_value=context_value, + variable_values=variables, + ) + + return result + + @property + async def graphql_request(self): + if self.request.method == "GET": + return json.loads(self.request.query["q"]) - return HttpResponse(content) + return await self.request.json() - async def handle_get(self, request: HttpRequest) -> HttpResponse: + async def handle_schema_get(self, request: HttpRequest) -> HttpResponse: """TODO""" - content = self._schema.json() - return HttpResponse(content) + return HttpResponse(print_schema(self._schema)) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py index 5a5f5962b..57be9dd00 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py @@ -2,11 +2,23 @@ annotations, ) -from graphql import GraphQLSchema, GraphQLObjectType, GraphQLField +from functools import ( + wraps, +) +from inspect import ( + isawaitable, +) + +from graphql import ( + GraphQLArgument, + GraphQLField, + GraphQLObjectType, + GraphQLSchema, +) -from minos.plugins.graphql.decorators import ( - GraphQlQueryEnrouteDecorator, - GraphQlCommandEnrouteDecorator, +from minos.networks import ( + EnrouteDecoratorKind, + InMemoryRequest, ) @@ -15,30 +27,60 @@ def __init__(self, *args, **kwargs): self.schema = GraphQLSchema(**kwargs) @classmethod - def build(cls, queries: list, mutations: list) -> GraphQLSchema: - params = dict() - if len(queries) > 0: - params["query"] = GraphQLSchemaBuilder._build_queries(queries) - if len(mutations) > 0: - params["mutation"] = GraphQLSchemaBuilder._build_mutations(mutations) - return cls(**params).schema + def build(cls, routes) -> GraphQLSchema: + schema_args = cls._build(routes) + return cls(**schema_args).schema @classmethod - def _build_queries(cls, queries): - fields = {item.name: cls._build_query(item) for item in queries} + def _build(cls, routes) -> dict: + query = cls._build_queries(routes) + mutation = cls._build_mutations(routes) - return GraphQLObjectType("Query", fields=fields) + return {"query": query, "mutation": mutation} @staticmethod - def _build_query(item: GraphQlQueryEnrouteDecorator): - return {item.name: GraphQLField(item.argument, resolve=item)} + def adapt_callback(callback): + @wraps(callback) + async def _wrapper(_source, _info, request): + request = InMemoryRequest(request) + + response = callback(request) + if isawaitable(response): + response = await response + + return await response.content() + + return _wrapper @classmethod - def _build_mutations(cls, mutations): - fields = {item.name: cls._build_mutation(item) for item in mutations} + def _build_queries(cls, routes): + fields = dict() + for route, callback in routes.items(): + callback = cls.adapt_callback(callback) + if route.KIND == EnrouteDecoratorKind.Query: + fields[route.name] = cls._build_field(route, callback) + + result = None + if len(fields) > 0: + result = GraphQLObjectType("Query", fields=fields) + + return result + + @classmethod + def _build_mutations(cls, routes): + fields = dict() + for route, callback in routes.items(): + callback = cls.adapt_callback(callback) + + if route.KIND == EnrouteDecoratorKind.Command: + fields[route.name] = cls._build_field(route, callback) + + result = None + if len(fields) > 0: + result = GraphQLObjectType("Mutation", fields=fields) - return GraphQLObjectType("Mutation", fields=fields) + return result @staticmethod - def _build_mutation(item: GraphQlCommandEnrouteDecorator): - return {item.name: GraphQLField(item.argument, resolve=item)} + def _build_field(item, callback): + return GraphQLField(item.output, args={"request": GraphQLArgument(item.argument)}, resolve=callback) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/services.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/services.py deleted file mode 100644 index e8bca0f69..000000000 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/services.py +++ /dev/null @@ -1,56 +0,0 @@ -import aiohttp_jinja2 -import jinja2 -from aiohttp import ( - web, -) -from aiomisc.service.aiohttp import ( - AIOHTTPService, -) - -from .handlers import ( - GraphiqlHandler, - GraphqlHandler, -) - - -class RestService(AIOHTTPService): - """ - Rest Interface - - Expose REST Interface handler using aiomisc AIOHTTPService. - - """ - - def __init__(self, **kwargs): - # self.handler = RestHandler.from_config(**kwargs) - # super().__init__(**(kwargs | {"address": self.handler.host, "port": self.handler.port})) - super().__init__(**(kwargs | {"address": "localhost", "port": 7030})) - - """ - def _mount_graphql(self, app: web.Application): - GraphQLView.attach(app, schema=schema, graphiql=True) - """ - - def register_handlers(self, app: web.Application): - routes = [(r"/graphql", GraphqlHandler)] - - # if self.dev: - routes += [(r"/graphiql", GraphiqlHandler)] - - app.router.add_routes([web.view(route[0], route[1]) for route in routes]) - - async def initialize_jinja2(self, app: web.Application): - aiohttp_jinja2.setup(app, loader=jinja2.FileSystemLoader("./templates")) - - async def create_application(self) -> web.Application: - """Create the web application. - - :return: A ``web.Application`` instance. - """ - app = web.Application() - self.register_handlers(app) - await self.initialize_jinja2(app) - # self._mount_routes(app) - # self._mount_graphql(app) - return app - # return self.handler.get_app() # pragma: no cover diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/__init__.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/__init__.py deleted file mode 100644 index 5bcaa3cb0..000000000 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -__author__ = "Minos Framework Devs" -__email__ = "hey@minos.run" -__version__ = "0.6.0" - -from .schema import ( - star_wars_schema, -) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/data.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/data.py deleted file mode 100644 index 65aa5f650..000000000 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/data.py +++ /dev/null @@ -1,151 +0,0 @@ -"""This defines a basic set of data for our Star Wars Schema. - -This data is hard coded for the sake of the demo, but you could imagine fetching this -data from a backend service rather than from hardcoded JSON objects in a more complex -demo. -""" - -from typing import ( - Awaitable, - Collection, - Dict, - Iterator, - Optional, -) - -__all__ = ["get_droid", "get_friends", "get_hero", "get_human", "get_secret_backstory"] - -# These are classes which correspond to the schema. -# They represent the shape of the data visited during field resolution. - - -class Character: - id: str - name: str - friends: Collection[str] - appearsIn: Collection[str] - - -# noinspection PyPep8Naming -class Human(Character): - type = "Human" - homePlanet: str - - # noinspection PyShadowingBuiltins - def __init__(self, id, name, friends, appearsIn, homePlanet): - self.id, self.name = id, name - self.friends, self.appearsIn = friends, appearsIn - self.homePlanet = homePlanet - - -# noinspection PyPep8Naming -class Droid(Character): - type = "Droid" - primaryFunction: str - - # noinspection PyShadowingBuiltins - def __init__(self, id, name, friends, appearsIn, primaryFunction): - self.id, self.name = id, name - self.friends, self.appearsIn = friends, appearsIn - self.primaryFunction = primaryFunction - - -luke = Human( - id="1000", - name="Luke Skywalker", - friends=["1002", "1003", "2000", "2001"], - appearsIn=[4, 5, 6], - homePlanet="Tatooine", -) - -vader = Human( - id="1001", - name="Darth Vader", - friends=["1004"], - appearsIn=[4, 5, 6], - homePlanet="Tatooine", -) - -han = Human( - id="1002", - name="Han Solo", - friends=["1000", "1003", "2001"], - appearsIn=[4, 5, 6], - homePlanet=None, -) - -leia = Human( - id="1003", - name="Leia Organa", - friends=["1000", "1002", "2000", "2001"], - appearsIn=[4, 5, 6], - homePlanet="Alderaan", -) - -tarkin = Human(id="1004", name="Wilhuff Tarkin", friends=["1001"], appearsIn=[4], homePlanet=None) - -human_data: Dict[str, Human] = { - "1000": luke, - "1001": vader, - "1002": han, - "1003": leia, - "1004": tarkin, -} - -threepio = Droid( - id="2000", - name="C-3PO", - friends=["1000", "1002", "1003", "2001"], - appearsIn=[4, 5, 6], - primaryFunction="Protocol", -) - -artoo = Droid( - id="2001", - name="R2-D2", - friends=["1000", "1002", "1003"], - appearsIn=[4, 5, 6], - primaryFunction="Astromech", -) - -droid_data: Dict[str, Droid] = {"2000": threepio, "2001": artoo} - - -# noinspection PyShadowingBuiltins -async def get_character(id: str) -> Optional[Character]: - """Helper function to get a character by ID.""" - # We use an async function just to illustrate that GraphQL-core supports it. - return human_data.get(id) or droid_data.get(id) - - -def get_friends(character: Character) -> Iterator[Awaitable[Optional[Character]]]: - """Allows us to query for a character's friends.""" - # Notice that GraphQL-core accepts iterators of awaitables. - return map(get_character, character.friends) - - -def get_hero(episode: int) -> Character: - """Allows us to fetch the undisputed hero of the trilogy, R2-D2.""" - if episode == 5: - # Luke is the hero of Episode V. - return luke - # Artoo is the hero otherwise. - return artoo - - -# noinspection PyShadowingBuiltins -def get_human(id: str) -> Optional[Human]: - """Allows us to query for the human with the given id.""" - return human_data.get(id) - - -# noinspection PyShadowingBuiltins -def get_droid(id: str) -> Optional[Droid]: - """Allows us to query for the droid with the given id.""" - return droid_data.get(id) - - -# noinspection PyUnusedLocal -def get_secret_backstory(character: Character) -> str: - """Raise an error when attempting to get the secret backstory.""" - raise RuntimeError("secretBackstory is secret.") diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/schema.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/schema.py deleted file mode 100644 index b6d9068aa..000000000 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/star_wars_example/schema.py +++ /dev/null @@ -1,241 +0,0 @@ -"""Star Wars GraphQL schema - -This is designed to be an end-to-end test, demonstrating the full GraphQL stack. - -We will create a GraphQL schema that describes the major characters in the original -Star Wars trilogy. - -NOTE: This may contain spoilers for the original Star Wars trilogy. - -Using our shorthand to describe type systems, the type system for our Star Wars example -is:: - - enum Episode { NEW_HOPE, EMPIRE, JEDI } - - interface Character { - id: String! - name: String - friends: [Character] - appearsIn: [Episode] - } - - type Human implements Character { - id: String! - name: String - friends: [Character] - appearsIn: [Episode] - homePlanet: String - } - - type Droid implements Character { - id: String! - name: String - friends: [Character] - appearsIn: [Episode] - primaryFunction: String - } - - type Query { - hero(episode: Episode): Character - human(id: String!): Human - droid(id: String!): Droid - } -""" -import asyncio - -from graphql.type import ( - GraphQLArgument, - GraphQLEnumType, - GraphQLEnumValue, - GraphQLField, - GraphQLInterfaceType, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLSchema, - GraphQLString, -) - -from .data import ( - get_droid, - get_friends, - get_hero, - get_human, - get_secret_backstory, -) - -__all__ = ["star_wars_schema"] - -# We begin by setting up our schema. - -# The original trilogy consists of three movies. -# -# This implements the following type system shorthand: -# enum Episode { NEW_HOPE, EMPIRE, JEDI } - -episode_enum = GraphQLEnumType( - "Episode", - { - "NEW_HOPE": GraphQLEnumValue(4, description="Released in 1977."), - "EMPIRE": GraphQLEnumValue(5, description="Released in 1980."), - "JEDI": GraphQLEnumValue(6, description="Released in 1983."), - }, - description="One of the films in the Star Wars Trilogy", -) - -# Characters in the Star Wars trilogy are either humans or droids. -# -# This implements the following type system shorthand: -# interface Character { -# id: String! -# name: String -# friends: [Character] -# appearsIn: [Episode] -# secretBackstory: String - -human_type: GraphQLObjectType -droid_type: GraphQLObjectType - -character_interface: GraphQLInterfaceType = GraphQLInterfaceType( - "Character", - lambda: { - "id": GraphQLField(GraphQLNonNull(GraphQLString), description="The id of the character."), - "name": GraphQLField(GraphQLString, description="The name of the character."), - "friends": GraphQLField( - GraphQLList(character_interface), - description="The friends of the character," " or an empty list if they have none.", - ), - "appearsIn": GraphQLField(GraphQLList(episode_enum), description="Which movies they appear in."), - "secretBackstory": GraphQLField(GraphQLString, description="All secrets about their past."), - }, - resolve_type=lambda character, _info, _type: { - "Human": human_type.name, - "Droid": droid_type.name, - }[character.type], - description="A character in the Star Wars Trilogy", -) - -# We define our human type, which implements the character interface. -# -# This implements the following type system shorthand: -# type Human : Character { -# id: String! -# name: String -# friends: [Character] -# appearsIn: [Episode] -# secretBackstory: String -# } - -human_type = GraphQLObjectType( - "Human", - lambda: { - "id": GraphQLField(GraphQLNonNull(GraphQLString), description="The id of the human."), - "name": GraphQLField(GraphQLString, description="The name of the human."), - "friends": GraphQLField( - GraphQLList(character_interface), - description="The friends of the human," " or an empty list if they have none.", - resolve=lambda human, _info: get_friends(human), - ), - "appearsIn": GraphQLField(GraphQLList(episode_enum), description="Which movies they appear in."), - "homePlanet": GraphQLField( - GraphQLString, - description="The home planet of the human, or null if unknown.", - ), - "secretBackstory": GraphQLField( - GraphQLString, - resolve=lambda human, _info: get_secret_backstory(human), - description="Where are they from and how they came to be who they are.", - ), - }, - interfaces=[character_interface], - description="A humanoid creature in the Star Wars universe.", -) - -# The other type of character in Star Wars is a droid. -# -# This implements the following type system shorthand: -# type Droid : Character { -# id: String! -# name: String -# friends: [Character] -# appearsIn: [Episode] -# secretBackstory: String -# primaryFunction: String -# } - -droid_type = GraphQLObjectType( - "Droid", - lambda: { - "id": GraphQLField(GraphQLNonNull(GraphQLString), description="The id of the droid."), - "name": GraphQLField(GraphQLString, description="The name of the droid."), - "friends": GraphQLField( - GraphQLList(character_interface), - description="The friends of the droid," " or an empty list if they have none.", - resolve=lambda droid, _info: get_friends(droid), - ), - "appearsIn": GraphQLField(GraphQLList(episode_enum), description="Which movies they appear in."), - "secretBackstory": GraphQLField( - GraphQLString, - resolve=lambda droid, _info: get_secret_backstory(droid), - description="Construction date and the name of the designer.", - ), - "primaryFunction": GraphQLField(GraphQLString, description="The primary function of the droid."), - }, - interfaces=[character_interface], - description="A mechanical creature in the Star Wars universe.", -) - -# This is the type that will be the root of our query, and the -# entry point into our schema. It gives us the ability to fetch -# objects by their IDs, as well as to fetch the undisputed hero -# of the Star Wars trilogy, R2-D2, directly. -# -# This implements the following type system shorthand: -# type Query { -# hero(episode: Episode): Character -# human(id: String!): Human -# droid(id: String!): Droid -# } - -# noinspection PyShadowingBuiltins -query_type = GraphQLObjectType( - "Query", - lambda: { - "hero": GraphQLField( - character_interface, - args={ - "episode": GraphQLArgument( - episode_enum, - description=( - "If omitted, returns the hero of the whole saga." - " If provided, returns the hero of that particular episode." - ), - ) - }, - resolve=lambda _source, _info, episode=None: get_hero(episode), - ), - "human": GraphQLField( - human_type, - args={"id": GraphQLArgument(GraphQLNonNull(GraphQLString), description="id of the human")}, - resolve=lambda _source, _info, id: get_human(id), - ), - "droid": GraphQLField( - droid_type, - args={"id": GraphQLArgument(GraphQLNonNull(GraphQLString), description="id of the droid")}, - resolve=lambda _source, _info, id: get_droid(id), - ), - }, -) - - -async def resolve_hello(obj, info): - await asyncio.sleep(3) - return "world" - - -mutation_type = GraphQLObjectType("Mutation", fields={"hello": GraphQLField(GraphQLString, resolve=resolve_hello)}) - -# Finally, we construct our schema (whose starting query type is the query -# type we defined above) and export it. - -star_wars_schema = GraphQLSchema(query=query_type, mutation=mutation_type, types=[human_type, droid_type]) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/templates/graphiql.html b/packages/plugins/minos-router-graphql/minos/plugins/graphql/templates/graphiql.html deleted file mode 100644 index 0a90e30e2..000000000 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/templates/graphiql.html +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - - - - - - - - -
Loading...
- - - diff --git a/packages/plugins/minos-router-graphql/tests/services/commands.py b/packages/plugins/minos-router-graphql/tests/services/commands.py index 71f2a2943..318f10354 100644 --- a/packages/plugins/minos-router-graphql/tests/services/commands.py +++ b/packages/plugins/minos-router-graphql/tests/services/commands.py @@ -1,4 +1,8 @@ -from graphql import GraphQLField, GraphQLString +from graphql import ( + GraphQLField, + GraphQLString, +) + from minos.networks import ( BrokerResponse, Request, @@ -8,13 +12,13 @@ class CommandService: - @enroute.graphql.command(name="order-command", argument=GraphQLField(GraphQLString), output=GraphQLString) + @enroute.graphql.command(name="order_command", argument=GraphQLString, output=GraphQLString) def create_order(self, request: Request): """For testng purposes.""" return "eu38hj32-889283-j2jjb5kl" - @enroute.graphql.command(name="ticket-command", argument=GraphQLField(GraphQLString), output=GraphQLString) + @enroute.graphql.command(name="ticket_command", argument=GraphQLString, output=GraphQLString) def create_ticket(self, request: Request): """For testng purposes.""" diff --git a/packages/plugins/minos-router-graphql/tests/services/queries.py b/packages/plugins/minos-router-graphql/tests/services/queries.py index 54b94e2a0..30c04c737 100644 --- a/packages/plugins/minos-router-graphql/tests/services/queries.py +++ b/packages/plugins/minos-router-graphql/tests/services/queries.py @@ -1,4 +1,8 @@ -from graphql import GraphQLField, GraphQLString +from graphql import ( + GraphQLField, + GraphQLString, +) + from minos.networks import ( Request, Response, @@ -7,14 +11,14 @@ class QueryService: - @enroute.graphql.query(name="order-query", argument=GraphQLField(GraphQLString), output=GraphQLString) + @enroute.graphql.query(name="order_query", argument=GraphQLString, output=GraphQLString) def get_order(self, request: Request): """For testng purposes.""" - return "eu38hj32-889283-j2jjb5kl" + return Response("eu38hj32-889283-j2jjb5kl") - @enroute.graphql.query(name="ticket-query", argument=GraphQLField(GraphQLString), output=GraphQLString) + @enroute.graphql.query(name="ticket_query", argument=GraphQLString, output=GraphQLString) def get_ticket(self, request: Request): """For testng purposes.""" - return "zdw4gg4g-gser44gkl-jh4j3h4h" + return Response("zdw4gg4g-gser44gkl-jh4j3h4h") diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py index 1568a6597..a93afd5ba 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py @@ -1,25 +1,29 @@ import unittest -from graphql import GraphQLField, GraphQLString -from minos.networks import ( - EnrouteAnalyzer, +from graphql import ( + GraphQLField, + GraphQLString, ) + from minos.common import ( classname, ) - -from minos.plugins.graphql.decorators import GraphQlQueryEnrouteDecorator -from tests.utils import ( - FakeCommandService, - FakeQueryService, +from minos.networks import ( + EnrouteAnalyzer, ) from minos.plugins.graphql import ( GraphQlEnroute, ) +from minos.plugins.graphql.decorators import ( + GraphQlQueryEnrouteDecorator, +) +from tests.utils import ( + FakeCommandService, + FakeQueryService, +) class TestSomething(unittest.TestCase): - def test_decorated_str(self): analyzer = EnrouteAnalyzer(classname(FakeQueryService)) self.assertEqual(FakeQueryService, analyzer.decorated) @@ -29,11 +33,13 @@ def test_get_all_queries(self): observed = analyzer.get_all() expected = { - "get_order": {GraphQlQueryEnrouteDecorator(name="order", argument=GraphQLField(GraphQLString), output=GraphQLString)}, + "get_order": { + GraphQlQueryEnrouteDecorator(name="order", argument=GraphQLField(GraphQLString), output=GraphQLString) + }, } self.assertEqual(expected, observed) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py index 4674c92a9..7a1be2d05 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py @@ -1,7 +1,13 @@ import unittest -from minos.common import MinosConfig +from graphql import ( + validate_schema, + GraphQLSchema, +) +from minos.common import ( + MinosConfig, +) from minos.plugins.graphql import ( GraphQlEnroute, GraphQlHttpRouter, @@ -9,19 +15,18 @@ from tests.utils import ( BASE_PATH, ) -from minos.common.testing import ( - PostgresAsyncTestCase, -) class TestSomething(unittest.TestCase): CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" _config = MinosConfig(CONFIG_FILE_PATH) - def test_true(self): + def test_from_config(self): router = GraphQlHttpRouter.from_config(config=self._config) - self.assertTrue(GraphQlEnroute) + + self.assertIsInstance(router._schema, GraphQLSchema) + self.assertIsInstance(router, GraphQlHttpRouter) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py index bc9d7fc9a..bcbd99a12 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py @@ -1,19 +1,94 @@ import unittest +from graphql import ( + graphql_sync, + validate_schema, GraphQLString, +) +from minos.networks import ( + Request, + Response, +) +from minos.common import ( + MinosConfig, +) from minos.plugins.graphql import ( - GraphQlEnroute, GraphQLSchemaBuilder, + GraphQlEnroute, + GraphQlHttpRouter, + GraphQLSchemaBuilder, ) +from minos.plugins.graphql.decorators import GraphQlCommandEnrouteDecorator, GraphQlQueryEnrouteDecorator + +from tests.utils import ( + BASE_PATH, +) + + +async def callback_fn(request: Request): + return Response("ticket #4") class TestSomething(unittest.TestCase): + CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" + _config = MinosConfig(CONFIG_FILE_PATH) + + def test_build(self): + routes = { + GraphQlCommandEnrouteDecorator(name="order_command", argument=GraphQLString, + output=GraphQLString): callback_fn, + GraphQlCommandEnrouteDecorator(name="ticket_command", argument=GraphQLString, + output=GraphQLString): callback_fn, + GraphQlQueryEnrouteDecorator(name="order_query", argument=GraphQLString, output=GraphQLString): callback_fn, + GraphQlQueryEnrouteDecorator(name="ticket_query", argument=GraphQLString, output=GraphQLString): callback_fn} + + schema = GraphQLSchemaBuilder.build(routes=routes) + + errors = validate_schema(schema) + + self.assertEqual(errors, []) + + def test_build_only_queries(self): + routes = { + GraphQlQueryEnrouteDecorator(name="order_query", argument=GraphQLString, output=GraphQLString): callback_fn, + GraphQlQueryEnrouteDecorator(name="ticket_query", argument=GraphQLString, output=GraphQLString): callback_fn} + + schema = GraphQLSchemaBuilder.build(routes=routes) + + errors = validate_schema(schema) + + self.assertEqual(errors, []) + + def test_build_only_commands(self): + routes = { + GraphQlCommandEnrouteDecorator(name="order_command", argument=GraphQLString, + output=GraphQLString): callback_fn, + GraphQlCommandEnrouteDecorator(name="ticket_command", argument=GraphQLString, + output=GraphQLString): callback_fn} + + schema = GraphQLSchemaBuilder.build(routes=routes) + + errors = validate_schema(schema) + + self.assertEqual(errors, []) + + def test_schema_valid(self): + router = GraphQlHttpRouter.from_config(config=self._config) + + errors = validate_schema(router._schema) + + self.assertEqual(errors, []) + + def test_query(self): + routes = { + GraphQlQueryEnrouteDecorator(name="order_query", argument=GraphQLString, output=GraphQLString): callback_fn} + + schema = GraphQLSchemaBuilder.build(routes=routes) + + source = "{ order_query }" - def test_true(self): - mutations = [GraphQlCommandEnrouteDecorator('order-command'), GraphQlCommandEnrouteDecorator('ticket-command')] - queries = [GraphQlQueryEnrouteDecorator('order-query'), GraphQlQueryEnrouteDecorator('ticket-query')] + result = graphql_sync(schema, source) - schema = GraphQLSchemaBuilder.build(queries=queries, mutations=mutations) - self.assertTrue(GraphQlEnroute) + self.assertEqual(result.errors, []) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/packages/plugins/minos-router-graphql/tests/utils.py b/packages/plugins/minos-router-graphql/tests/utils.py index d2d98e20f..5a59ee2f0 100644 --- a/packages/plugins/minos-router-graphql/tests/utils.py +++ b/packages/plugins/minos-router-graphql/tests/utils.py @@ -8,7 +8,13 @@ Any, ) -from graphql import GraphQLObjectType, GraphQLField, GraphQLString, GraphQLArgument +from graphql import ( + GraphQLArgument, + GraphQLField, + GraphQLObjectType, + GraphQLString, +) + from minos.common import ( DeclarativeModel, ) @@ -36,23 +42,24 @@ def __lt__(self, other: Any) -> bool: class FakeCommandService: """For testng purposes.""" - #@enroute.broker.command("GetOrder") + # @enroute.broker.command("GetOrder") def get_order_command(self, request: Request) -> Response: """For testng purposes.""" return Response("get_order_command") + """ @enroute.graphql.command( args={"request": GraphQLArgument(GraphQlObject({"name": GraphQLString, "surname": GraphQLString}))}, response=GrapqlUUID ) """ + def get_hero(self, request: Request) -> Response: """For testng purposes.""" uuid = request.content() return Response(uuid) - class FakeQueryService: """For testng purposes.""" @@ -62,4 +69,3 @@ def get_order(self, request: Request): """For testng purposes.""" return "eu38hj32-889283-j2jjb5kl" - From 9f52d752a640c6b5a693e5f26023cfa1a6c4c118 Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Mon, 14 Mar 2022 20:52:17 +0100 Subject: [PATCH 18/44] ISSUE #149 --- .../minos/plugins/graphql/schema_builder.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py index 57be9dd00..5bc97ec28 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py @@ -13,7 +13,7 @@ GraphQLArgument, GraphQLField, GraphQLObjectType, - GraphQLSchema, + GraphQLSchema, GraphQLString, ) from minos.networks import ( @@ -60,7 +60,11 @@ def _build_queries(cls, routes): if route.KIND == EnrouteDecoratorKind.Query: fields[route.name] = cls._build_field(route, callback) - result = None + result = GraphQLObjectType("Query", fields={ + 'hello': GraphQLField( + GraphQLString, + resolve=lambda obj, info: 'world') + }) if len(fields) > 0: result = GraphQLObjectType("Query", fields=fields) From 88edc781f1ac848c042c70aebb0accbe304dd7a9 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Mon, 14 Mar 2022 19:52:26 +0000 Subject: [PATCH 19/44] Restyled by black --- .../minos/plugins/graphql/schema_builder.py | 11 +++--- .../tests/test_graphql/test_schema_builder.py | 37 +++++++++++++------ 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py index 5bc97ec28..29556868e 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py @@ -13,7 +13,8 @@ GraphQLArgument, GraphQLField, GraphQLObjectType, - GraphQLSchema, GraphQLString, + GraphQLSchema, + GraphQLString, ) from minos.networks import ( @@ -60,11 +61,9 @@ def _build_queries(cls, routes): if route.KIND == EnrouteDecoratorKind.Query: fields[route.name] = cls._build_field(route, callback) - result = GraphQLObjectType("Query", fields={ - 'hello': GraphQLField( - GraphQLString, - resolve=lambda obj, info: 'world') - }) + result = GraphQLObjectType( + "Query", fields={"hello": GraphQLField(GraphQLString, resolve=lambda obj, info: "world")} + ) if len(fields) > 0: result = GraphQLObjectType("Query", fields=fields) diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py index bcbd99a12..73fcdfa04 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py @@ -2,7 +2,8 @@ from graphql import ( graphql_sync, - validate_schema, GraphQLString, + validate_schema, + GraphQLString, ) from minos.networks import ( Request, @@ -33,12 +34,17 @@ class TestSomething(unittest.TestCase): def test_build(self): routes = { - GraphQlCommandEnrouteDecorator(name="order_command", argument=GraphQLString, - output=GraphQLString): callback_fn, - GraphQlCommandEnrouteDecorator(name="ticket_command", argument=GraphQLString, - output=GraphQLString): callback_fn, + GraphQlCommandEnrouteDecorator( + name="order_command", argument=GraphQLString, output=GraphQLString + ): callback_fn, + GraphQlCommandEnrouteDecorator( + name="ticket_command", argument=GraphQLString, output=GraphQLString + ): callback_fn, GraphQlQueryEnrouteDecorator(name="order_query", argument=GraphQLString, output=GraphQLString): callback_fn, - GraphQlQueryEnrouteDecorator(name="ticket_query", argument=GraphQLString, output=GraphQLString): callback_fn} + GraphQlQueryEnrouteDecorator( + name="ticket_query", argument=GraphQLString, output=GraphQLString + ): callback_fn, + } schema = GraphQLSchemaBuilder.build(routes=routes) @@ -49,7 +55,10 @@ def test_build(self): def test_build_only_queries(self): routes = { GraphQlQueryEnrouteDecorator(name="order_query", argument=GraphQLString, output=GraphQLString): callback_fn, - GraphQlQueryEnrouteDecorator(name="ticket_query", argument=GraphQLString, output=GraphQLString): callback_fn} + GraphQlQueryEnrouteDecorator( + name="ticket_query", argument=GraphQLString, output=GraphQLString + ): callback_fn, + } schema = GraphQLSchemaBuilder.build(routes=routes) @@ -59,10 +68,13 @@ def test_build_only_queries(self): def test_build_only_commands(self): routes = { - GraphQlCommandEnrouteDecorator(name="order_command", argument=GraphQLString, - output=GraphQLString): callback_fn, - GraphQlCommandEnrouteDecorator(name="ticket_command", argument=GraphQLString, - output=GraphQLString): callback_fn} + GraphQlCommandEnrouteDecorator( + name="order_command", argument=GraphQLString, output=GraphQLString + ): callback_fn, + GraphQlCommandEnrouteDecorator( + name="ticket_command", argument=GraphQLString, output=GraphQLString + ): callback_fn, + } schema = GraphQLSchemaBuilder.build(routes=routes) @@ -79,7 +91,8 @@ def test_schema_valid(self): def test_query(self): routes = { - GraphQlQueryEnrouteDecorator(name="order_query", argument=GraphQLString, output=GraphQLString): callback_fn} + GraphQlQueryEnrouteDecorator(name="order_query", argument=GraphQLString, output=GraphQLString): callback_fn + } schema = GraphQLSchemaBuilder.build(routes=routes) From a8c51a33ad47c914ce48a607ab6e47b37707762d Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Mon, 14 Mar 2022 19:52:27 +0000 Subject: [PATCH 20/44] Restyled by isort --- .../tests/test_graphql/test_routers.py | 2 +- .../tests/test_graphql/test_schema_builder.py | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py index 7a1be2d05..4f88c7c27 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py @@ -1,8 +1,8 @@ import unittest from graphql import ( - validate_schema, GraphQLSchema, + validate_schema, ) from minos.common import ( diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py index 73fcdfa04..59c20ea86 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py @@ -1,24 +1,27 @@ import unittest from graphql import ( + GraphQLString, graphql_sync, validate_schema, - GraphQLString, +) + +from minos.common import ( + MinosConfig, ) from minos.networks import ( Request, Response, ) -from minos.common import ( - MinosConfig, -) from minos.plugins.graphql import ( GraphQlEnroute, GraphQlHttpRouter, GraphQLSchemaBuilder, ) -from minos.plugins.graphql.decorators import GraphQlCommandEnrouteDecorator, GraphQlQueryEnrouteDecorator - +from minos.plugins.graphql.decorators import ( + GraphQlCommandEnrouteDecorator, + GraphQlQueryEnrouteDecorator, +) from tests.utils import ( BASE_PATH, ) From 8d66c495374034ec492fcd4aa03802b3d6ef1ca8 Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Mon, 14 Mar 2022 20:59:54 +0100 Subject: [PATCH 21/44] ISSUE #149 --- .../minos-router-graphql-publish.yml | 33 ++++++++++ .../workflows/minos-router-graphql-tests.yml | 66 +++++++++++++++++++ .../plugins/minos-router-graphql/docs/conf.py | 6 +- 3 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/minos-router-graphql-publish.yml create mode 100644 .github/workflows/minos-router-graphql-tests.yml diff --git a/.github/workflows/minos-router-graphql-publish.yml b/.github/workflows/minos-router-graphql-publish.yml new file mode 100644 index 000000000..0b641d384 --- /dev/null +++ b/.github/workflows/minos-router-graphql-publish.yml @@ -0,0 +1,33 @@ +name: "Publish: minos-router-graphql" + +on: + push: + branches: + - '*.*.x' + paths: + - 'packages/plugins/minos-router-graphql/**' + +jobs: + deploy: + runs-on: ubuntu-latest + container: python:3.9-buster + defaults: + run: + working-directory: packages/plugins/minos-router-graphql + + steps: + + - name: Check out repository code + uses: actions/checkout@v2 + + - name: Install Poetry + uses: snok/install-poetry@v1 + + - name: Install dependencies + run: make install + + - name: Publish package + run: make release + env: + POETRY_HTTP_BASIC_PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} + POETRY_HTTP_BASIC_PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} diff --git a/.github/workflows/minos-router-graphql-tests.yml b/.github/workflows/minos-router-graphql-tests.yml new file mode 100644 index 000000000..54c26751e --- /dev/null +++ b/.github/workflows/minos-router-graphql-tests.yml @@ -0,0 +1,66 @@ +name: "Test: minos-router-graphql" + +on: + push: + branches: + - main + - '*.*.x' + pull_request: + paths: + - 'packages/plugins/minos-router-graphql/**' + - 'packages/core/minos-microservice-networks/**' + - 'packages/core/minos-microservice-common/**' + +jobs: + build: + runs-on: ubuntu-latest + container: python:3.9-buster + defaults: + run: + working-directory: packages/plugins/minos-router-graphql + + services: + postgres: + image: postgres + env: + POSTGRES_USER: minos + POSTGRES_PASSWORD: min0s + POSTGRES_DB: order_db + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + env: + MINOS_BROKER_QUEUE_HOST: postgres + MINOS_BROKER_HOST: kafka + MINOS_REPOSITORY_HOST: postgres + MINOS_SNAPSHOT_HOST: postgres + + steps: + - name: Check out repository code + uses: actions/checkout@v2 + + - name: Install Poetry + uses: snok/install-poetry@v1 + + - name: Install dependencies + run: make install + + - name: Lint package + run: make lint + + - name: Test package with coverage + run: make coverage + + - name: Publish coverage + uses: codecov/codecov-action@v2 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./packages/plugins/minos-http-aiohttp/coverage.xml + fail_ci_if_error: true + + - name: Generate documentation + run: make docs + + - name: Generate build + run: make dist diff --git a/packages/plugins/minos-router-graphql/docs/conf.py b/packages/plugins/minos-router-graphql/docs/conf.py index b83dc06a1..f17c29f90 100755 --- a/packages/plugins/minos-router-graphql/docs/conf.py +++ b/packages/plugins/minos-router-graphql/docs/conf.py @@ -25,7 +25,7 @@ import sphinx_rtd_theme from minos.plugins import ( - rest_aiohttp, + graphql, ) # -- General configuration --------------------------------------------- @@ -67,9 +67,9 @@ # the built documents. # # The short X.Y version. -version = rest_aiohttp.__version__ +version = graphql.__version__ # The full version, including alpha/beta/rc tags. -release = rest_aiohttp.__version__ +release = graphql.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From e9aeea090cbc2ee4b44c7a4226040911d86aa9e5 Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Mon, 14 Mar 2022 21:04:13 +0100 Subject: [PATCH 22/44] ISSUE #149 --- .../minos/plugins/graphql/decorators.py | 4 ---- .../plugins/minos-router-graphql/tests/services/commands.py | 6 ++---- .../plugins/minos-router-graphql/tests/services/queries.py | 1 - .../tests/test_graphql/test_decorators.py | 4 ---- .../minos-router-graphql/tests/test_graphql/test_routers.py | 2 -- .../tests/test_graphql/test_schema_builder.py | 1 - 6 files changed, 2 insertions(+), 16 deletions(-) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py index c0243708a..c48258acc 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py @@ -1,11 +1,7 @@ from abc import ( ABC, ) -from collections import ( - defaultdict, -) from collections.abc import ( - Collection, Iterable, ) from typing import ( diff --git a/packages/plugins/minos-router-graphql/tests/services/commands.py b/packages/plugins/minos-router-graphql/tests/services/commands.py index 318f10354..44a5aa536 100644 --- a/packages/plugins/minos-router-graphql/tests/services/commands.py +++ b/packages/plugins/minos-router-graphql/tests/services/commands.py @@ -1,10 +1,8 @@ from graphql import ( - GraphQLField, GraphQLString, ) from minos.networks import ( - BrokerResponse, Request, Response, enroute, @@ -16,10 +14,10 @@ class CommandService: def create_order(self, request: Request): """For testng purposes.""" - return "eu38hj32-889283-j2jjb5kl" + return Response("eu38hj32-889283-j2jjb5kl") @enroute.graphql.command(name="ticket_command", argument=GraphQLString, output=GraphQLString) def create_ticket(self, request: Request): """For testng purposes.""" - return "zdw4gg4g-gser44gkl-jh4j3h4h" + return Response("zdw4gg4g-gser44gkl-jh4j3h4h") diff --git a/packages/plugins/minos-router-graphql/tests/services/queries.py b/packages/plugins/minos-router-graphql/tests/services/queries.py index 30c04c737..a57c7c3bc 100644 --- a/packages/plugins/minos-router-graphql/tests/services/queries.py +++ b/packages/plugins/minos-router-graphql/tests/services/queries.py @@ -1,5 +1,4 @@ from graphql import ( - GraphQLField, GraphQLString, ) diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py index a93afd5ba..4d8e70eb1 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py @@ -11,14 +11,10 @@ from minos.networks import ( EnrouteAnalyzer, ) -from minos.plugins.graphql import ( - GraphQlEnroute, -) from minos.plugins.graphql.decorators import ( GraphQlQueryEnrouteDecorator, ) from tests.utils import ( - FakeCommandService, FakeQueryService, ) diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py index 4f88c7c27..08333caad 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py @@ -2,14 +2,12 @@ from graphql import ( GraphQLSchema, - validate_schema, ) from minos.common import ( MinosConfig, ) from minos.plugins.graphql import ( - GraphQlEnroute, GraphQlHttpRouter, ) from tests.utils import ( diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py index 59c20ea86..e0d9278a3 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py @@ -14,7 +14,6 @@ Response, ) from minos.plugins.graphql import ( - GraphQlEnroute, GraphQlHttpRouter, GraphQLSchemaBuilder, ) From 22915d7c01ca0292d69eea45671375a8aba36122 Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Mon, 14 Mar 2022 21:15:11 +0100 Subject: [PATCH 23/44] ISSUE #149 --- .../plugins/minos-router-graphql/poetry.lock | 22 +++---------------- .../minos-router-graphql/pyproject.toml | 2 -- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/packages/plugins/minos-router-graphql/poetry.lock b/packages/plugins/minos-router-graphql/poetry.lock index 836ad91c6..1bfd569c3 100644 --- a/packages/plugins/minos-router-graphql/poetry.lock +++ b/packages/plugins/minos-router-graphql/poetry.lock @@ -18,18 +18,6 @@ yarl = ">=1.0,<2.0" [package.extras] speedups = ["aiodns", "brotli", "cchardet"] -[[package]] -name = "aiohttp-jinja2" -version = "1.5" -description = "jinja2 template renderer for aiohttp.web (http server for asyncio)" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -aiohttp = ">=3.6.3" -jinja2 = ">=3.0.0" - [[package]] name = "aiomisc" version = "15.7.2" @@ -392,7 +380,7 @@ plugins = ["setuptools"] name = "jinja2" version = "3.0.3" description = "A very fast and expressive template engine." -category = "main" +category = "dev" optional = false python-versions = ">=3.6" @@ -426,7 +414,7 @@ mistune = "0.8.4" name = "markupsafe" version = "2.1.0" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" +category = "dev" optional = false python-versions = ">=3.7" @@ -939,7 +927,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "e7b55f719918838ac8d73e1ee1cf1df328f9c6d62e7bb835c897f447c755a214" +content-hash = "3a311fef55d0ee3f97b74f9ef3d732eda18f8d20bc86df62882b3de3a81a6cba" [metadata.files] aiohttp = [ @@ -1016,10 +1004,6 @@ aiohttp = [ {file = "aiohttp-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac"}, {file = "aiohttp-3.8.1.tar.gz", hash = "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578"}, ] -aiohttp-jinja2 = [ - {file = "aiohttp-jinja2-1.5.tar.gz", hash = "sha256:7c3ba5eac060b691f4e50534af2d79fca2a75712ebd2b25e6fcb1295859f910b"}, - {file = "aiohttp_jinja2-1.5-py3-none-any.whl", hash = "sha256:b55c0ed167b0cc4b6d6a50fb2299a44beb5dc4aec9df21305b91a5484694cf74"}, -] aiomisc = [ {file = "aiomisc-15.7.2-py3-none-any.whl", hash = "sha256:e0b00145866b5c258a23e2c270bee2910f2562b078f6159d786a522699e8cfeb"}, {file = "aiomisc-15.7.2.tar.gz", hash = "sha256:fcc4517fa49056ec11ecd3ec5d341c554914c0a2d295c127b70583bab1ecab5b"}, diff --git a/packages/plugins/minos-router-graphql/pyproject.toml b/packages/plugins/minos-router-graphql/pyproject.toml index 2fd5a3fa3..d0cfd45e4 100644 --- a/packages/plugins/minos-router-graphql/pyproject.toml +++ b/packages/plugins/minos-router-graphql/pyproject.toml @@ -34,8 +34,6 @@ python = "^3.9" minos-microservice-common = "^0.5.0" minos-microservice-networks = "^0.5.0" aiohttp = "^3.8.1" -aiohttp-jinja2 = "^1.5" -Jinja2 = "^3.0.3" graphql-core = "^3.2.0" [tool.poetry.dev-dependencies] From 3dc7e9f8248553d316a0b163a59e0465137cc3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Tue, 15 Mar 2022 09:00:32 +0100 Subject: [PATCH 24/44] ISSUE #149 * Add `pre-commit`. --- .pre-commit-config.yaml | 7 +++++++ .../minos-router-graphql/.editorconfig | 21 ------------------- 2 files changed, 7 insertions(+), 21 deletions(-) delete mode 100644 packages/plugins/minos-router-graphql/.editorconfig diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e4f94cbb1..ccb513194 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,3 +50,10 @@ repos: name: Check minos-http-aiohttp files: ^packages/plugins/minos-http-aiohttp/ language: system + + - id: minos-router-graphql-check + pass_filenames: false + entry: make --directory=packages/plugins/minos-router-graphql check + name: Check minos-router-graphql + files: ^packages/plugins/minos-router-graphql/ + language: system diff --git a/packages/plugins/minos-router-graphql/.editorconfig b/packages/plugins/minos-router-graphql/.editorconfig deleted file mode 100644 index d4a2c4405..000000000 --- a/packages/plugins/minos-router-graphql/.editorconfig +++ /dev/null @@ -1,21 +0,0 @@ -# http://editorconfig.org - -root = true - -[*] -indent_style = space -indent_size = 4 -trim_trailing_whitespace = true -insert_final_newline = true -charset = utf-8 -end_of_line = lf - -[*.bat] -indent_style = tab -end_of_line = crlf - -[LICENSE] -insert_final_newline = false - -[Makefile] -indent_style = tab From fef1da3f72194558f471111f78a3f767da8a24c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Tue, 15 Mar 2022 09:37:49 +0100 Subject: [PATCH 25/44] ISSUE #149 * Be compatible with `latest` Router API --- .../minos/plugins/graphql/handlers.py | 37 ++++++++ .../minos/plugins/graphql/routers.py | 92 +++---------------- .../minos/plugins/graphql/schema_builder.py | 7 +- .../tests/test_graphql/test_routers.py | 16 ++-- .../tests/test_graphql/test_schema_builder.py | 25 ++--- 5 files changed, 72 insertions(+), 105 deletions(-) create mode 100644 packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py new file mode 100644 index 000000000..57bb13576 --- /dev/null +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py @@ -0,0 +1,37 @@ +from graphql import ( + graphql, + print_schema, +) + +from minos.networks import ( + HttpRequest, + HttpResponse, +) + + +class GraphQlHandler: + """TODO""" + + def __init__(self, schema): + self._schema = schema + + async def execute_operation(self, request: HttpRequest) -> HttpResponse: + """TODO""" + + source = await request.content() + + result = await graphql(schema=self._schema, source=source) + errors = result.errors + if errors is None: + errors = list() + + if len(errors): + status = 500 + else: + status = 200 + + return HttpResponse({"data": result.data, "errors": [err.message for err in errors]}, status=status) + + async def get_schema(self, request: HttpRequest) -> HttpResponse: + """TODO""" + return HttpResponse(print_schema(self._schema)) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py index 329dfb8e5..2edbe9b82 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py @@ -2,99 +2,31 @@ annotations, ) -import json from typing import ( Callable, ) -from graphql import ( - GraphQLSchema, - graphql, - print_schema, -) - -from minos.common import ( - MinosConfig, -) from minos.networks import ( - EnrouteBuilder, + EnrouteDecorator, HttpEnrouteDecorator, - HttpRequest, - HttpResponse, HttpRouter, ) +from .handlers import ( + GraphQlHandler, +) +from .schema_builder import ( + GraphQLSchemaBuilder, +) + class GraphQlHttpRouter(HttpRouter): """TODO""" - def __init__(self, schema: GraphQLSchema, *args, **kwargs): - super().__init__(*args, **kwargs) - self._schema = schema - - @classmethod - def _from_config(cls, config: MinosConfig, **kwargs) -> GraphQlHttpRouter: - builder = EnrouteBuilder(*config.services, middleware=config.middleware) - routes = builder.get_all(config=config, **kwargs) - - from minos.plugins.graphql import ( - GraphQLSchemaBuilder, - ) - + def _filter_routes(self, routes: dict[EnrouteDecorator, Callable]) -> dict[EnrouteDecorator, Callable]: schema = GraphQLSchemaBuilder.build(routes) - - return cls(schema) - - @property - def routes(self) -> dict[HttpEnrouteDecorator, Callable]: - """TODO""" + handler = GraphQlHandler(schema) return { - HttpEnrouteDecorator("/graphql", "POST"): self.handle_post, - HttpEnrouteDecorator("/graphql/schema", "GET"): self.handle_schema_get, + HttpEnrouteDecorator("/graphql", "POST"): handler.execute_operation, + HttpEnrouteDecorator("/graphql/schema", "GET"): handler.get_schema, } - - async def handle_post(self, request: HttpRequest) -> HttpResponse: - """TODO""" - status = 200 - result = await self.execute_graphql() - - if result and result.errors: - status = 500 - - errors = result.errors - - if errors is None: - errors = [] - - return HttpResponse( - {"data": result.data, "errors": [err.message for err in errors]}, - status=status, - headers={"Access-Control-Allow-Origin": "*"}, - ) - - async def execute_graphql(self): - graphql_req = await self.graphql_request - context_value = graphql_req.get("context", {}) - variables = graphql_req.get("variables", {}) - - # context_value["application"] = self.app - source = graphql_req["query"] - result = await graphql( - schema=self._schema, - source=source, - context_value=context_value, - variable_values=variables, - ) - - return result - - @property - async def graphql_request(self): - if self.request.method == "GET": - return json.loads(self.request.query["q"]) - - return await self.request.json() - - async def handle_schema_get(self, request: HttpRequest) -> HttpResponse: - """TODO""" - return HttpResponse(print_schema(self._schema)) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py index 29556868e..76ebc98ad 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py @@ -8,6 +8,9 @@ from inspect import ( isawaitable, ) +from typing import ( + Any, +) from graphql import ( GraphQLArgument, @@ -42,8 +45,8 @@ def _build(cls, routes) -> dict: @staticmethod def adapt_callback(callback): @wraps(callback) - async def _wrapper(_source, _info, request): - request = InMemoryRequest(request) + async def _wrapper(_source, _info, raw: Any = None): + request = InMemoryRequest(raw) response = callback(request) if isawaitable(response): diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py index 08333caad..3d9487985 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py @@ -1,12 +1,11 @@ import unittest -from graphql import ( - GraphQLSchema, -) - from minos.common import ( MinosConfig, ) +from minos.networks import ( + HttpEnrouteDecorator, +) from minos.plugins.graphql import ( GraphQlHttpRouter, ) @@ -15,15 +14,18 @@ ) -class TestSomething(unittest.TestCase): +class TestGraphQlHttpRouter(unittest.TestCase): CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" _config = MinosConfig(CONFIG_FILE_PATH) def test_from_config(self): - router = GraphQlHttpRouter.from_config(config=self._config) + router = GraphQlHttpRouter.from_config(self._config) - self.assertIsInstance(router._schema, GraphQLSchema) self.assertIsInstance(router, GraphQlHttpRouter) + self.assertEqual( + {HttpEnrouteDecorator("/graphql", "POST"), HttpEnrouteDecorator("/graphql/schema", "GET")}, + router.routes.keys(), + ) if __name__ == "__main__": diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py index e0d9278a3..940316e89 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py @@ -2,7 +2,7 @@ from graphql import ( GraphQLString, - graphql_sync, + graphql, validate_schema, ) @@ -14,7 +14,6 @@ Response, ) from minos.plugins.graphql import ( - GraphQlHttpRouter, GraphQLSchemaBuilder, ) from minos.plugins.graphql.decorators import ( @@ -30,7 +29,7 @@ async def callback_fn(request: Request): return Response("ticket #4") -class TestSomething(unittest.TestCase): +class TestSchemaBuilder(unittest.IsolatedAsyncioTestCase): CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" _config = MinosConfig(CONFIG_FILE_PATH) @@ -52,7 +51,7 @@ def test_build(self): errors = validate_schema(schema) - self.assertEqual(errors, []) + self.assertEqual(list(), errors) def test_build_only_queries(self): routes = { @@ -66,7 +65,7 @@ def test_build_only_queries(self): errors = validate_schema(schema) - self.assertEqual(errors, []) + self.assertEqual(list(), errors) def test_build_only_commands(self): routes = { @@ -82,16 +81,9 @@ def test_build_only_commands(self): errors = validate_schema(schema) - self.assertEqual(errors, []) + self.assertEqual(list(), errors) - def test_schema_valid(self): - router = GraphQlHttpRouter.from_config(config=self._config) - - errors = validate_schema(router._schema) - - self.assertEqual(errors, []) - - def test_query(self): + async def test_query(self): routes = { GraphQlQueryEnrouteDecorator(name="order_query", argument=GraphQLString, output=GraphQLString): callback_fn } @@ -100,9 +92,10 @@ def test_query(self): source = "{ order_query }" - result = graphql_sync(schema, source) + result = await graphql(schema, source) - self.assertEqual(result.errors, []) + self.assertEqual({"order_query": "ticket #4"}, result.data) + self.assertEqual(None, result.errors) if __name__ == "__main__": From d61b6b9dbede47f897850c594b6b63085c4b06be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Tue, 15 Mar 2022 09:42:19 +0100 Subject: [PATCH 26/44] ISSUE #149 * Update docs. --- .../docs/_static/style.css | 3 ++ .../docs/_templates/layout.html | 4 +++ .../docs/{authors.rst => authors.md} | 0 .../plugins/minos-router-graphql/docs/conf.py | 8 ++--- .../docs/{history.rst => history.md} | 0 .../docs/{index.rst => index.md} | 6 ++-- .../minos-router-graphql/docs/make.bat | 36 ------------------- .../docs/{readme.rst => readme.md} | 3 +- .../docs/{runthetests.rst => runthetests.md} | 0 .../minos-router-graphql/docs/usage.md | 1 + .../minos-router-graphql/docs/usage.rst | 7 ---- 11 files changed, 15 insertions(+), 53 deletions(-) create mode 100644 packages/plugins/minos-router-graphql/docs/_static/style.css create mode 100644 packages/plugins/minos-router-graphql/docs/_templates/layout.html rename packages/plugins/minos-router-graphql/docs/{authors.rst => authors.md} (100%) rename packages/plugins/minos-router-graphql/docs/{history.rst => history.md} (100%) rename packages/plugins/minos-router-graphql/docs/{index.rst => index.md} (54%) delete mode 100644 packages/plugins/minos-router-graphql/docs/make.bat rename packages/plugins/minos-router-graphql/docs/{readme.rst => readme.md} (50%) rename packages/plugins/minos-router-graphql/docs/{runthetests.rst => runthetests.md} (100%) create mode 100644 packages/plugins/minos-router-graphql/docs/usage.md delete mode 100644 packages/plugins/minos-router-graphql/docs/usage.rst diff --git a/packages/plugins/minos-router-graphql/docs/_static/style.css b/packages/plugins/minos-router-graphql/docs/_static/style.css new file mode 100644 index 000000000..8aa6c288f --- /dev/null +++ b/packages/plugins/minos-router-graphql/docs/_static/style.css @@ -0,0 +1,3 @@ +.wy-nav-content { + max-width: 1200px !important; +} diff --git a/packages/plugins/minos-router-graphql/docs/_templates/layout.html b/packages/plugins/minos-router-graphql/docs/_templates/layout.html new file mode 100644 index 000000000..b0a448060 --- /dev/null +++ b/packages/plugins/minos-router-graphql/docs/_templates/layout.html @@ -0,0 +1,4 @@ +{% extends "!layout.html" %} +{% block extrahead %} + +{% endblock %} \ No newline at end of file diff --git a/packages/plugins/minos-router-graphql/docs/authors.rst b/packages/plugins/minos-router-graphql/docs/authors.md similarity index 100% rename from packages/plugins/minos-router-graphql/docs/authors.rst rename to packages/plugins/minos-router-graphql/docs/authors.md diff --git a/packages/plugins/minos-router-graphql/docs/conf.py b/packages/plugins/minos-router-graphql/docs/conf.py index f17c29f90..8ac6c97e2 100755 --- a/packages/plugins/minos-router-graphql/docs/conf.py +++ b/packages/plugins/minos-router-graphql/docs/conf.py @@ -58,7 +58,7 @@ master_doc = "index" # General information about the project. -project = "Minos Rest Aiohttp" +project = "Minos Router GraphQl" copyright = "2021, Clariteia" author = "Clariteia Devs" @@ -147,7 +147,7 @@ # (source start file, target name, title, author, documentclass # [howto, manual, or own class]). latex_documents = [ - (master_doc, "minos.tex", "Minos Rest Aiohttp Documentation", "Clariteia Devs", "manual"), + (master_doc, "minos.tex", "Minos Router GraphQl Documentation", "Clariteia Devs", "manual"), ] @@ -155,7 +155,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [(master_doc, "minos", "Minos Rest Aiohttp Documentation", [author], 1)] +man_pages = [(master_doc, "minos", "Minos Router GraphQl Documentation", [author], 1)] # -- Options for Texinfo output ---------------------------------------- @@ -167,7 +167,7 @@ ( master_doc, "minos", - "Minos Rest Aiohttp Documentation", + "Minos Router GraphQl Documentation", author, "minos", "One line description of project.", diff --git a/packages/plugins/minos-router-graphql/docs/history.rst b/packages/plugins/minos-router-graphql/docs/history.md similarity index 100% rename from packages/plugins/minos-router-graphql/docs/history.rst rename to packages/plugins/minos-router-graphql/docs/history.md diff --git a/packages/plugins/minos-router-graphql/docs/index.rst b/packages/plugins/minos-router-graphql/docs/index.md similarity index 54% rename from packages/plugins/minos-router-graphql/docs/index.rst rename to packages/plugins/minos-router-graphql/docs/index.md index c1b30a0fd..d62945fe1 100644 --- a/packages/plugins/minos-router-graphql/docs/index.rst +++ b/packages/plugins/minos-router-graphql/docs/index.md @@ -1,5 +1,4 @@ -Welcome to Minos Rest Aiohttp's documentation! -====================================== +# Welcome to Minos Router GraphQl's documentation! .. toctree:: :maxdepth: 2 @@ -11,8 +10,7 @@ Welcome to Minos Rest Aiohttp's documentation! authors history -Indices and tables -==================== +# Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` diff --git a/packages/plugins/minos-router-graphql/docs/make.bat b/packages/plugins/minos-router-graphql/docs/make.bat deleted file mode 100644 index 916674840..000000000 --- a/packages/plugins/minos-router-graphql/docs/make.bat +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=python -msphinx -) -set SOURCEDIR=. -set BUILDDIR=_build -set SPHINXPROJ=minos_rest_aiohttp - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The Sphinx module was not found. Make sure you have Sphinx installed, - echo.then set the SPHINXBUILD environment variable to point to the full - echo.path of the 'sphinx-build' executable. Alternatively you may add the - echo.Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/packages/plugins/minos-router-graphql/docs/readme.rst b/packages/plugins/minos-router-graphql/docs/readme.md similarity index 50% rename from packages/plugins/minos-router-graphql/docs/readme.rst rename to packages/plugins/minos-router-graphql/docs/readme.md index af09813a4..da72dbef1 100644 --- a/packages/plugins/minos-router-graphql/docs/readme.rst +++ b/packages/plugins/minos-router-graphql/docs/readme.md @@ -1,4 +1,3 @@ -Introduction -************** +# Introduction .. mdinclude:: ../README.md diff --git a/packages/plugins/minos-router-graphql/docs/runthetests.rst b/packages/plugins/minos-router-graphql/docs/runthetests.md similarity index 100% rename from packages/plugins/minos-router-graphql/docs/runthetests.rst rename to packages/plugins/minos-router-graphql/docs/runthetests.md diff --git a/packages/plugins/minos-router-graphql/docs/usage.md b/packages/plugins/minos-router-graphql/docs/usage.md new file mode 100644 index 000000000..8f04b05ad --- /dev/null +++ b/packages/plugins/minos-router-graphql/docs/usage.md @@ -0,0 +1 @@ +# Usage diff --git a/packages/plugins/minos-router-graphql/docs/usage.rst b/packages/plugins/minos-router-graphql/docs/usage.rst deleted file mode 100644 index 1f662b1ca..000000000 --- a/packages/plugins/minos-router-graphql/docs/usage.rst +++ /dev/null @@ -1,7 +0,0 @@ -===== -Usage -===== - -To use Minos Rest Aiohttp in a project:: - - import minos_rest_aiohttp From a0a86d78c6e9adfa80f83233813fec0a549bc77d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Tue, 15 Mar 2022 09:43:55 +0100 Subject: [PATCH 27/44] ISSUE #149 * Remove `aiohttp` from required dependencies. --- .../plugins/minos-router-graphql/poetry.lock | 40 +++++++++++++++++-- .../minos-router-graphql/pyproject.toml | 1 - 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/packages/plugins/minos-router-graphql/poetry.lock b/packages/plugins/minos-router-graphql/poetry.lock index 1bfd569c3..fa147869f 100644 --- a/packages/plugins/minos-router-graphql/poetry.lock +++ b/packages/plugins/minos-router-graphql/poetry.lock @@ -340,7 +340,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "importlib-metadata" -version = "4.11.2" +version = "4.11.3" description = "Read metadata from Python packages" category = "dev" optional = false @@ -444,6 +444,7 @@ fastavro = "^1.4.0" lmdb = "^1.2.1" orjson = "^3.5.2" PyYAML = ">=5.4.1,<7.0.0" +uvloop = "^0.16.0" [package.source] type = "directory" @@ -882,6 +883,19 @@ brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] +[[package]] +name = "uvloop" +version = "0.16.0" +description = "Fast implementation of asyncio event loop on top of libuv" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] +docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"] +test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"] + [[package]] name = "virtualenv" version = "20.13.3" @@ -927,7 +941,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "3a311fef55d0ee3f97b74f9ef3d732eda18f8d20bc86df62882b3de3a81a6cba" +content-hash = "da35fc32418385430fcb9d75cbbe109963f1dc2ef2ce3888697eb665f01c1ba1" [metadata.files] aiohttp = [ @@ -1285,8 +1299,8 @@ imagesize = [ {file = "imagesize-1.3.0.tar.gz", hash = "sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.11.2-py3-none-any.whl", hash = "sha256:d16e8c1deb60de41b8e8ed21c1a7b947b0bc62fab7e1d470bcdf331cea2e6735"}, - {file = "importlib_metadata-4.11.2.tar.gz", hash = "sha256:b36ffa925fe3139b2f6ff11d6925ffd4fa7bc47870165e3ac260ac7b4f91e6ac"}, + {file = "importlib_metadata-4.11.3-py3-none-any.whl", hash = "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6"}, + {file = "importlib_metadata-4.11.3.tar.gz", hash = "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -1701,6 +1715,24 @@ urllib3 = [ {file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"}, {file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"}, ] +uvloop = [ + {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"}, + {file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30ba9dcbd0965f5c812b7c2112a1ddf60cf904c1c160f398e7eed3a6b82dcd9c"}, + {file = "uvloop-0.16.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bd53f7f5db562f37cd64a3af5012df8cac2c464c97e732ed556800129505bd64"}, + {file = "uvloop-0.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:772206116b9b57cd625c8a88f2413df2fcfd0b496eb188b82a43bed7af2c2ec9"}, + {file = "uvloop-0.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b572256409f194521a9895aef274cea88731d14732343da3ecdb175228881638"}, + {file = "uvloop-0.16.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:04ff57aa137230d8cc968f03481176041ae789308b4d5079118331ab01112450"}, + {file = "uvloop-0.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a19828c4f15687675ea912cc28bbcb48e9bb907c801873bd1519b96b04fb805"}, + {file = "uvloop-0.16.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e814ac2c6f9daf4c36eb8e85266859f42174a4ff0d71b99405ed559257750382"}, + {file = "uvloop-0.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bd8f42ea1ea8f4e84d265769089964ddda95eb2bb38b5cbe26712b0616c3edee"}, + {file = "uvloop-0.16.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:647e481940379eebd314c00440314c81ea547aa636056f554d491e40503c8464"}, + {file = "uvloop-0.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e0d26fa5875d43ddbb0d9d79a447d2ace4180d9e3239788208527c4784f7cab"}, + {file = "uvloop-0.16.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ccd57ae8db17d677e9e06192e9c9ec4bd2066b77790f9aa7dede2cc4008ee8f"}, + {file = "uvloop-0.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:089b4834fd299d82d83a25e3335372f12117a7d38525217c2258e9b9f4578897"}, + {file = "uvloop-0.16.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98d117332cc9e5ea8dfdc2b28b0a23f60370d02e1395f88f40d1effd2cb86c4f"}, + {file = "uvloop-0.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e5f2e2ff51aefe6c19ee98af12b4ae61f5be456cd24396953244a30880ad861"}, + {file = "uvloop-0.16.0.tar.gz", hash = "sha256:f74bc20c7b67d1c27c72601c78cf95be99d5c2cdd4514502b4f3eb0933ff1228"}, +] virtualenv = [ {file = "virtualenv-20.13.3-py2.py3-none-any.whl", hash = "sha256:dd448d1ded9f14d1a4bfa6bfc0c5b96ae3be3f2d6c6c159b23ddcfd701baa021"}, {file = "virtualenv-20.13.3.tar.gz", hash = "sha256:e9dd1a1359d70137559034c0f5433b34caf504af2dc756367be86a5a32967134"}, diff --git a/packages/plugins/minos-router-graphql/pyproject.toml b/packages/plugins/minos-router-graphql/pyproject.toml index d0cfd45e4..97dbd0367 100644 --- a/packages/plugins/minos-router-graphql/pyproject.toml +++ b/packages/plugins/minos-router-graphql/pyproject.toml @@ -33,7 +33,6 @@ include = [ python = "^3.9" minos-microservice-common = "^0.5.0" minos-microservice-networks = "^0.5.0" -aiohttp = "^3.8.1" graphql-core = "^3.2.0" [tool.poetry.dev-dependencies] From 5fd824c0cc13f2a16065545f72e68a6b8212d5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Tue, 15 Mar 2022 09:56:57 +0100 Subject: [PATCH 28/44] ISSUE #149 * Fix documentation bug. --- README.md | 1 + packages/plugins/minos-router-graphql/docs/index.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0cf6c90fc..f94c5f13d 100644 --- a/README.md +++ b/README.md @@ -1157,6 +1157,7 @@ The plugin packages provide connectors to external technologies like brokers, di * [minos-broker-kafka](https://minos-framework.github.io/minos-python/packages/plugins/minos-broker-kafka): The `kafka` plugin package. * [minos-discovery-minos](https://minos-framework.github.io/minos-python/packages/plugins/minos-discovery-minos): The `minos-discovery` plugin package. * [minos-http-aiohttp](https://minos-framework.github.io/minos-python/packages/plugins/minos-http-aiohttp): The `aiohttp` plugin package. +* * [minos-router-graphql](https://minos-framework.github.io/minos-python/packages/plugins/minos-router-graphql): The `grapqhl` plugin package. ## Source Code diff --git a/packages/plugins/minos-router-graphql/docs/index.md b/packages/plugins/minos-router-graphql/docs/index.md index d62945fe1..038a73eb8 100644 --- a/packages/plugins/minos-router-graphql/docs/index.md +++ b/packages/plugins/minos-router-graphql/docs/index.md @@ -6,7 +6,7 @@ readme runthetests usage - api/minos + api/minos.plugins authors history From 55a0b6c03e19a4ae6cf9bd545158d33dac59cc57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Tue, 15 Mar 2022 09:58:25 +0100 Subject: [PATCH 29/44] ISSUE #149 * Fix bug related with coverage publishing. --- .github/workflows/minos-router-graphql-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/minos-router-graphql-tests.yml b/.github/workflows/minos-router-graphql-tests.yml index 54c26751e..e830f4370 100644 --- a/.github/workflows/minos-router-graphql-tests.yml +++ b/.github/workflows/minos-router-graphql-tests.yml @@ -56,7 +56,7 @@ jobs: uses: codecov/codecov-action@v2 with: token: ${{ secrets.CODECOV_TOKEN }} - files: ./packages/plugins/minos-http-aiohttp/coverage.xml + files: ./packages/plugins/minos-router-graphql/coverage.xml fail_ci_if_error: true - name: Generate documentation From 4d822c159b9e094cf8e5964c14bba7e06b4827bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Tue, 15 Mar 2022 10:03:13 +0100 Subject: [PATCH 30/44] ISSUE #149 * Add `builders` module. --- .../minos-router-graphql/minos/plugins/graphql/__init__.py | 6 +++--- .../minos/plugins/graphql/builders/__init__.py | 3 +++ .../graphql/{schema_builder.py => builders/schema.py} | 5 +++++ .../minos-router-graphql/minos/plugins/graphql/routers.py | 6 +++--- .../tests/test_graphql/test_builders/__init__.py | 0 .../test_schema.py} | 0 6 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/__init__.py rename packages/plugins/minos-router-graphql/minos/plugins/graphql/{schema_builder.py => builders/schema.py} (97%) create mode 100644 packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/__init__.py rename packages/plugins/minos-router-graphql/tests/test_graphql/{test_schema_builder.py => test_builders/test_schema.py} (100%) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py index 3ffd178ce..37578be32 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py @@ -2,15 +2,15 @@ __email__ = "hey@minos.run" __version__ = "0.6.0" +from .builders import ( + GraphQLSchemaBuilder, +) from .decorators import ( GraphQlEnroute, ) from .routers import ( GraphQlHttpRouter, ) -from .schema_builder import ( - GraphQLSchemaBuilder, -) def _register_enroute(): diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/__init__.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/__init__.py new file mode 100644 index 000000000..1bc5fda51 --- /dev/null +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/__init__.py @@ -0,0 +1,3 @@ +from .schema import ( + GraphQLSchemaBuilder, +) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py similarity index 97% rename from packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py rename to packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py index 76ebc98ad..3680983db 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/schema_builder.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py @@ -27,11 +27,14 @@ class GraphQLSchemaBuilder: + """TODO""" + def __init__(self, *args, **kwargs): self.schema = GraphQLSchema(**kwargs) @classmethod def build(cls, routes) -> GraphQLSchema: + """TODO""" schema_args = cls._build(routes) return cls(**schema_args).schema @@ -44,6 +47,8 @@ def _build(cls, routes) -> dict: @staticmethod def adapt_callback(callback): + """TODO""" + @wraps(callback) async def _wrapper(_source, _info, raw: Any = None): request = InMemoryRequest(raw) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py index 2edbe9b82..3c216f829 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py @@ -12,12 +12,12 @@ HttpRouter, ) +from .builders import ( + GraphQLSchemaBuilder, +) from .handlers import ( GraphQlHandler, ) -from .schema_builder import ( - GraphQLSchemaBuilder, -) class GraphQlHttpRouter(HttpRouter): diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/__init__.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py similarity index 100% rename from packages/plugins/minos-router-graphql/tests/test_graphql/test_schema_builder.py rename to packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py From 2b7fdb05a88f060b7007677a07fc49a7ca8c638a Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Tue, 15 Mar 2022 13:31:27 +0100 Subject: [PATCH 31/44] ISSUE #149 --- .../minos/plugins/graphql/builders/schema.py | 5 +- .../minos/plugins/graphql/decorators.py | 3 +- .../minos/plugins/graphql/handlers.py | 3 +- .../test_graphql/test_builders/test_schema.py | 36 ++++ .../tests/test_graphql/test_handlers.py | 161 ++++++++++++++++++ 5 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py index 3680983db..c1a2685ed 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py @@ -94,4 +94,7 @@ def _build_mutations(cls, routes): @staticmethod def _build_field(item, callback): - return GraphQLField(item.output, args={"request": GraphQLArgument(item.argument)}, resolve=callback) + args = None + if item.argument is not None: + args = {"request": GraphQLArgument(item.argument)} + return GraphQLField(item.output, args=args, resolve=callback) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py index c48258acc..1c83e0e8e 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py @@ -6,6 +6,7 @@ ) from typing import ( Final, + Optional, ) from minos.networks import ( @@ -18,7 +19,7 @@ class GraphQlEnrouteDecorator(EnrouteDecorator, ABC): """GraphQl Enroute class""" - def __init__(self, name: str, argument, output): + def __init__(self, name: str, output, argument: Optional = None): self.name = name self.argument = argument self.output = output diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py index 57bb13576..49248e06a 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py @@ -1,4 +1,5 @@ from graphql import ( + GraphQLSchema, graphql, print_schema, ) @@ -12,7 +13,7 @@ class GraphQlHandler: """TODO""" - def __init__(self, schema): + def __init__(self, schema: GraphQLSchema): self._schema = schema async def execute_operation(self, request: HttpRequest) -> HttpResponse: diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py index 940316e89..f7ddf303b 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py @@ -67,6 +67,18 @@ def test_build_only_queries(self): self.assertEqual(list(), errors) + def test_build_queries_without_arguments(self): + routes = { + GraphQlQueryEnrouteDecorator(name="order_query", output=GraphQLString): callback_fn, + GraphQlQueryEnrouteDecorator(name="ticket_query", output=GraphQLString): callback_fn, + } + + schema = GraphQLSchemaBuilder.build(routes=routes) + + errors = validate_schema(schema) + + self.assertEqual(list(), errors) + def test_build_only_commands(self): routes = { GraphQlCommandEnrouteDecorator( @@ -83,6 +95,18 @@ def test_build_only_commands(self): self.assertEqual(list(), errors) + def test_build_commands_without_arguments(self): + routes = { + GraphQlCommandEnrouteDecorator(name="order_command", output=GraphQLString): callback_fn, + GraphQlCommandEnrouteDecorator(name="ticket_command", output=GraphQLString): callback_fn, + } + + schema = GraphQLSchemaBuilder.build(routes=routes) + + errors = validate_schema(schema) + + self.assertEqual(list(), errors) + async def test_query(self): routes = { GraphQlQueryEnrouteDecorator(name="order_query", argument=GraphQLString, output=GraphQLString): callback_fn @@ -97,6 +121,18 @@ async def test_query(self): self.assertEqual({"order_query": "ticket #4"}, result.data) self.assertEqual(None, result.errors) + async def test_query_without_arguments(self): + routes = {GraphQlQueryEnrouteDecorator(name="order_query", output=GraphQLString): callback_fn} + + schema = GraphQLSchemaBuilder.build(routes=routes) + + source = "{ order_query }" + + result = await graphql(schema, source) + + self.assertEqual({"order_query": "ticket #4"}, result.data) + self.assertEqual(None, result.errors) + if __name__ == "__main__": unittest.main() diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py new file mode 100644 index 000000000..f42754386 --- /dev/null +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py @@ -0,0 +1,161 @@ +import json +import unittest +from typing import ( + NamedTuple, + Optional, +) + +from graphql import ( + GraphQLArgument, + GraphQLBoolean, + GraphQLField, + GraphQLID, + GraphQLInt, + GraphQLNonNull, + GraphQLObjectType, + GraphQLString, +) + +from minos.common import ( + MinosConfig, +) +from minos.networks import ( + InMemoryRequest, + Request, + Response, +) +from minos.plugins.graphql import ( + GraphQLSchemaBuilder, +) +from minos.plugins.graphql.decorators import ( + GraphQlQueryEnrouteDecorator, +) +from minos.plugins.graphql.handlers import ( + GraphQlHandler, +) +from tests.test_graphql.test_builders.test_schema import ( + callback_fn, +) +from tests.utils import ( + BASE_PATH, +) + + +class User(NamedTuple): + """A simple user object class.""" + + firstName: str + lastName: str + tweets: Optional[int] + id: Optional[str] = None + verified: bool = False + + +async def resolve_ticket(request: Request): + return Response(3) + + +user_type = GraphQLObjectType( + "UserType", + { + "id": GraphQLField(GraphQLNonNull(GraphQLID)), + "firstName": GraphQLField(GraphQLNonNull(GraphQLString)), + "lastName": GraphQLField(GraphQLNonNull(GraphQLString)), + "tweets": GraphQLField(GraphQLInt), + "verified": GraphQLField(GraphQLNonNull(GraphQLBoolean)), + }, +) + + +class TestGraphQlHttpRouter(unittest.IsolatedAsyncioTestCase): + CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" + _config = MinosConfig(CONFIG_FILE_PATH) + + async def test_execute_operation(self): + routes = { + GraphQlQueryEnrouteDecorator(name="order_query", output=GraphQLString): callback_fn, + GraphQlQueryEnrouteDecorator(name="ticket_query", output=GraphQLString): callback_fn, + } + + schema = GraphQLSchemaBuilder.build(routes=routes) + + handler = GraphQlHandler(schema) + + request = InMemoryRequest(content="{ order_query }") + + result = await handler.execute_operation(request) + + self.assertDictEqual(await result.content(), {"data": {"order_query": "ticket #4"}, "errors": []}) + + async def test_execute_wrong_operation(self): + routes = { + GraphQlQueryEnrouteDecorator(name="order_query", output=GraphQLString): callback_fn, + GraphQlQueryEnrouteDecorator(name="ticket_query", output=GraphQLString): callback_fn, + } + + schema = GraphQLSchemaBuilder.build(routes=routes) + + handler = GraphQlHandler(schema) + + request = InMemoryRequest(content="{ fictitious_query }") + + result = await handler.execute_operation(request) + + content = await result.content() + + self.assertNotEquals(content["errors"], []) + + async def test_schema(self): + routes = { + GraphQlQueryEnrouteDecorator(name="order_query", output=GraphQLString): callback_fn, + GraphQlQueryEnrouteDecorator(name="ticket_query", output=GraphQLString): callback_fn, + } + + schema = GraphQLSchemaBuilder.build(routes=routes) + + handler = GraphQlHandler(schema) + + request = InMemoryRequest(content="Some content") + + result = await handler.get_schema(request) + + self.assertIsInstance(await result.content(), str) + self.assertMultiLineEqual( + await result.content(), "type Query {\n order_query: String\n ticket_query: String\n}" + ) + + async def operation_with_variables(self): + routes = { + GraphQlQueryEnrouteDecorator( + name="order_query", argument=GraphQLArgument(GraphQLID), output=user_type + ): callback_fn + } + + schema = GraphQLSchemaBuilder.build(routes=routes) + + handler = GraphQlHandler(schema) + + query = """ + query ($userId: ID!) { + User(id: $userId) { + id, firstName, lastName, tweets, verified + } + } + """ + + variables = {"userId": 3} + + content = {"query": query, "variables": variables} + + request = InMemoryRequest(content=json.dumps(content)) + + result = await handler.get_schema(request) + + self.assertIsInstance(await result.content(), str) + self.assertMultiLineEqual( + await result.content(), "type Query {\n order_query: String\n ticket_query: String\n}" + ) + + +if __name__ == "__main__": + unittest.main() From f395a2ba14ee03889cc820dba87109b394a993fb Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Tue, 15 Mar 2022 17:09:23 +0100 Subject: [PATCH 32/44] ISSUE #149 --- .../minos/plugins/graphql/builders/schema.py | 4 +- .../minos/plugins/graphql/handlers.py | 20 ++- .../test_graphql/test_builders/test_schema.py | 11 ++ .../tests/test_graphql/test_handlers.py | 137 ++++++++++++------ .../minos-router-graphql/tests/utils.py | 58 ++++++++ 5 files changed, 180 insertions(+), 50 deletions(-) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py index c1a2685ed..da207fbcf 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py @@ -50,8 +50,8 @@ def adapt_callback(callback): """TODO""" @wraps(callback) - async def _wrapper(_source, _info, raw: Any = None): - request = InMemoryRequest(raw) + async def _wrapper(_source, _info, request: Any = None): + request = InMemoryRequest(request) response = callback(request) if isawaitable(response): diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py index 49248e06a..632b29189 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py @@ -19,15 +19,29 @@ def __init__(self, schema: GraphQLSchema): async def execute_operation(self, request: HttpRequest) -> HttpResponse: """TODO""" - source = await request.content() + content = await request.content() + + source = dict() + variables = dict() + + if type(content) == str: + source = content + + if type(content) == dict: + if "query" in content: + source = content["query"] + + if "variables" in content: + variables = content["variables"] + + result = await graphql(schema=self._schema, source=source, variable_values=variables) - result = await graphql(schema=self._schema, source=source) errors = result.errors if errors is None: errors = list() if len(errors): - status = 500 + status = 400 else: status = 200 diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py index f7ddf303b..6267438bc 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py @@ -1,6 +1,7 @@ import unittest from graphql import ( + GraphQLID, GraphQLString, graphql, validate_schema, @@ -22,6 +23,7 @@ ) from tests.utils import ( BASE_PATH, + user_type, ) @@ -133,6 +135,15 @@ async def test_query_without_arguments(self): self.assertEqual({"order_query": "ticket #4"}, result.data) self.assertEqual(None, result.errors) + async def test_schema(self): + routes = {GraphQlQueryEnrouteDecorator(name="order_query", argument=GraphQLID, output=user_type): callback_fn} + + schema = GraphQLSchemaBuilder.build(routes=routes) + + errors = validate_schema(schema) + + self.assertEqual(list(), errors) + if __name__ == "__main__": unittest.main() diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py index f42754386..2ae5e6be0 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py @@ -1,18 +1,8 @@ -import json import unittest -from typing import ( - NamedTuple, - Optional, -) from graphql import ( - GraphQLArgument, - GraphQLBoolean, - GraphQLField, - GraphQLID, GraphQLInt, GraphQLNonNull, - GraphQLObjectType, GraphQLString, ) @@ -28,6 +18,7 @@ GraphQLSchemaBuilder, ) from minos.plugins.graphql.decorators import ( + GraphQlCommandEnrouteDecorator, GraphQlQueryEnrouteDecorator, ) from minos.plugins.graphql.handlers import ( @@ -38,35 +29,17 @@ ) from tests.utils import ( BASE_PATH, + resolve_create_user, + resolve_user, + user_input_type, + user_type, ) -class User(NamedTuple): - """A simple user object class.""" - - firstName: str - lastName: str - tweets: Optional[int] - id: Optional[str] = None - verified: bool = False - - async def resolve_ticket(request: Request): return Response(3) -user_type = GraphQLObjectType( - "UserType", - { - "id": GraphQLField(GraphQLNonNull(GraphQLID)), - "firstName": GraphQLField(GraphQLNonNull(GraphQLString)), - "lastName": GraphQLField(GraphQLNonNull(GraphQLString)), - "tweets": GraphQLField(GraphQLInt), - "verified": GraphQLField(GraphQLNonNull(GraphQLBoolean)), - }, -) - - class TestGraphQlHttpRouter(unittest.IsolatedAsyncioTestCase): CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" _config = MinosConfig(CONFIG_FILE_PATH) @@ -103,7 +76,7 @@ async def test_execute_wrong_operation(self): content = await result.content() - self.assertNotEquals(content["errors"], []) + self.assertNotEqual(content["errors"], []) async def test_schema(self): routes = { @@ -124,11 +97,9 @@ async def test_schema(self): await result.content(), "type Query {\n order_query: String\n ticket_query: String\n}" ) - async def operation_with_variables(self): + async def test_query_with_variables(self): routes = { - GraphQlQueryEnrouteDecorator( - name="order_query", argument=GraphQLArgument(GraphQLID), output=user_type - ): callback_fn + GraphQlQueryEnrouteDecorator(name="order_query", argument=GraphQLInt, output=GraphQLInt): resolve_ticket } schema = GraphQLSchemaBuilder.build(routes=routes) @@ -136,9 +107,39 @@ async def operation_with_variables(self): handler = GraphQlHandler(schema) query = """ - query ($userId: ID!) { - User(id: $userId) { - id, firstName, lastName, tweets, verified + query ($userId: Int!) { + order_query(request: $userId) + } + """ + + variables = {"userId": 3} + + content = {"query": query, "variables": variables} + + request = InMemoryRequest(content=content) + + result = await handler.execute_operation(request) + + content = await result.content() + + self.assertDictEqual({"order_query": 3}, content["data"]) + self.assertEqual([], content["errors"]) + + async def test_query_with_variables_return_user(self): + routes = {GraphQlQueryEnrouteDecorator(name="order_query", argument=GraphQLInt, output=user_type): resolve_user} + + schema = GraphQLSchemaBuilder.build(routes=routes) + + handler = GraphQlHandler(schema) + + query = """ + query ($userId: Int!) { + order_query(request: $userId) { + id + firstName + lastName + tweets + verified } } """ @@ -147,14 +148,60 @@ async def operation_with_variables(self): content = {"query": query, "variables": variables} - request = InMemoryRequest(content=json.dumps(content)) + request = InMemoryRequest(content=content) - result = await handler.get_schema(request) + result = await handler.execute_operation(request) - self.assertIsInstance(await result.content(), str) - self.assertMultiLineEqual( - await result.content(), "type Query {\n order_query: String\n ticket_query: String\n}" + content = await result.content() + + self.assertDictEqual( + {"order_query": {"id": "3", "firstName": "Jack", "lastName": "Johnson", "tweets": 563, "verified": True}}, + content["data"], + ) + self.assertEqual([], content["errors"]) + + async def test_mutation(self): + routes = { + GraphQlCommandEnrouteDecorator( + name="createUser", argument=GraphQLNonNull(user_input_type), output=user_type + ): resolve_create_user + } + + schema = GraphQLSchemaBuilder.build(routes=routes) + + handler = GraphQlHandler(schema) + + query = """ + mutation ($userData: UserInputType!) { + createUser(request: $userData) { + id, firstName, lastName, tweets, verified + } + } + """ + + variables = {"userData": dict(firstName="John", lastName="Doe", tweets=42, verified=True)} + + content = {"query": query, "variables": variables} + + request = InMemoryRequest(content=content) + + result = await handler.execute_operation(request) + + content = await result.content() + + self.assertDictEqual( + { + "createUser": { + "id": "4kjjj43-l23k4l3-325kgaa2", + "firstName": "John", + "lastName": "Doe", + "tweets": 42, + "verified": True, + } + }, + content["data"], ) + self.assertEqual([], content["errors"]) if __name__ == "__main__": diff --git a/packages/plugins/minos-router-graphql/tests/utils.py b/packages/plugins/minos-router-graphql/tests/utils.py index 5a59ee2f0..1963f10da 100644 --- a/packages/plugins/minos-router-graphql/tests/utils.py +++ b/packages/plugins/minos-router-graphql/tests/utils.py @@ -6,11 +6,19 @@ ) from typing import ( Any, + NamedTuple, + Optional, ) from graphql import ( GraphQLArgument, + GraphQLBoolean, GraphQLField, + GraphQLID, + GraphQLInputField, + GraphQLInputObjectType, + GraphQLInt, + GraphQLNonNull, GraphQLObjectType, GraphQLString, ) @@ -69,3 +77,53 @@ def get_order(self, request: Request): """For testng purposes.""" return "eu38hj32-889283-j2jjb5kl" + + +user_type = GraphQLObjectType( + "UserType", + { + "id": GraphQLField(GraphQLNonNull(GraphQLID)), + "firstName": GraphQLField(GraphQLNonNull(GraphQLString)), + "lastName": GraphQLField(GraphQLNonNull(GraphQLString)), + "tweets": GraphQLField(GraphQLInt), + "verified": GraphQLField(GraphQLNonNull(GraphQLBoolean)), + }, +) + +user_input_type = GraphQLInputObjectType( + "UserInputType", + { + "firstName": GraphQLInputField(GraphQLNonNull(GraphQLString)), + "lastName": GraphQLInputField(GraphQLNonNull(GraphQLString)), + "tweets": GraphQLInputField(GraphQLInt), + "verified": GraphQLInputField(GraphQLBoolean), + }, +) + + +class User(NamedTuple): + """A simple user object class.""" + + firstName: str + lastName: str + tweets: Optional[int] + id: Optional[str] = None + verified: bool = False + + +async def resolve_user(request: Request): + id = await request.content() + return Response(User(firstName="Jack", lastName="Johnson", tweets=563, id=str(id), verified=True)) + + +async def resolve_create_user(request: Request): + params = await request.content() + return Response( + User( + firstName=params["firstName"], + lastName=params["lastName"], + tweets=params["tweets"], + id="4kjjj43-l23k4l3-325kgaa2", + verified=params["verified"], + ) + ) From e670910957e0252e7e521236f6f40c858a3c89f9 Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Tue, 15 Mar 2022 17:53:34 +0100 Subject: [PATCH 33/44] ISSUE #149 --- .../minos/plugins/graphql/handlers.py | 18 ++++++++----- .../tests/test_graphql/test_handlers.py | 26 +++++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py index 632b29189..8179787ad 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py @@ -7,27 +7,28 @@ from minos.networks import ( HttpRequest, HttpResponse, + ResponseException, ) class GraphQlHandler: - """TODO""" + """GraphQl Handler""" def __init__(self, schema: GraphQLSchema): self._schema = schema async def execute_operation(self, request: HttpRequest) -> HttpResponse: - """TODO""" + """Execute incoming request extracting variables and passing to graphql""" content = await request.content() source = dict() variables = dict() - if type(content) == str: + if isinstance(content, str): source = content - if type(content) == dict: + if isinstance(content, dict): if "query" in content: source = content["query"] @@ -40,13 +41,16 @@ async def execute_operation(self, request: HttpRequest) -> HttpResponse: if errors is None: errors = list() + status = 200 + if len(errors): status = 400 - else: - status = 200 + for error in errors: + if isinstance(error.original_error, ResponseException): + status = error.original_error.status return HttpResponse({"data": result.data, "errors": [err.message for err in errors]}, status=status) async def get_schema(self, request: HttpRequest) -> HttpResponse: - """TODO""" + """Get schema""" return HttpResponse(print_schema(self._schema)) diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py index 2ae5e6be0..3ef2a44f7 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py @@ -13,6 +13,7 @@ InMemoryRequest, Request, Response, + ResponseException, ) from minos.plugins.graphql import ( GraphQLSchemaBuilder, @@ -40,6 +41,10 @@ async def resolve_ticket(request: Request): return Response(3) +async def resolve_ticket_raises(request: Request): + raise ResponseException("Some error.", status=403) + + class TestGraphQlHttpRouter(unittest.IsolatedAsyncioTestCase): CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" _config = MinosConfig(CONFIG_FILE_PATH) @@ -58,8 +63,24 @@ async def test_execute_operation(self): result = await handler.execute_operation(request) + self.assertEqual(200, result.status) self.assertDictEqual(await result.content(), {"data": {"order_query": "ticket #4"}, "errors": []}) + async def test_execute_operation_raises(self): + routes = { + GraphQlQueryEnrouteDecorator(name="ticket_query", output=GraphQLString): resolve_ticket_raises, + } + + schema = GraphQLSchemaBuilder.build(routes=routes) + + handler = GraphQlHandler(schema) + + request = InMemoryRequest(content="{ ticket_query }") + + result = await handler.execute_operation(request) + + self.assertEqual(403, result.status) + async def test_execute_wrong_operation(self): routes = { GraphQlQueryEnrouteDecorator(name="order_query", output=GraphQLString): callback_fn, @@ -76,6 +97,7 @@ async def test_execute_wrong_operation(self): content = await result.content() + self.assertEqual(400, result.status) self.assertNotEqual(content["errors"], []) async def test_schema(self): @@ -92,6 +114,7 @@ async def test_schema(self): result = await handler.get_schema(request) + self.assertEqual(200, result.status) self.assertIsInstance(await result.content(), str) self.assertMultiLineEqual( await result.content(), "type Query {\n order_query: String\n ticket_query: String\n}" @@ -122,6 +145,7 @@ async def test_query_with_variables(self): content = await result.content() + self.assertEqual(200, result.status) self.assertDictEqual({"order_query": 3}, content["data"]) self.assertEqual([], content["errors"]) @@ -154,6 +178,7 @@ async def test_query_with_variables_return_user(self): content = await result.content() + self.assertEqual(200, result.status) self.assertDictEqual( {"order_query": {"id": "3", "firstName": "Jack", "lastName": "Johnson", "tweets": 563, "verified": True}}, content["data"], @@ -189,6 +214,7 @@ async def test_mutation(self): content = await result.content() + self.assertEqual(200, result.status) self.assertDictEqual( { "createUser": { From abe57eb5f72d2407a3db662be21771e3aea50712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Wed, 16 Mar 2022 08:49:45 +0100 Subject: [PATCH 34/44] ISSUE #149 * Minor change. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f94c5f13d..89f6913a4 100644 --- a/README.md +++ b/README.md @@ -1157,7 +1157,7 @@ The plugin packages provide connectors to external technologies like brokers, di * [minos-broker-kafka](https://minos-framework.github.io/minos-python/packages/plugins/minos-broker-kafka): The `kafka` plugin package. * [minos-discovery-minos](https://minos-framework.github.io/minos-python/packages/plugins/minos-discovery-minos): The `minos-discovery` plugin package. * [minos-http-aiohttp](https://minos-framework.github.io/minos-python/packages/plugins/minos-http-aiohttp): The `aiohttp` plugin package. -* * [minos-router-graphql](https://minos-framework.github.io/minos-python/packages/plugins/minos-router-graphql): The `grapqhl` plugin package. +* [minos-router-graphql](https://minos-framework.github.io/minos-python/packages/plugins/minos-router-graphql): The `grapqhl` plugin package. ## Source Code From d33a557d44ea130f103d99074d02614f7790445a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Wed, 16 Mar 2022 09:02:10 +0100 Subject: [PATCH 35/44] Apply suggestions from code review --- .../minos/plugins/graphql/decorators.py | 8 ++++---- .../minos-router-graphql/minos/plugins/graphql/routers.py | 2 +- .../tests/test_graphql/test_builders/test_schema.py | 2 +- .../tests/test_graphql/test_decorators.py | 2 +- .../tests/test_graphql/test_handlers.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py index 1c83e0e8e..6f7016398 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/decorators.py @@ -17,7 +17,7 @@ class GraphQlEnrouteDecorator(EnrouteDecorator, ABC): - """GraphQl Enroute class""" + """GraphQl Enroute Decorator class""" def __init__(self, name: str, output, argument: Optional = None): self.name = name @@ -29,13 +29,13 @@ def __iter__(self) -> Iterable: class GraphQlCommandEnrouteDecorator(GraphQlEnrouteDecorator): - """GraphQl Command Enroute class""" + """GraphQl Command Enroute Decorator class""" KIND: Final[EnrouteDecoratorKind] = EnrouteDecoratorKind.Command class GraphQlQueryEnrouteDecorator(GraphQlEnrouteDecorator): - """GraphQl Query Enroute class""" + """GraphQl Query Enroute Decorator class""" KIND: Final[EnrouteDecoratorKind] = EnrouteDecoratorKind.Query @@ -48,6 +48,6 @@ class GraphQlEnroute: @classmethod def register(cls): - """TODO""" + """Register the graphql sub-enroute.""" # noinspection PyProtectedMember enroute._register_sub_enroute("graphql", cls) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py index 3c216f829..9b01201dc 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py @@ -21,7 +21,7 @@ class GraphQlHttpRouter(HttpRouter): - """TODO""" + """GraphQl Http Router class.""" def _filter_routes(self, routes: dict[EnrouteDecorator, Callable]) -> dict[EnrouteDecorator, Callable]: schema = GraphQLSchemaBuilder.build(routes) diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py index 6267438bc..6a997aae1 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py @@ -31,7 +31,7 @@ async def callback_fn(request: Request): return Response("ticket #4") -class TestSchemaBuilder(unittest.IsolatedAsyncioTestCase): +class TestGraphQLSchemaBuilder(unittest.IsolatedAsyncioTestCase): CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" _config = MinosConfig(CONFIG_FILE_PATH) diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py index 4d8e70eb1..2774487ba 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py @@ -19,7 +19,7 @@ ) -class TestSomething(unittest.TestCase): +class TestGraphQlEnrouteDecorator(unittest.TestCase): def test_decorated_str(self): analyzer = EnrouteAnalyzer(classname(FakeQueryService)) self.assertEqual(FakeQueryService, analyzer.decorated) diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py index 3ef2a44f7..83c0504aa 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py @@ -45,7 +45,7 @@ async def resolve_ticket_raises(request: Request): raise ResponseException("Some error.", status=403) -class TestGraphQlHttpRouter(unittest.IsolatedAsyncioTestCase): +class TestGraphQlHandler(unittest.IsolatedAsyncioTestCase): CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" _config = MinosConfig(CONFIG_FILE_PATH) From 6d6b1ce3c2604829e712584c1f6e8a655384af9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Wed, 16 Mar 2022 10:30:44 +0100 Subject: [PATCH 36/44] ISSUE #149 * Remove warnings. * Set version to `0.0.0` --- .../minos-http-aiohttp/minos/plugins/aiohttp/__init__.py | 2 +- .../minos-router-graphql/minos/plugins/graphql/__init__.py | 2 +- packages/plugins/minos-router-graphql/pyproject.toml | 2 +- .../tests/test_graphql/test_builders/test_schema.py | 4 ++-- .../minos-router-graphql/tests/test_graphql/test_handlers.py | 4 ++-- .../minos-router-graphql/tests/test_graphql/test_routers.py | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/plugins/minos-http-aiohttp/minos/plugins/aiohttp/__init__.py b/packages/plugins/minos-http-aiohttp/minos/plugins/aiohttp/__init__.py index 9a07f1da2..a44aedd20 100644 --- a/packages/plugins/minos-http-aiohttp/minos/plugins/aiohttp/__init__.py +++ b/packages/plugins/minos-http-aiohttp/minos/plugins/aiohttp/__init__.py @@ -1,6 +1,6 @@ __author__ = "Minos Framework Devs" __email__ = "hey@minos.run" -__version__ = "0.6.0" +__version__ = "0.0.0" from .connectors import ( AioHttpConnector, diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py index 37578be32..1e038f258 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py @@ -1,6 +1,6 @@ __author__ = "Minos Framework Devs" __email__ = "hey@minos.run" -__version__ = "0.6.0" +__version__ = "0.0.0" from .builders import ( GraphQLSchemaBuilder, diff --git a/packages/plugins/minos-router-graphql/pyproject.toml b/packages/plugins/minos-router-graphql/pyproject.toml index 97dbd0367..35156832d 100644 --- a/packages/plugins/minos-router-graphql/pyproject.toml +++ b/packages/plugins/minos-router-graphql/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "minos-router-graphql" -version = "0.6.0" +version = "0.0.0" description = "The graphql plugin of the Minos Framework." readme = "README.md" repository = "https://github.com/minos-framework/minos-python" diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py index 6a997aae1..095803c62 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py @@ -8,7 +8,7 @@ ) from minos.common import ( - MinosConfig, + Config, ) from minos.networks import ( Request, @@ -33,7 +33,7 @@ async def callback_fn(request: Request): class TestGraphQLSchemaBuilder(unittest.IsolatedAsyncioTestCase): CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" - _config = MinosConfig(CONFIG_FILE_PATH) + _config = Config(CONFIG_FILE_PATH) def test_build(self): routes = { diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py index 83c0504aa..ab8f1acf5 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py @@ -7,7 +7,7 @@ ) from minos.common import ( - MinosConfig, + Config, ) from minos.networks import ( InMemoryRequest, @@ -47,7 +47,7 @@ async def resolve_ticket_raises(request: Request): class TestGraphQlHandler(unittest.IsolatedAsyncioTestCase): CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" - _config = MinosConfig(CONFIG_FILE_PATH) + _config = Config(CONFIG_FILE_PATH) async def test_execute_operation(self): routes = { diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py index 3d9487985..9681278e9 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_routers.py @@ -1,7 +1,7 @@ import unittest from minos.common import ( - MinosConfig, + Config, ) from minos.networks import ( HttpEnrouteDecorator, @@ -16,7 +16,7 @@ class TestGraphQlHttpRouter(unittest.TestCase): CONFIG_FILE_PATH = BASE_PATH / "test_config.yml" - _config = MinosConfig(CONFIG_FILE_PATH) + _config = Config(CONFIG_FILE_PATH) def test_from_config(self): router = GraphQlHttpRouter.from_config(self._config) From d37cb99c0db5e19a47b5bde1d25f2964fbad7d18 Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Wed, 16 Mar 2022 11:18:40 +0100 Subject: [PATCH 37/44] ISSUE #149 --- .../minos-router-graphql/minos/plugins/graphql/__init__.py | 5 +++++ .../minos/plugins/graphql/builders/schema.py | 2 +- .../minos-router-graphql/minos/plugins/graphql/handlers.py | 2 +- .../tests/test_graphql/test_builders/test_schema.py | 2 -- .../tests/test_graphql/test_decorators.py | 2 +- .../tests/test_graphql/test_handlers.py | 6 ++---- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py index 1e038f258..bbf8c0123 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/__init__.py @@ -6,7 +6,12 @@ GraphQLSchemaBuilder, ) from .decorators import ( + GraphQlCommandEnrouteDecorator, GraphQlEnroute, + GraphQlQueryEnrouteDecorator, +) +from .handlers import ( + GraphQlHandler, ) from .routers import ( GraphQlHttpRouter, diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py index da207fbcf..82a5bc12f 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py @@ -70,7 +70,7 @@ def _build_queries(cls, routes): fields[route.name] = cls._build_field(route, callback) result = GraphQLObjectType( - "Query", fields={"hello": GraphQLField(GraphQLString, resolve=lambda obj, info: "world")} + "Query", fields={"dummy": GraphQLString} ) if len(fields) > 0: result = GraphQLObjectType("Query", fields=fields) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py index 8179787ad..abfb3da43 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py @@ -44,7 +44,7 @@ async def execute_operation(self, request: HttpRequest) -> HttpResponse: status = 200 if len(errors): - status = 400 + status = 500 for error in errors: if isinstance(error.original_error, ResponseException): status = error.original_error.status diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py index 095803c62..abf9c7bb1 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py @@ -16,8 +16,6 @@ ) from minos.plugins.graphql import ( GraphQLSchemaBuilder, -) -from minos.plugins.graphql.decorators import ( GraphQlCommandEnrouteDecorator, GraphQlQueryEnrouteDecorator, ) diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py index 2774487ba..060820bc6 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_decorators.py @@ -11,7 +11,7 @@ from minos.networks import ( EnrouteAnalyzer, ) -from minos.plugins.graphql.decorators import ( +from minos.plugins.graphql import ( GraphQlQueryEnrouteDecorator, ) from tests.utils import ( diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py index ab8f1acf5..9512b0062 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py @@ -16,15 +16,13 @@ ResponseException, ) from minos.plugins.graphql import ( + GraphQlHandler, GraphQLSchemaBuilder, ) from minos.plugins.graphql.decorators import ( GraphQlCommandEnrouteDecorator, GraphQlQueryEnrouteDecorator, ) -from minos.plugins.graphql.handlers import ( - GraphQlHandler, -) from tests.test_graphql.test_builders.test_schema import ( callback_fn, ) @@ -97,7 +95,7 @@ async def test_execute_wrong_operation(self): content = await result.content() - self.assertEqual(400, result.status) + self.assertEqual(500, result.status) self.assertNotEqual(content["errors"], []) async def test_schema(self): From 96013c33043b56e79dab7ec6bc32518b8dc9981b Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Wed, 16 Mar 2022 10:18:49 +0000 Subject: [PATCH 38/44] Restyled by black --- .../minos/plugins/graphql/builders/schema.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py index 82a5bc12f..be9b7e4a4 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py @@ -69,9 +69,7 @@ def _build_queries(cls, routes): if route.KIND == EnrouteDecoratorKind.Query: fields[route.name] = cls._build_field(route, callback) - result = GraphQLObjectType( - "Query", fields={"dummy": GraphQLString} - ) + result = GraphQLObjectType("Query", fields={"dummy": GraphQLString}) if len(fields) > 0: result = GraphQLObjectType("Query", fields=fields) From 96ce996eb45c154c474b003830cae1b83dfbdb6c Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Wed, 16 Mar 2022 10:18:50 +0000 Subject: [PATCH 39/44] Restyled by isort --- .../tests/test_graphql/test_builders/test_schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py index abf9c7bb1..f33fa82e3 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_builders/test_schema.py @@ -15,9 +15,9 @@ Response, ) from minos.plugins.graphql import ( - GraphQLSchemaBuilder, GraphQlCommandEnrouteDecorator, GraphQlQueryEnrouteDecorator, + GraphQLSchemaBuilder, ) from tests.utils import ( BASE_PATH, From 605de860cf6ae59ff4c6e1b1936287b1ce8a8d59 Mon Sep 17 00:00:00 2001 From: Vladyslav Fenchak Date: Wed, 16 Mar 2022 14:43:54 +0100 Subject: [PATCH 40/44] ISSUE #149 --- .../plugins/minos-router-graphql/README.md | 311 +++++++++++++++++- .../minos-router-graphql/docs/index.md | 3 +- .../tests/test_graphql/test_handlers.py | 27 ++ 3 files changed, 337 insertions(+), 4 deletions(-) diff --git a/packages/plugins/minos-router-graphql/README.md b/packages/plugins/minos-router-graphql/README.md index 6aaf6f343..cf7148835 100644 --- a/packages/plugins/minos-router-graphql/README.md +++ b/packages/plugins/minos-router-graphql/README.md @@ -12,9 +12,314 @@ ## Summary -Minos is a framework which helps you create [reactive](https://www.reactivemanifesto.org/) microservices in Python. -Internally, it leverages Event Sourcing, CQRS and a message driven architecture to fulfil the commitments of an -asynchronous environment. +This is graphQL plugin for Minos framework. This plugin integrates the official [graphql-core](https://github.com/graphql-python/graphql-core) library. It is oriented to facilitate the development and better organize the graphql code. + +## How to +### Installation +Modify `config.yaml` file and add: +```yaml +... +routers: + - minos.plugins.graphql.GraphQlRouter +... +``` + +### Define your bussines operation +We will use simple query for this demostration: +```python +from graphql import ( + GraphQLString, +) + +from minos.networks import ( + Request, + Response, + enroute, +) + + +class QueryService: + @enroute.graphql.query(name="SimpleQuery", output=GraphQLString) + def simple_query(self, request: Request): + ... + + return Response("ABCD") +``` + +### Execute query +Send `post` request to `http://your_ip_address:port/graphql` endpoint: +```javascript +{ SimpleQuery } +``` + +You will receive: +```json +{ + "data": { + "SimpleQuery": "ABCD" + }, + "errors": [] +} +``` +That's all you need to make it work ! + +For more information about graphql and how to define fields or structures, please see the official [graphql-core](https://github.com/graphql-python/graphql-core). library. + +## Decorators +There are 2 types of decorators, one for `queries` and one for `mutations` (commands). + +```python +@enroute.graphql.query(name="TestQuery", argument=GraphQLString, output=GraphQLString) +def test_query(self, request: Request): + ... + return Responnse(...) + +@enroute.graphql.command(name="TestCommand", argument=GraphQLString, output=GraphQLString) +def test_command(self, request: Request): + ... + return Responnse(...) +``` + +Both decorators have the following arguments: + - `name`: The name of the query or command + - `argument` [Optional]: The arguments it receives, if any. + - `output`: The expected output. + +### Resolvers +As you have seen above, the decorator does not specify the function that will resolve it. + +This is because it automatically takes the decorated function. + +In the following example: + +```python +@enroute.graphql.query(name="TestQuery", argument=GraphQLString, output=GraphQLString) +def test_query(self, request: Request): + ... + return Responnse(...) +``` + +The function in charge of resolving the query is the decorated function `test_query`. + +### Queries (Query Service) +Queries are used for a single purpose as their name indicates and that is to obtain information, that is, for queries. + +Base structure example: +```python +class QueryService: + @enroute.graphql.query(name="TestQuery", argument=GraphQLString, output=GraphQLString) + def test_query(self, request: Request): + ... + return Responnse(...) +``` + +More complex example: +```python +from graphql import ( + GraphQLBoolean, + GraphQLField, + GraphQLID, + GraphQLInt, + GraphQLNonNull, + GraphQLObjectType, + GraphQLString, +) +from typing import ( + NamedTuple, + Optional, +) +from minos.networks import ( + Request, + Response, + enroute, +) + + +user_type = GraphQLObjectType( + "UserType", + { + "id": GraphQLField(GraphQLNonNull(GraphQLID)), + "firstName": GraphQLField(GraphQLNonNull(GraphQLString)), + "lastName": GraphQLField(GraphQLNonNull(GraphQLString)), + "tweets": GraphQLField(GraphQLInt), + "verified": GraphQLField(GraphQLNonNull(GraphQLBoolean)), + }, +) + + +class User(NamedTuple): + """A simple user object class.""" + + firstName: str + lastName: str + tweets: Optional[int] + id: Optional[str] = None + verified: bool = False + + +class QueryService: + @enroute.graphql.query(name="GetUser", argument=GraphQLInt, output=user_type) + def test_query(self, request: Request): + id = await request.content() + return Response(User(firstName="Jack", lastName="Johnson", tweets=563, id=str(id), verified=True)) +``` + +If you POST `/graphql` endpoint passing the query and variables: + +```json +{ + "query": "query ($userId: Int!) { + order_query(request: $userId) { + id + firstName + lastName + tweets + verified + } + }", + "variables": { + "userId": 3 + } +} +``` + +Yoy will receive: + +```json +{ + "data": { + "order_query": { + "id": "3", + "firstName": "Jack", + "lastName": "Johnson", + "tweets": 563, + "verified": true + } + }, + "errors": [] +} +``` + +### Mutations (Command Service) +Mutations are used to create, update or delete information. + +Base structure example: +```python +class CommandService: + @enroute.graphql.command(name="TestQuery", argument=GraphQLString, output=GraphQLString) + def test_command(self, request: Request): + ... + return Responnse(...) +``` + +More complex example: +```python +from graphql import ( + GraphQLBoolean, + GraphQLField, + GraphQLID, + GraphQLInputField, + GraphQLInputObjectType, + GraphQLInt, + GraphQLNonNull, + GraphQLObjectType, + GraphQLString, +) +from typing import ( + NamedTuple, + Optional, +) +from minos.networks import ( + Request, + Response, + enroute, +) + + +user_type = GraphQLObjectType( + "UserType", + { + "id": GraphQLField(GraphQLNonNull(GraphQLID)), + "firstName": GraphQLField(GraphQLNonNull(GraphQLString)), + "lastName": GraphQLField(GraphQLNonNull(GraphQLString)), + "tweets": GraphQLField(GraphQLInt), + "verified": GraphQLField(GraphQLNonNull(GraphQLBoolean)), + }, +) + + +user_input_type = GraphQLInputObjectType( + "UserInputType", + { + "firstName": GraphQLInputField(GraphQLNonNull(GraphQLString)), + "lastName": GraphQLInputField(GraphQLNonNull(GraphQLString)), + "tweets": GraphQLInputField(GraphQLInt), + "verified": GraphQLInputField(GraphQLBoolean), + }, +) + + +class User(NamedTuple): + """A simple user object class.""" + + firstName: str + lastName: str + tweets: Optional[int] + id: Optional[str] = None + verified: bool = False + + +class CommandService: + @enroute.graphql.command(name="GetUser", argument=GraphQLNonNull(user_input_type), output=user_type) + def test_command(self, request: Request): + params = await request.content() + return Response( + User( + firstName=params["firstName"], + lastName=params["lastName"], + tweets=params["tweets"], + id="4kjjj43-l23k4l3-325kgaa2", + verified=params["verified"], + ) + ) +``` + +If you POST `/graphql` endpoint passing the query and variables: + +```json +{ + "query": "mutation ($userData: UserInputType!) { + createUser(request: $userData) { + id, firstName, lastName, tweets, verified + } + }", + "variables": { + "userData": { + "firstName": "John", + "lastName":"Doe", + "tweets": 42, + "verified":true + } + } +} +``` + +Yoy will receive: + +```json +{ + "data": { + "createUser": { + "id": "4kjjj43-l23k4l3-325kgaa2", + "firstName": "John", + "lastName": "Doe", + "tweets": 42, + "verified": true + } + }, + "errors": [] +} +``` ## Documentation diff --git a/packages/plugins/minos-router-graphql/docs/index.md b/packages/plugins/minos-router-graphql/docs/index.md index 038a73eb8..afb71f631 100644 --- a/packages/plugins/minos-router-graphql/docs/index.md +++ b/packages/plugins/minos-router-graphql/docs/index.md @@ -1,4 +1,5 @@ -# Welcome to Minos Router GraphQl's documentation! +# GraphQL plugin form Minos framework! +This plugin integrates the official graphql-core library. .. toctree:: :maxdepth: 2 diff --git a/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py b/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py index 9512b0062..fd5d7f68f 100644 --- a/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py +++ b/packages/plugins/minos-router-graphql/tests/test_graphql/test_handlers.py @@ -39,6 +39,10 @@ async def resolve_ticket(request: Request): return Response(3) +async def resolve_simple_query(request: Request): + return Response("ABCD") + + async def resolve_ticket_raises(request: Request): raise ResponseException("Some error.", status=403) @@ -147,6 +151,29 @@ async def test_query_with_variables(self): self.assertDictEqual({"order_query": 3}, content["data"]) self.assertEqual([], content["errors"]) + async def test_simple_query(self): + routes = {GraphQlQueryEnrouteDecorator(name="SimpleQuery", output=GraphQLString): resolve_simple_query} + + schema = GraphQLSchemaBuilder.build(routes=routes) + + handler = GraphQlHandler(schema) + + query = """ + { SimpleQuery } + """ + + content = {"query": query} + + request = InMemoryRequest(content=content) + + result = await handler.execute_operation(request) + + content = await result.content() + + self.assertEqual(200, result.status) + self.assertDictEqual({"SimpleQuery": "ABCD"}, content["data"]) + self.assertEqual([], content["errors"]) + async def test_query_with_variables_return_user(self): routes = {GraphQlQueryEnrouteDecorator(name="order_query", argument=GraphQLInt, output=user_type): resolve_user} From ffa7fb8941543a3dee6f4daa45609141f97b9389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Thu, 17 Mar 2022 17:35:43 +0100 Subject: [PATCH 41/44] ISSUE #149 * Add description message. * Add type hints. --- .../minos/plugins/graphql/builders/schema.py | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py index be9b7e4a4..48e9c7ca7 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py @@ -2,6 +2,9 @@ annotations, ) +from collections.abc import ( + Callable, +) from functools import ( wraps, ) @@ -10,6 +13,9 @@ ) from typing import ( Any, + Awaitable, + Optional, + Union, ) from graphql import ( @@ -23,6 +29,12 @@ from minos.networks import ( EnrouteDecoratorKind, InMemoryRequest, + Request, + Response, +) + +from ..decorators import ( + GraphQlEnrouteDecorator, ) @@ -33,20 +45,22 @@ def __init__(self, *args, **kwargs): self.schema = GraphQLSchema(**kwargs) @classmethod - def build(cls, routes) -> GraphQLSchema: + def build(cls, routes: dict[GraphQlEnrouteDecorator, Callable]) -> GraphQLSchema: """TODO""" schema_args = cls._build(routes) return cls(**schema_args).schema @classmethod - def _build(cls, routes) -> dict: + def _build(cls, routes: dict[GraphQlEnrouteDecorator, Callable]) -> dict[str, Optional[GraphQLObjectType]]: query = cls._build_queries(routes) mutation = cls._build_mutations(routes) return {"query": query, "mutation": mutation} @staticmethod - def adapt_callback(callback): + def adapt_callback( + callback: Callable[[Request], Union[Optional[Response], Awaitable[Optional[Response]]]] + ) -> Callable[[Any, Any, Any], Awaitable[Any]]: """TODO""" @wraps(callback) @@ -62,21 +76,23 @@ async def _wrapper(_source, _info, request: Any = None): return _wrapper @classmethod - def _build_queries(cls, routes): + def _build_queries(cls, routes: dict[GraphQlEnrouteDecorator, Callable]) -> GraphQLObjectType: fields = dict() for route, callback in routes.items(): callback = cls.adapt_callback(callback) if route.KIND == EnrouteDecoratorKind.Query: fields[route.name] = cls._build_field(route, callback) - result = GraphQLObjectType("Query", fields={"dummy": GraphQLString}) - if len(fields) > 0: - result = GraphQLObjectType("Query", fields=fields) + if not len(fields): + fields["dummy"] = GraphQLField( + type_=GraphQLString, + description="Dummy query added to surpass the 'Type Query must define at least one field' constraint." + ) - return result + return GraphQLObjectType("Query", fields=fields) @classmethod - def _build_mutations(cls, routes): + def _build_mutations(cls, routes: dict[GraphQlEnrouteDecorator, Callable]) -> Optional[GraphQLObjectType]: fields = dict() for route, callback in routes.items(): callback = cls.adapt_callback(callback) @@ -84,14 +100,13 @@ def _build_mutations(cls, routes): if route.KIND == EnrouteDecoratorKind.Command: fields[route.name] = cls._build_field(route, callback) - result = None - if len(fields) > 0: - result = GraphQLObjectType("Mutation", fields=fields) + if not len(fields): + return None - return result + return GraphQLObjectType("Mutation", fields=fields) @staticmethod - def _build_field(item, callback): + def _build_field(item: GraphQlEnrouteDecorator, callback: Callable) -> GraphQLField: args = None if item.argument is not None: args = {"request": GraphQLArgument(item.argument)} From dcf5a4e162f6a92baafde499d30bfea74c0559dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Thu, 17 Mar 2022 17:42:50 +0100 Subject: [PATCH 42/44] ISSUE #149 * Minor change. --- .../minos/plugins/graphql/builders/schema.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py index 48e9c7ca7..6345aeb1d 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py @@ -107,7 +107,11 @@ def _build_mutations(cls, routes: dict[GraphQlEnrouteDecorator, Callable]) -> Op @staticmethod def _build_field(item: GraphQlEnrouteDecorator, callback: Callable) -> GraphQLField: + argument = item.argument + output = item.output + args = None - if item.argument is not None: - args = {"request": GraphQLArgument(item.argument)} - return GraphQLField(item.output, args=args, resolve=callback) + if argument is not None: + args = {"request": GraphQLArgument(argument)} + + return GraphQLField(output, args=args, resolve=callback) From ff072562626193eb180d92053a485d14954d14ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Fri, 18 Mar 2022 10:03:00 +0100 Subject: [PATCH 43/44] ISSUE #150 * Reformat code. --- .../minos/plugins/graphql/builders/schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py index 6345aeb1d..6e9a4b77b 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py @@ -86,7 +86,7 @@ def _build_queries(cls, routes: dict[GraphQlEnrouteDecorator, Callable]) -> Grap if not len(fields): fields["dummy"] = GraphQLField( type_=GraphQLString, - description="Dummy query added to surpass the 'Type Query must define at least one field' constraint." + description="Dummy query added to surpass the 'Type Query must define at least one field' constraint.", ) return GraphQLObjectType("Query", fields=fields) From 12604df19943e7232343c8d7467407ea3b8e568a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergio=20Garc=C3=ADa=20Prado?= Date: Fri, 18 Mar 2022 12:44:55 +0100 Subject: [PATCH 44/44] ISSUE #149 * Minor improvements. --- .../minos/plugins/graphql/builders/schema.py | 14 ++++-- .../minos/plugins/graphql/handlers.py | 44 +++++++++++++------ .../minos/plugins/graphql/routers.py | 8 ++++ 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py index 6e9a4b77b..4655d217b 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/builders/schema.py @@ -39,14 +39,18 @@ class GraphQLSchemaBuilder: - """TODO""" + """GraphQL Schema Builder class.""" def __init__(self, *args, **kwargs): self.schema = GraphQLSchema(**kwargs) @classmethod def build(cls, routes: dict[GraphQlEnrouteDecorator, Callable]) -> GraphQLSchema: - """TODO""" + """Build a new schema from routes. + + :param routes: The routes to build the schema. + :return: A ``GraphQLSchema`` instance. + """ schema_args = cls._build(routes) return cls(**schema_args).schema @@ -61,7 +65,11 @@ def _build(cls, routes: dict[GraphQlEnrouteDecorator, Callable]) -> dict[str, Op def adapt_callback( callback: Callable[[Request], Union[Optional[Response], Awaitable[Optional[Response]]]] ) -> Callable[[Any, Any, Any], Awaitable[Any]]: - """TODO""" + """Adapt a callback from framework's Request-Response to GraphQl structure. + + :param callback: The callback to be adapted. + :return: The adapted callback. + """ @wraps(callback) async def _wrapper(_source, _info, request: Any = None): diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py index abfb3da43..6d8a0cd54 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/handlers.py @@ -1,12 +1,17 @@ +from typing import ( + Any, +) + from graphql import ( + ExecutionResult, GraphQLSchema, graphql, print_schema, ) from minos.networks import ( - HttpRequest, - HttpResponse, + Request, + Response, ResponseException, ) @@ -17,26 +22,33 @@ class GraphQlHandler: def __init__(self, schema: GraphQLSchema): self._schema = schema - async def execute_operation(self, request: HttpRequest) -> HttpResponse: - """Execute incoming request extracting variables and passing to graphql""" + async def execute_operation(self, request: Request) -> Response: + """Execute incoming request extracting variables and passing to graphql. - content = await request.content() + :param request: The request containing the graphql operation. + :return: A response containing the graphql result. + """ + result = await graphql(schema=self._schema, **(await self._build_graphql_arguments(request))) + return self._build_response_from_graphql(result) + @staticmethod + async def _build_graphql_arguments(request: Request) -> dict[str, Any]: + content = await request.content() source = dict() variables = dict() if isinstance(content, str): source = content - - if isinstance(content, dict): + elif isinstance(content, dict): if "query" in content: source = content["query"] - if "variables" in content: variables = content["variables"] - result = await graphql(schema=self._schema, source=source, variable_values=variables) + return {"source": source, "variable_values": variables} + @staticmethod + def _build_response_from_graphql(result: ExecutionResult) -> Response: errors = result.errors if errors is None: errors = list() @@ -49,8 +61,14 @@ async def execute_operation(self, request: HttpRequest) -> HttpResponse: if isinstance(error.original_error, ResponseException): status = error.original_error.status - return HttpResponse({"data": result.data, "errors": [err.message for err in errors]}, status=status) + content = {"data": result.data, "errors": [err.message for err in errors]} + + return Response(content, status=status) + + async def get_schema(self, request: Request) -> Response: + """Get schema - async def get_schema(self, request: HttpRequest) -> HttpResponse: - """Get schema""" - return HttpResponse(print_schema(self._schema)) + :param request: An empty request. + :return: A Response containing the schema as a string. + """ + return Response(print_schema(self._schema)) diff --git a/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py b/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py index 9b01201dc..322b1105a 100644 --- a/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py +++ b/packages/plugins/minos-router-graphql/minos/plugins/graphql/routers.py @@ -15,6 +15,9 @@ from .builders import ( GraphQLSchemaBuilder, ) +from .decorators import ( + GraphQlEnrouteDecorator, +) from .handlers import ( GraphQlHandler, ) @@ -24,6 +27,11 @@ class GraphQlHttpRouter(HttpRouter): """GraphQl Http Router class.""" def _filter_routes(self, routes: dict[EnrouteDecorator, Callable]) -> dict[EnrouteDecorator, Callable]: + routes = { + decorator: callback + for decorator, callback in routes.items() + if isinstance(decorator, GraphQlEnrouteDecorator) + } schema = GraphQLSchemaBuilder.build(routes) handler = GraphQlHandler(schema) return {