Commit 95c22494 authored by Viacheslav Sukhovieiev's avatar Viacheslav Sukhovieiev
Browse files

Merge branch 'kpn/perf_refactor' into 'master'

Feat(Perf): performance refactoring

See merge request !1
parents ea321152 531cc193
# These are supported funding model platforms
github: [cr0hn]
language: python
sudo: false
python:
- "3.5"
- "3.6"
- "3.7"
- "3.8"
env:
- AIOHTTP_VERSION=3.6.2
- AIOHTTP_VERSION=2.3.10
install:
- pip install -U pip
- pip install -q aiohttp==$AIOHTTP_VERSION
- python setup.py install
- pip install -r requirements-dev.txt
- pip install -U codecov
script:
- make cov
after_success:
- codecov
deploy:
# Deploy to PyPI
provider: pypi
user: $PYPI_USER
password: $PYPI_PASSWD
distributions: "sdist"
on:
tags: true
python: 3.7
condition: $AIOHTTP_VERSION = 3.6.2
# - provider: script
# script: deploy/pypi.sh
# skip_cleanup: true
# on:
# branch: master
......@@ -19,7 +19,8 @@ Python version | Python 3.4 and above
What's aiohttp-swagger
----------------------
aiohttp-swagger is a plugin for aiohttp.web server that allow to document APIs using Swagger show the Swagger-ui console.
aiohttp-swagger is a plugin for aiohttp.web server that allow to document APIs using Swagger show the Swagger-ui
console.
What's new?
-----------
......
import asyncio
from os.path import abspath, dirname, join
from types import FunctionType
......@@ -27,31 +26,29 @@ async def _swagger_def(request):
"""
Returns the Swagger JSON Definition
"""
return web.json_response(text=request.app["SWAGGER_DEF_CONTENT"])
def setup_swagger(app: web.Application,
*,
swagger_from_file: str = None,
swagger_url: str = "/api/doc",
api_base_url: str = "/",
swagger_validator_url: str = "",
description: str = "Swagger API definition",
api_version: str = "1.0.0",
ui_version: int = None,
title: str = "Swagger API",
contact: str = "",
swagger_home_decor: FunctionType = None,
swagger_def_decor: FunctionType = None,
swagger_info: dict = None,
swagger_template_path: str = None,
definitions: dict = None,
security_definitions: dict = None):
_swagger_url = ("/{}".format(swagger_url)
if not swagger_url.startswith("/")
else swagger_url)
return web.json_response(text=request.app["SWAGGER_DEF_CONTENT"], dumps=json.dumps)
async def setup_swagger(app: web.Application,
*,
swagger_from_file: str = None,
swagger_url: str = "/api/doc",
api_base_url: str = "/",
swagger_validator_url: str = "",
description: str = "Swagger API definition",
api_version: str = "1.0.0",
ui_version: int = None,
title: str = "Swagger API",
contact: str = "",
swagger_home_decor: FunctionType = None,
swagger_def_decor: FunctionType = None,
swagger_info: dict = None,
swagger_template_path: str = None,
definitions: dict = None,
security_definitions: dict = None):
_swagger_url = (f"/{swagger_url}" if not swagger_url.startswith("/") else swagger_url)
_base_swagger_url = _swagger_url.rstrip('/')
_swagger_def_url = '{}/swagger.json'.format(_base_swagger_url)
_swagger_def_url = f'{_base_swagger_url}/swagger.json'
if ui_version == 3:
STATIC_PATH = abspath(join(dirname(__file__), "swagger_ui3"))
......@@ -61,9 +58,9 @@ def setup_swagger(app: web.Application,
# Build Swagget Info
if swagger_info is None:
if swagger_from_file:
swagger_info = load_doc_from_yaml_file(swagger_from_file)
swagger_info = await load_doc_from_yaml_file(swagger_from_file)
else:
swagger_info = generate_doc_from_each_end_point(
swagger_info = await generate_doc_from_each_end_point(
app, ui_version=ui_version,
api_base_url=api_base_url, description=description,
api_version=api_version, title=title, contact=contact,
......@@ -85,12 +82,11 @@ def setup_swagger(app: web.Application,
# Add API routes
app.router.add_route('GET', _swagger_url, _swagger_home_func)
app.router.add_route('GET', "{}/".format(_base_swagger_url),
_swagger_home_func)
app.router.add_route('GET', f"{_base_swagger_url}/", _swagger_home_func)
app.router.add_route('GET', _swagger_def_url, _swagger_def_func)
# Set statics
statics_path = '{}/swagger_static'.format(_base_swagger_url)
statics_path = f'{_base_swagger_url}/swagger_static'
app.router.add_static(statics_path, STATIC_PATH)
# --------------------------------------------------------------------------
......@@ -100,11 +96,9 @@ def setup_swagger(app: web.Application,
with open(join(STATIC_PATH, "index.html"), "r") as f:
app["SWAGGER_TEMPLATE_CONTENT"] = (
f.read()
.replace("##SWAGGER_CONFIG##", '{}{}'.
format(api_base_url.rstrip('/'), _swagger_def_url))
.replace("##STATIC_PATH##", '{}{}'.
format(api_base_url.rstrip('/'), statics_path))
.replace("##SWAGGER_VALIDATOR_URL##", swagger_validator_url)
.replace("##SWAGGER_CONFIG##", f"{api_base_url.rstrip('/')}{_swagger_def_url}")
.replace("##STATIC_PATH##", f"{api_base_url.rstrip('/')}{statics_path}")
.replace("##SWAGGER_VALIDATOR_URL##", swagger_validator_url)
)
......
from collections import defaultdict
from os.path import abspath, dirname, join
from inspect import isclass
from os.path import abspath, dirname, join
import yaml
from yaml import load, YAMLError
try:
from yaml import CFullLoader as Loader
except ImportError: # pragma: no cover
from yaml import FullLoader as Loader
from aiohttp import web
from aiohttp.hdrs import METH_ANY, METH_ALL
from jinja2 import Environment, BaseLoader
try:
import ujson as json
except ImportError: # pragma: no cover
except ImportError: # pragma: no cover
import json
SWAGGER_TEMPLATE = abspath(join(dirname(__file__), "..", "templates"))
......@@ -27,9 +31,9 @@ def _extract_swagger_docs(end_point_doc, method="get"):
# Build JSON YAML Obj
try:
end_point_swagger_doc = (
yaml.full_load("\n".join(end_point_doc[end_point_swagger_start:]))
load("\n".join(end_point_doc[end_point_swagger_start:]), Loader=Loader)
)
except yaml.YAMLError:
except YAMLError:
end_point_swagger_doc = {
"description": "⚠ Swagger document could not be loaded "
"from docstring ⚠",
......@@ -39,7 +43,6 @@ def _extract_swagger_docs(end_point_doc, method="get"):
def _build_doc_from_func_doc(route):
out = {}
if isclass(route.handler) and issubclass(route.handler, web.View):
for method_name in _get_method_names_for_handler(route):
......@@ -56,6 +59,7 @@ def _build_doc_from_func_doc(route):
out.update(_extract_swagger_docs(end_point_doc, method=str(route.method).lower()))
return out
def _get_method_names_for_handler(route):
# Return all valid method names in handler if the method is *,
# otherwise return the specific method.
......@@ -71,18 +75,18 @@ def _get_method_names_for_handler(route):
}
def generate_doc_from_each_end_point(
app: web.Application,
*,
ui_version: int = None,
api_base_url: str = "/",
description: str = "Swagger API definition",
api_version: str = "1.0.0",
title: str = "Swagger API",
contact: str = "",
template_path: str = None,
definitions: dict = None,
security_definitions: dict = None):
async def generate_doc_from_each_end_point(
app: web.Application,
*,
ui_version: int = None,
api_base_url: str = "/",
description: str = "Swagger API definition",
api_version: str = "1.0.0",
title: str = "Swagger API",
contact: str = "",
template_path: str = None,
definitions: dict = None,
security_definitions: dict = None):
# Clean description
_start_desc = 0
for i, word in enumerate(description):
......@@ -93,17 +97,17 @@ def generate_doc_from_each_end_point(
def nesteddict2yaml(d, indent=10, result=""):
for key, value in d.items():
result += " " * indent + str(key) + ':'
result += " " * indent + f'{str(key)}:'
if isinstance(value, dict):
result = nesteddict2yaml(value, indent + 2, result + "\n")
result = nesteddict2yaml(value, indent + 2, f'{result}\n')
elif isinstance(value, str):
result += " \"" + str(value) + "\"\n"
result += f' "{str(value)}"\n'
else:
result += " " + str(value) + "\n"
result += f' {str(value)}\n'
return result
# Load base Swagger template
jinja2_env = Environment(loader=BaseLoader())
jinja2_env = Environment(loader=BaseLoader(), enable_async=True)
jinja2_env.filters['nesteddict2yaml'] = nesteddict2yaml
if template_path is None:
......@@ -114,7 +118,7 @@ def generate_doc_from_each_end_point(
with open(template_path, "r") as f:
swagger_base = (
jinja2_env.from_string(f.read()).render(
await jinja2_env.from_string(f.read()).render_async(
description=cleaned_description,
version=api_version,
title=title,
......@@ -125,7 +129,7 @@ def generate_doc_from_each_end_point(
)
# The Swagger OBJ
swagger = yaml.full_load(swagger_base)
swagger = load(swagger_base, Loader=Loader)
swagger["paths"] = defaultdict(dict)
for route in app.router.routes():
......@@ -138,9 +142,9 @@ def generate_doc_from_each_end_point(
with open(route.handler.swagger_file, "r") as f:
end_point_doc = {
route.method.lower():
yaml.full_load(f.read())
load(f, Loader=Loader)
}
except yaml.YAMLError:
except YAMLError:
end_point_doc = {
route.method.lower(): {
"description": "⚠ Swagger document could not be "
......@@ -175,10 +179,9 @@ def generate_doc_from_each_end_point(
return json.dumps(swagger)
def load_doc_from_yaml_file(doc_path: str):
async def load_doc_from_yaml_file(doc_path: str):
with open(doc_path, "r") as f:
loaded_yaml = yaml.full_load(f.read())
return json.dumps(loaded_yaml)
return json.dumps(load(f, Loader=Loader))
__all__ = ("generate_doc_from_each_end_point", "load_doc_from_yaml_file")
class swagger_path(object):
class swagger_path:
__slots__ = ['swagger_file']
def __init__(self, swagger_file):
self.swagger_file = swagger_file
def __call__(self, f):
f.swagger_file = self.swagger_file
return f
......@@ -23,7 +23,7 @@ sys.path.insert(0, os.path.abspath(os.path.join('..', "..")))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# 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
......@@ -45,7 +45,7 @@ templates_path = ['_templates']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
......@@ -75,7 +75,6 @@ author = u'Daniel Garcia - cr0hn'
version = "1.0"
release = "1.0.0"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
......@@ -85,9 +84,9 @@ language = "en"
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
......@@ -95,33 +94,32 @@ exclude_patterns = []
# The reST default role (used for this markup: `text`) to use for all
# documents.
#default_role = None
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
highlight_language = "python3"
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# keep_warnings = False
# 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
......@@ -151,7 +149,6 @@ html_theme_options = {
'logo_text_align': "center"
}
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
......@@ -164,7 +161,7 @@ html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
......@@ -184,27 +181,27 @@ html_sidebars = {
}
# If false, no module index is generated.
#html_domain_indices = True
# html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = False
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# html_use_opensearch = ''
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
......@@ -214,11 +211,11 @@ html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
#html_search_options = {'type': 'default'}
# html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = 'scorer.js'
# html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'aiohttp-swaggerdoc'
......@@ -226,17 +223,17 @@ htmlhelp_basename = 'aiohttp-swaggerdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
# Latex figure (float) alignment
#'figure_align': 'htbp',
# Latex figure (float) alignment
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
......@@ -249,23 +246,23 @@ latex_documents = [
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
......@@ -278,7 +275,7 @@ man_pages = [
]
# If true, show URL addresses after external links.
#man_show_urls = False
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
......@@ -293,16 +290,16 @@ texinfo_documents = [
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
# texinfo_no_detailmenu = False
# -- Options for Epub output ----------------------------------------------
......@@ -314,62 +311,62 @@ epub_publisher = author
epub_copyright = copyright
# The basename for the epub file. It defaults to the project name.
#epub_basename = project
# epub_basename = project
# The HTML theme for the epub output. Since the default themes are not
# optimized for small screen space, using the same theme for HTML and epub
# output is usually not wise. This defaults to 'epub', a theme designed to save
# visual space.
#epub_theme = 'epub'
# epub_theme = 'epub'
# The language of the text. It defaults to the language option
# or 'en' if the language is not set.
#epub_language = ''
# epub_language = ''
# The scheme of the identifier. Typical schemes are ISBN or URL.
#epub_scheme = ''
# epub_scheme = ''
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#epub_identifier = ''
# epub_identifier = ''
# A unique identification for the text.
#epub_uid = ''
# epub_uid = ''
# A tuple containing the cover image and cover page html template filenames.
#epub_cover = ()
# epub_cover = ()
# A sequence of (type, uri, title) tuples for the guide element of content.opf.
#epub_guide = ()
# epub_guide = ()
# HTML files that should be inserted before the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_pre_files = []
# epub_pre_files = []
# HTML files shat should be inserted after the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_post_files = []
# epub_post_files = []
# A list of files that should not be packed into the epub file.
epub_exclude_files = ['search.html']
# The depth of the table of contents in toc.ncx.
#epub_tocdepth = 3
# epub_tocdepth = 3
# Allow duplicate toc entries.
#epub_tocdup = True
# epub_tocdup = True
# Choose between 'default' and 'includehidden'.
#epub_tocscope = 'default'
# epub_tocscope = 'default'
# Fix unsupported image types using the Pillow.
#epub_fix_images = False
# epub_fix_images = False
# Scale large images.
#epub_max_image_width = 0
# epub_max_image_width = 0
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#epub_show_urls = 'inline'
# epub_show_urls = 'inline'
# If false, no index is generated.
#epub_use_index = True
# epub_use_index = True
from os.path import join, dirname
from aiohttp import web
if __name__ == '__main__':
from aiohttp_swagger import *