From bcdf390abe4284df9336876dc668dc94b6b65fa8 Mon Sep 17 00:00:00 2001 From: Sebastian Tobie Date: Thu, 30 Aug 2018 11:54:22 +0200 Subject: [PATCH 1/3] docs refactoring 1/4 Parser cleanup its bad style to set __all__ with values that doesnt exist. Its confusing. Signed-off-by: Sebastian Tobie --- vibora/parsers/__init__.py | 2 +- vibora/parsers/parser.pyx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vibora/parsers/__init__.py b/vibora/parsers/__init__.py index 64764dd..ebccafd 100644 --- a/vibora/parsers/__init__.py +++ b/vibora/parsers/__init__.py @@ -1,4 +1,4 @@ from . import parser, response, errors from .parser import parse_url -__all__ = parser.__all__ + errors.__all__ + response.__all__ +__all__ = ['parser', 'response', 'errors', 'parse_url'] diff --git a/vibora/parsers/parser.pyx b/vibora/parsers/parser.pyx index 4b7b131..5566046 100644 --- a/vibora/parsers/parser.pyx +++ b/vibora/parsers/parser.pyx @@ -15,7 +15,7 @@ from . cimport cparser from ..protocol.cprotocol cimport Connection from ..headers.headers cimport Headers -__all__ = ('parse_url', 'HttpParser', 'HttpResponseParser') +__all__ = ('parse_url', 'HttpParser') cdef class HttpParser: def __init__(self, Connection protocol, max_headers_size: int, max_body_size: int): From 6edfd6b4219f6bd0929ead36d611ea437b37df45 Mon Sep 17 00:00:00 2001 From: Sebastian Tobie Date: Thu, 30 Aug 2018 11:59:29 +0200 Subject: [PATCH 2/3] docs refactoring 2/4 cython now embeds docstrings into the binary. If we document the cython pyx and pxd, the hints files are obsolete and stop breaking sphinx. And we dont need two implementations of the same class. Signed-off-by: Sebastian Tobie --- build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.py b/build.py index 76ce68b..318b43a 100644 --- a/build.py +++ b/build.py @@ -18,7 +18,7 @@ # Calling Cython to compile our extensions. cython = os.path.join(os.path.dirname(sys.executable), 'cython') -process = subprocess.run([cython] + list(pending_compilation) + ['--fast-fail']) +process = subprocess.run([cython] + list(pending_compilation) + ['--fast-fail', '-X', 'embedsignature=true']) if process.returncode != 0: raise SystemExit(f'Failed to compile .pyx files to C.') From 9a58dbfcc965a8fc182046fbd360f0f9d2741448 Mon Sep 17 00:00:00 2001 From: Sebastian Tobie Date: Thu, 30 Aug 2018 12:12:31 +0200 Subject: [PATCH 3/3] docs refactoring 3/4 the big I reformated the markdown files to restrucured Text and putted them into docs/docs. The docs/api directory has the required files for the automatic documentation of the modules. sphinx-autodoc-typehints adds the typehints to the docs. Signed-off-by: Sebastian Tobie --- docs/api.rst | 37 ++++ docs/api/application.rst | 4 + docs/api/blueprints.rst | 4 + docs/api/cache.rst | 4 + docs/api/client.rst | 28 +++ docs/api/components.rst | 7 + docs/api/constants.rst | 5 + docs/api/context.rst | 5 + docs/api/cookies.rst | 5 + docs/api/exceptions.rst | 5 + docs/api/headers.rst | 5 + docs/api/hooks.rst | 5 + docs/api/limits.rst | 5 + docs/api/multipart.rst | 7 + docs/api/optimizer.rst | 5 + docs/api/parsers.rst | 10 + docs/api/protocol.rst | 11 ++ docs/api/request.rst | 5 + docs/api/responses.rst | 5 + docs/api/router.rst | 6 + docs/api/schemas.rst | 20 ++ docs/api/server.rst | 5 + docs/api/sessions.rst | 8 + docs/api/static.rst | 5 + docs/api/templates.rst | 17 ++ docs/api/utils.rst | 5 + docs/api/websockets.rst | 5 + docs/api/workers.rst | 8 + docs/client/examples.md | 0 docs/client/initial.md | 0 docs/client/session.md | 0 docs/client/websocket.md | 0 docs/components/initial.md | 87 --------- docs/conf.py | 175 ++++++++++++++++++ docs/configs.md | 109 ----------- docs/docs.rst | 22 +++ docs/docs/client/examples.rst | 2 + docs/docs/client/initial.rst | 10 + docs/docs/client/session.rst | 2 + docs/docs/client/websocket.rst | 2 + docs/docs/components/initial.rst | 89 +++++++++ .../components/request.rst} | 119 ++++++------ docs/docs/configs.rst | 103 +++++++++++ .../contributing.rst} | 11 +- docs/{deploy.md => docs/deploy.rst} | 3 +- docs/{events.md => docs/events.rst} | 32 ++-- docs/docs/extensions.rst | 4 + docs/{faq.md => docs/faq.rst} | 41 ++-- docs/docs/logging.rst | 27 +++ docs/{responses.md => docs/responses.rst} | 68 ++++--- docs/{routing.md => docs/routing.rst} | 146 +++++++-------- .../fields.md => docs/schemas/fields.rst} | 32 ++-- docs/docs/schemas/initial.rst | 89 +++++++++ docs/{started.md => docs/started.rst} | 28 +-- .../engine.md => docs/templates/engine.rst} | 11 +- docs/docs/templates/extending.rst | 4 + docs/docs/templates/performance.rst | 4 + .../syntax.md => docs/templates/syntax.rst} | 31 ++-- docs/docs/testing/advanced.rst | 4 + docs/docs/testing/started.rst | 33 ++++ docs/extensions.md | 1 - docs/index.rst | 16 ++ docs/logging.md | 29 --- docs/redirects/page.md | 1 - docs/schemas/initial.md | 80 -------- docs/summary.md | 23 --- docs/templates/extending.md | 1 - docs/templates/performance.md | 1 - docs/testing/advanced.md | 1 - docs/testing/started.md | 28 --- setup.py | 2 +- 71 files changed, 1096 insertions(+), 616 deletions(-) create mode 100644 docs/api.rst create mode 100644 docs/api/application.rst create mode 100644 docs/api/blueprints.rst create mode 100644 docs/api/cache.rst create mode 100644 docs/api/client.rst create mode 100644 docs/api/components.rst create mode 100644 docs/api/constants.rst create mode 100644 docs/api/context.rst create mode 100644 docs/api/cookies.rst create mode 100644 docs/api/exceptions.rst create mode 100644 docs/api/headers.rst create mode 100644 docs/api/hooks.rst create mode 100644 docs/api/limits.rst create mode 100644 docs/api/multipart.rst create mode 100644 docs/api/optimizer.rst create mode 100644 docs/api/parsers.rst create mode 100644 docs/api/protocol.rst create mode 100644 docs/api/request.rst create mode 100644 docs/api/responses.rst create mode 100644 docs/api/router.rst create mode 100644 docs/api/schemas.rst create mode 100644 docs/api/server.rst create mode 100644 docs/api/sessions.rst create mode 100644 docs/api/static.rst create mode 100644 docs/api/templates.rst create mode 100644 docs/api/utils.rst create mode 100644 docs/api/websockets.rst create mode 100644 docs/api/workers.rst delete mode 100644 docs/client/examples.md delete mode 100644 docs/client/initial.md delete mode 100644 docs/client/session.md delete mode 100644 docs/client/websocket.md delete mode 100644 docs/components/initial.md create mode 100644 docs/conf.py delete mode 100644 docs/configs.md create mode 100644 docs/docs.rst create mode 100644 docs/docs/client/examples.rst create mode 100644 docs/docs/client/initial.rst create mode 100644 docs/docs/client/session.rst create mode 100644 docs/docs/client/websocket.rst create mode 100644 docs/docs/components/initial.rst rename docs/{components/request.md => docs/components/request.rst} (50%) create mode 100644 docs/docs/configs.rst rename docs/{contributing.md => docs/contributing.rst} (88%) rename docs/{deploy.md => docs/deploy.rst} (97%) rename docs/{events.md => docs/events.rst} (59%) create mode 100644 docs/docs/extensions.rst rename docs/{faq.md => docs/faq.rst} (76%) create mode 100644 docs/docs/logging.rst rename docs/{responses.md => docs/responses.rst} (66%) rename docs/{routing.md => docs/routing.rst} (53%) rename docs/{schemas/fields.md => docs/schemas/fields.rst} (76%) create mode 100644 docs/docs/schemas/initial.rst rename docs/{started.md => docs/started.rst} (63%) rename docs/{templates/engine.md => docs/templates/engine.rst} (86%) create mode 100644 docs/docs/templates/extending.rst create mode 100644 docs/docs/templates/performance.rst rename docs/{templates/syntax.md => docs/templates/syntax.rst} (61%) create mode 100644 docs/docs/testing/advanced.rst create mode 100644 docs/docs/testing/started.rst delete mode 100644 docs/extensions.md create mode 100644 docs/index.rst delete mode 100644 docs/logging.md delete mode 100644 docs/redirects/page.md delete mode 100644 docs/schemas/initial.md delete mode 100644 docs/summary.md delete mode 100644 docs/templates/extending.md delete mode 100644 docs/templates/performance.md delete mode 100644 docs/testing/advanced.md delete mode 100644 docs/testing/started.md diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 0000000..1e1a7bf --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,37 @@ +API +=== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + api/cache + api/client + api/components + api/headers + api/multipart + api/protocol + api/request + api/responses + api/router + api/schemas + api/sessions + api/templates + api/websockets + api/workers + api/application + api/blueprints + api/constants + api/context + api/cookies + api/exceptions + api/hooks + api/limits + api/optimizer + api/server + api/static + api/utils + api/parsers + + +.. automodule:: vibora diff --git a/docs/api/application.rst b/docs/api/application.rst new file mode 100644 index 0000000..13ff90b --- /dev/null +++ b/docs/api/application.rst @@ -0,0 +1,4 @@ +Application +=========== + +.. automodule:: vibora.application diff --git a/docs/api/blueprints.rst b/docs/api/blueprints.rst new file mode 100644 index 0000000..4cc77d3 --- /dev/null +++ b/docs/api/blueprints.rst @@ -0,0 +1,4 @@ +Blueprints +========== + +.. automodule:: vibora.blueprints diff --git a/docs/api/cache.rst b/docs/api/cache.rst new file mode 100644 index 0000000..d353b09 --- /dev/null +++ b/docs/api/cache.rst @@ -0,0 +1,4 @@ +Cache +===== + +.. .. automodule:: vibora.cache.cache diff --git a/docs/api/client.rst b/docs/api/client.rst new file mode 100644 index 0000000..5ebe0cd --- /dev/null +++ b/docs/api/client.rst @@ -0,0 +1,28 @@ +Client +====== + +.. automodule:: vibora.client + +.. automodule:: vibora.client.session + +.. automodule:: vibora.client.response + +.. automodule:: vibora.client.retries + +.. automodule:: vibora.client.connection + +.. automodule:: vibora.client.decoders + +.. automodule:: vibora.client.defaults + +.. automodule:: vibora.client.exceptions + +.. automodule:: vibora.client.limits + +.. automodule:: vibora.client.pool + +.. automodule:: vibora.client.request + +.. automodule:: vibora.client.response + +.. automodule:: vibora.client.websocket diff --git a/docs/api/components.rst b/docs/api/components.rst new file mode 100644 index 0000000..2c1ad4d --- /dev/null +++ b/docs/api/components.rst @@ -0,0 +1,7 @@ +Components +========== + +.. automodule:: vibora.components.components + +.. I have no Idea why this is needed + .. automodule:: vibora.components.context diff --git a/docs/api/constants.rst b/docs/api/constants.rst new file mode 100644 index 0000000..44c0eed --- /dev/null +++ b/docs/api/constants.rst @@ -0,0 +1,5 @@ +Constants +========= + +.. automodule:: vibora.constants + diff --git a/docs/api/context.rst b/docs/api/context.rst new file mode 100644 index 0000000..8f4d40e --- /dev/null +++ b/docs/api/context.rst @@ -0,0 +1,5 @@ +Context +======= + +.. automodule:: vibora.context + diff --git a/docs/api/cookies.rst b/docs/api/cookies.rst new file mode 100644 index 0000000..19312d1 --- /dev/null +++ b/docs/api/cookies.rst @@ -0,0 +1,5 @@ +Cookies +======= + +.. automodule:: vibora.cookies + diff --git a/docs/api/exceptions.rst b/docs/api/exceptions.rst new file mode 100644 index 0000000..98038cf --- /dev/null +++ b/docs/api/exceptions.rst @@ -0,0 +1,5 @@ +Exceptions +========== + +.. automodule:: vibora.exceptions + diff --git a/docs/api/headers.rst b/docs/api/headers.rst new file mode 100644 index 0000000..2e7a9a4 --- /dev/null +++ b/docs/api/headers.rst @@ -0,0 +1,5 @@ +Headers +======= + +.. automodule:: vibora.headers.headers + diff --git a/docs/api/hooks.rst b/docs/api/hooks.rst new file mode 100644 index 0000000..c02ddbf --- /dev/null +++ b/docs/api/hooks.rst @@ -0,0 +1,5 @@ +Hooks +===== + +.. automodule:: vibora.hooks + diff --git a/docs/api/limits.rst b/docs/api/limits.rst new file mode 100644 index 0000000..7ec48f9 --- /dev/null +++ b/docs/api/limits.rst @@ -0,0 +1,5 @@ +Limits +====== + +.. automodule:: vibora.limits + diff --git a/docs/api/multipart.rst b/docs/api/multipart.rst new file mode 100644 index 0000000..ebbd76c --- /dev/null +++ b/docs/api/multipart.rst @@ -0,0 +1,7 @@ +Multipart +========= + +.. automodule:: vibora.multipart.parser + +.. automodule:: vibora.multipart.containers + diff --git a/docs/api/optimizer.rst b/docs/api/optimizer.rst new file mode 100644 index 0000000..d0185f1 --- /dev/null +++ b/docs/api/optimizer.rst @@ -0,0 +1,5 @@ +Optimizer +========= + +.. automodule:: vibora.optimizer + diff --git a/docs/api/parsers.rst b/docs/api/parsers.rst new file mode 100644 index 0000000..ad88214 --- /dev/null +++ b/docs/api/parsers.rst @@ -0,0 +1,10 @@ +Parsers +======= + +.. automodule:: vibora.parsers.parser + +.. automodule:: vibora.parsers.response + +.. automodule:: vibora.parsers.errors + +.. automodule:: vibora.parsers.typing diff --git a/docs/api/protocol.rst b/docs/api/protocol.rst new file mode 100644 index 0000000..e22f32c --- /dev/null +++ b/docs/api/protocol.rst @@ -0,0 +1,11 @@ +Protocol +======== + +.. automodule:: vibora.protocol + +.. automodule:: vibora.protocol.definitions + +.. automodule:: vibora.protocol.cprotocol + +.. automodule:: vibora.protocol.cwebsocket + diff --git a/docs/api/request.rst b/docs/api/request.rst new file mode 100644 index 0000000..c505933 --- /dev/null +++ b/docs/api/request.rst @@ -0,0 +1,5 @@ +Request +======= + +.. automodule:: vibora.request + diff --git a/docs/api/responses.rst b/docs/api/responses.rst new file mode 100644 index 0000000..7eca4c3 --- /dev/null +++ b/docs/api/responses.rst @@ -0,0 +1,5 @@ +Responses +========= + +.. automodule:: vibora.responses + diff --git a/docs/api/router.rst b/docs/api/router.rst new file mode 100644 index 0000000..0073f37 --- /dev/null +++ b/docs/api/router.rst @@ -0,0 +1,6 @@ +Router +====== + +.. automodule:: vibora.router.router + +.. automodule:: vibora.router.parser diff --git a/docs/api/schemas.rst b/docs/api/schemas.rst new file mode 100644 index 0000000..8d9112a --- /dev/null +++ b/docs/api/schemas.rst @@ -0,0 +1,20 @@ +Schemas +======= + +.. automodule:: vibora.schemas + +.. automodule:: vibora.schemas.extensions.fields + +.. automodule:: vibora.schemas.extensions.schemas + +.. automodule:: vibora.schemas.extensions.validator + +.. automodule:: vibora.schemas.exceptions + +.. automodule:: vibora.schemas.messages + +.. automodule:: vibora.schemas.schemas + +.. automodule:: vibora.schemas.types + +.. automodule:: vibora.schemas.validators diff --git a/docs/api/server.rst b/docs/api/server.rst new file mode 100644 index 0000000..0cfe180 --- /dev/null +++ b/docs/api/server.rst @@ -0,0 +1,5 @@ +Server +====== + +.. automodule:: vibora.server + diff --git a/docs/api/sessions.rst b/docs/api/sessions.rst new file mode 100644 index 0000000..8567466 --- /dev/null +++ b/docs/api/sessions.rst @@ -0,0 +1,8 @@ +Sessions +======== + +.. automodule:: vibora.sessions.base + +.. automodule:: vibora.sessions.client + +.. automodule:: vibora.sessions.files diff --git a/docs/api/static.rst b/docs/api/static.rst new file mode 100644 index 0000000..da8cac5 --- /dev/null +++ b/docs/api/static.rst @@ -0,0 +1,5 @@ +Static +====== + +.. automodule:: vibora.static + diff --git a/docs/api/templates.rst b/docs/api/templates.rst new file mode 100644 index 0000000..f8464ce --- /dev/null +++ b/docs/api/templates.rst @@ -0,0 +1,17 @@ +Templates +========= + +.. automodule:: vibora.templates.template +.. automodule:: vibora.templates.engine +.. automodule:: vibora.templates.ast +.. automodule:: vibora.templates.cache +.. automodule:: vibora.templates.compilers.base +.. automodule:: vibora.templates.compilers.cython +.. automodule:: vibora.templates.compilers.helpers +.. automodule:: vibora.templates.compilers.python +.. automodule:: vibora.templates.exceptions +.. automodule:: vibora.templates.extensions +.. automodule:: vibora.templates.loader +.. automodule:: vibora.templates.nodes +.. automodule:: vibora.templates.parser +.. automodule:: vibora.templates.utils diff --git a/docs/api/utils.rst b/docs/api/utils.rst new file mode 100644 index 0000000..da3317e --- /dev/null +++ b/docs/api/utils.rst @@ -0,0 +1,5 @@ +Utils +===== + +.. automodule:: vibora.utils + diff --git a/docs/api/websockets.rst b/docs/api/websockets.rst new file mode 100644 index 0000000..dc4e4c9 --- /dev/null +++ b/docs/api/websockets.rst @@ -0,0 +1,5 @@ +Websockets +========== + +.. automodule:: vibora.websockets.obj + diff --git a/docs/api/workers.rst b/docs/api/workers.rst new file mode 100644 index 0000000..d8ed05c --- /dev/null +++ b/docs/api/workers.rst @@ -0,0 +1,8 @@ +Workers +======= + +.. automodule:: vibora.workers.handler + +.. automodule:: vibora.workers.necromancer + +.. automodule:: vibora.workers.reaper diff --git a/docs/client/examples.md b/docs/client/examples.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/client/initial.md b/docs/client/initial.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/client/session.md b/docs/client/session.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/client/websocket.md b/docs/client/websocket.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/components/initial.md b/docs/components/initial.md deleted file mode 100644 index 4e65a84..0000000 --- a/docs/components/initial.md +++ /dev/null @@ -1,87 +0,0 @@ -### Components - -Every app has some hot objects that should be available almost -everywhere. Maybe they are database instances, maybe request objects. -Vibora call these objects `components` - -For now you should pay close attention to the `Request` component: - -This is the most important component and will be everywhere in your app. -It holds all information related to the current request and -also some useful references like the current application and route. - -You can ask for components in your route by using type hints: - -```py -from vibora import Vibora, Request, Response - -app = Vibora() - -@app.route('/') -async def home(request: Request): - print(request.headers) - return Response(b'123') -``` - -The request object has a special method that allows you to ask -for more components as you go. - -```py -from vibora import Vibora, Request, Response -from vibora import Route - -app = Vibora() - -@app.route('/') -async def home(request: Request): - current_route = request.get_component(Route) - return Response(current_route.pattern.encode()) -``` - -> By now you should have noticed that Vibora is smart enough -to know which components do you want in your routes so your routes -may not receive any parameters at all or ask as many components -do you wish. - - -### Adding custom components - -Vibora was designed to avoid global magic (unlike Flask for example) -because it makes testing harder -and more prone to errors specially in async environments. - -To help with this, -Vibora provides an API where you can register objects to later use. - -This means they are correctly encapsulated into a single app object, -allowing many apps instances to work concurrently, -encouraging the use of type hints which brings many benefits -in the long-term and also make your routes much easier to test. - -```py -from vibora import Vibora, Request, Response -from vibora import Route - -# Config will be a new component. -class Config: - def __init__(self): - self.name = 'Vibora Component' - -app = Vibora() - -# Registering the config instance. -app.add_component(Config()) - -@app.route('/') -async def home(request: Request, config: Config): - """ - Notice that if you specify a parameter of type "Config" - Vibora will automatically provide the config instance registered previously. - Instead of adding global variables you can now register new components, - that are easily testable and accessible. - """ - # You could also ask for the Config component at runtime. - current_config = request.get_component(Config) - assert current_config is config - return Response(config.name) -``` diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..9e33778 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# 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('..')) + + +# -- Project information ----------------------------------------------------- + +project = 'Vibora' +copyright = '2018, Frank Vieira' +author = 'Frank Vieira' + +# The short X.Y version +# The full version, including alpha/beta/rc tags +release = '0.0.6' +version = release.rsplit(".", 1)[1] + + +# -- 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 = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', + 'sphinx.ext.napoleon', + 'sphinx_autodoc_typehints', +] + +# 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'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# 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 pattern also affects html_static_path and html_extra_path . +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + + +# -- 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 = 'alabaster' + +# 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 = {} + +# 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'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Viboradoc' + + +# -- 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, 'Vibora.tex', 'Vibora Documentation', + 'Frank Vieira', '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, 'vibora', 'Vibora 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, 'Vibora', 'Vibora Documentation', + author, 'Vibora', 'One line description of project.', + 'Miscellaneous'), +] + + +# -- Extension configuration ------------------------------------------------- + +#1.7.8 and lower uses lists save the config +autodoc_default_flags = ['members', 'undoc-members', 'special-members'] + +#1.8 and up replace default_flags and member_order with default_options with additional settings +# None is equivalent with everything +autodoc_default_options = { + #default flags + 'members': None, + 'undoc-members': None, + 'special-members': '__init__', +} diff --git a/docs/configs.md b/docs/configs.md deleted file mode 100644 index 13f88d5..0000000 --- a/docs/configs.md +++ /dev/null @@ -1,109 +0,0 @@ -### Configuration - -Configuration handling in Vibora is simple thanks to components. - -In your init script (usually called run.py) you can load environment -variables, config files or whatever and register a -config class as a new component and that's all. - -This method is a little bit harder for beginners when compared to the -Django approach but it's way more flexible and allows you to build -whatever suits you better. - -Here goes a practical example: - -1) Create a file called config.py -```py -import aioredis - - -class Config: - def __init__(self, config: dict): - self.port = config['port'] - self.host = config['host'] - self.redis_host = config['redis']['host'] -``` - -2) Create a file called api.py -```py -from vibora import Vibora -from vibora.blueprints import Blueprint -from vibora.hooks import Events -from aioredis import ConnectionsPool -from config import Config - -api = Blueprint() - - -@api.route('/') -async def home(pool: ConnectionsPool): - await pool.set('my_key', 'any_value') - value = await pool.get('my_key') - return Response(value.encode()) - - -@api.handle(Events.BEFORE_SERVER_START) -async def initialize_db(app: Vibora, config: Config): - - # Creating a pool of connection to Redis. - pool = await aioredis.create_pool(config.redis_host) - - # In this case we are registering the pool as a new component - # but if you find yourself using too many components - # feel free to wrap them all inside a single component - # so you don't need to repeat yourself in every route. - app.components.add(pool) -``` - -3) Now create a file called config.json -```js -{ - "host": "0.0.0.0", - "port": 8000, - "redis_host": "127.0.0.1" -} -``` - -4) Now create a file called run.py - -```py -import json -from vibora import Vibora -from api import api -from config import Config - - -if __name__ == "__main__": - # Creating a new app - app = Vibora() - - # Registering our API - app.add_blueprint(api, prefixes={'v1': '/v1'}) - - # Opening the configuration file. - with open('config.json') as f: - - # Parsing the JSON configs. - config = Config(json.load(f)) - - # Registering the config as a component so you can use it - # later on (as we do in the "before_server_start" hook) - app.components.add(config) - - # Running the server. - app.run(host=config.host, port=config.port) -``` - -The previous example loads your configuration from JSON files, but -other approaches, such as environment variables, can be used. - -Notice that we register the config instance as a component because -databases drivers, for example, often need to be instantiated -after the server is forked so you'll need the config after -the "run script". - -Also, our config class in this example is a mere wrapper for our JSON -config but in a real app, you could be using the config class as -a components wrapper. You'll just need to add references to many -important components so you don't need to repeat yourself by -importing many different components in every route. diff --git a/docs/docs.rst b/docs/docs.rst new file mode 100644 index 0000000..2b12ded --- /dev/null +++ b/docs/docs.rst @@ -0,0 +1,22 @@ +Docs +==== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + docs/started + docs/routing + docs/components/initial + docs/responses + docs/schemas/initial + docs/events + docs/testing/started + docs/templates/engine + docs/logging + docs/configs + docs/deploy + docs/client/initial + docs/extensions + docs/contributing + docs/faq diff --git a/docs/docs/client/examples.rst b/docs/docs/client/examples.rst new file mode 100644 index 0000000..dd6258e --- /dev/null +++ b/docs/docs/client/examples.rst @@ -0,0 +1,2 @@ +Useful Examples +=============== diff --git a/docs/docs/client/initial.rst b/docs/docs/client/initial.rst new file mode 100644 index 0000000..214da4e --- /dev/null +++ b/docs/docs/client/initial.rst @@ -0,0 +1,10 @@ +HTTP Client +=========== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + session + examples + websocket diff --git a/docs/docs/client/session.rst b/docs/docs/client/session.rst new file mode 100644 index 0000000..aea192e --- /dev/null +++ b/docs/docs/client/session.rst @@ -0,0 +1,2 @@ +Session +======= diff --git a/docs/docs/client/websocket.rst b/docs/docs/client/websocket.rst new file mode 100644 index 0000000..8b3d195 --- /dev/null +++ b/docs/docs/client/websocket.rst @@ -0,0 +1,2 @@ +Websocket +========= diff --git a/docs/docs/components/initial.rst b/docs/docs/components/initial.rst new file mode 100644 index 0000000..c555125 --- /dev/null +++ b/docs/docs/components/initial.rst @@ -0,0 +1,89 @@ +Components +========== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + request + +Every app has some hot objects that should be available almost +everywhere. Maybe they are database instances, maybe request objects. +Vibora call these objects `components` + +For now you should pay close attention to the `Request` component: + +This is the most important component and will be everywhere in your app. +It holds all information related to the current request and +also some useful references like the current application and route. + +You can ask for components in your route by using type hints::: + + from vibora import Vibora, Request, Response + + app = Vibora() + + @app.route('/') + async def home(request: Request): + print(request.headers) + return Response(b'123') + +The request object has a special method that allows you to ask +for more components as you go.:: + + from vibora import Vibora, Request, Response + from vibora import Route + + app = Vibora() + + @app.route('/') + async def home(request: Request): + current_route = request.get_component(Route) + return Response(current_route.pattern.encode()) + +> By now you should have noticed that Vibora is smart enough +to know which components do you want in your routes so your routes +may not receive any parameters at all or ask as many components +do you wish. + + +Adding custom components +------------------------ + +Vibora was designed to avoid global magic (unlike Flask for example) +because it makes testing harder +and more prone to errors specially in async environments. + +To help with this, +Vibora provides an API where you can register objects to later use. + +This means they are correctly encapsulated into a single app object, +allowing many apps instances to work concurrently, +encouraging the use of type hints which brings many benefits +in the long-term and also make your routes much easier to test.:: + + from vibora import Vibora, Request, Response + from vibora import Route + + # Config will be a new component. + class Config: + def __init__(self): + self.name = 'Vibora Component' + + app = Vibora() + + # Registering the config instance. + app.add_component(Config()) + + @app.route('/') + async def home(request: Request, config: Config): + """ + Notice that if you specify a parameter of type "Config" + Vibora will automatically provide the config instance registered previously. + Instead of adding global variables you can now register new components, + that are easily testable and accessible. + """ + # You could also ask for the Config component at runtime. + current_config = request.get_component(Config) + assert current_config is config + return Response(config.name) diff --git a/docs/components/request.md b/docs/docs/components/request.rst similarity index 50% rename from docs/components/request.md rename to docs/docs/components/request.rst index 9940b80..fd43b76 100644 --- a/docs/components/request.md +++ b/docs/docs/components/request.rst @@ -1,32 +1,34 @@ -### Request Component +Request Component +================= The request component holds all the information related to the current request. Json, Forms, Files everything can be accessed through it. -### Receiving JSON +Receiving JSON +-------------- +:: -```py -from vibora import Vibora, Request -from vibora.responses import JsonResponse + from vibora import Vibora, Request + from vibora.responses import JsonResponse -app = Vibora() + app = Vibora() -@app.route('/') -async def home(request: Request): - values = await request.json() - print(values) - return JsonResponse(values) + @app.route('/') + async def home(request: Request): + values = await request.json() + print(values) + return JsonResponse(values) -app.run() -``` + app.run() Note that `request.json()` is actually a coroutine that needs to be **awaited**, this design prevents the entire JSON being uploaded in-memory before the route requires it. -### Uploaded Files +Uploaded Files +-------------- Uploaded files by multipart forms can be accessed by field name in `request.form` or through the @@ -46,57 +48,56 @@ Instead of pre-parsing the entire form you could call `request.stream_form()` and deal with every uploaded field as it arrives by the network. This is good when you don't want files hitting the disk and in some scenarios allows you to waste less memory -by doing way more coding yourself. +by doing way more coding yourself.:: -```py -import uuid -from vibora import Vibora, Request -from vibora.responses import JsonResponse + import uuid + from vibora import Vibora, Request + from vibora.responses import JsonResponse -app = Vibora() + app = Vibora() -@app.route('/', methods=['POST']) -async def home(request: Request): - uploaded_files = [] - for file in (await request.files): - file.save('/tmp/' + str(uuid.uuid4())) - print(f'Received uploaded file: {file.filename}') - uploaded_files.append(file.filename) - return JsonResponse(uploaded_files) -``` + @app.route('/', methods=['POST']) + async def home(request: Request): + uploaded_files = [] + for file in (await request.files): + file.save('/tmp/' + str(uuid.uuid4())) + print(f'Received uploaded file: {file.filename}') + uploaded_files.append(file.filename) + return JsonResponse(uploaded_files) -### Querystring +Querystring +----------- +:: -```py -from vibora import Vibora, Response, Request + from vibora import Vibora, Response, Request -app = Vibora() + app = Vibora() + + @app.route('/') + async def home(request: Request): + print(request.args) + return Response(f'Name: {request.args['name']}'.encode()) -@app.route('/') -async def home(request: Request): - print(request.args) - return Response(f'Name: {request.args['name']}'.encode()) -``` > A request to http://{address}/?name=vibora would return 'Name: vibora' -### Raw Stream +Raw Stream +---------- Sometimes you need a low-level access to the HTTP request body, `request.stream` method provides an easy way to consume the -stream by ourself. +stream by ourself.:: -```py -from vibora import Vibora, Request, Response + from vibora import Vibora, Request, Response -app = Vibora() + app = Vibora() -@app.route('/', methods=['POST']) -async def home(request: Request): - content = await request.stream.read() - return Response(content) -``` + @app.route('/', methods=['POST']) + async def home(request: Request): + content = await request.stream.read() + return Response(content) -### URLs +URLs +---- Ideally you shouldn't need to deal with the URL directly but sometimes that's the only way. The request object carries two properties @@ -107,17 +108,15 @@ that can help you: `request.parsed_url`: A parsed URL where you can access the path, host and all URL attributes easily. The URL is parsed by a -fast Cython parser so there is no need to you re-invent the wheel. +fast Cython parser so there is no need to you re-invent the wheel.:: -```py -from vibora import Vibora, Request -from vibora.responses import JsonResponse + from vibora import Vibora, Request + from vibora.responses import JsonResponse -app = Vibora() + app = Vibora() -@app.route('/') -async def home(request: Request): - return JsonResponse( - {'url': request.url, 'parsed_url': request.parsed_url} - ) -``` + @app.route('/') + async def home(request: Request): + return JsonResponse( + {'url': request.url, 'parsed_url': request.parsed_url} + ) diff --git a/docs/docs/configs.rst b/docs/docs/configs.rst new file mode 100644 index 0000000..914c505 --- /dev/null +++ b/docs/docs/configs.rst @@ -0,0 +1,103 @@ +Configuration +============= + +Configuration handling in Vibora is simple thanks to components. + +In your init script (usually called run.py) you can load environment +variables, config files or whatever and register a +config class as a new component and that's all. + +This method is a little bit harder for beginners when compared to the +Django approach but it's way more flexible and allows you to build +whatever suits you better. + +Here goes a practical example: + +1) Create a file called config.py:: + + import aioredis + + class Config: + def __init__(self, config: dict): + self.port = config['port'] + self.host = config['host'] + self.redis_host = config['redis']['host'] + +2) Create a file called api.py:: + + from vibora import Vibora + from vibora.blueprints import Blueprint + from vibora.hooks import Events + from aioredis import ConnectionsPool + from config import Config + + api = Blueprint() + + + @api.route('/') + async def home(pool: ConnectionsPool): + await pool.set('my_key', 'any_value') + value = await pool.get('my_key') + return Response(value.encode()) + + + @api.handle(Events.BEFORE_SERVER_START) + async def initialize_db(app: Vibora, config: Config): + # Creating a pool of connection to Redis. + pool = await aioredis.create_pool(config.redis_host) + + # In this case we are registering the pool as a new component + # but if you find yourself using too many components + # feel free to wrap them all inside a single component + # so you don't need to repeat yourself in every route. + app.components.add(pool) + +3) Now create a file called config.json:: + + { + "host": "0.0.0.0", + "port": 8000, + "redis_host": "127.0.0.1" + } + +4) Now create a file called run.py:: + + import json + from vibora import Vibora + from api import api + from config import Config + + + if __name__ == "__main__": + # Creating a new app + app = Vibora() + + # Registering our API + app.add_blueprint(api, prefixes={'v1': '/v1'}) + + # Opening the configuration file. + with open('config.json') as f: + + # Parsing the JSON configs. + config = Config(json.load(f)) + + # Registering the config as a component so you can use it + # later on (as we do in the "before_server_start" hook) + app.components.add(config) + + # Running the server. + app.run(host=config.host, port=config.port) + +The previous example loads your configuration from JSON files, but +other approaches, such as environment variables, can be used. + +Notice that we register the config instance as a component because +databases drivers, for example, often need to be instantiated +after the server is forked so you'll need the config after +the "run script". + +Also, our config class in this example is a mere wrapper for our JSON +config but in a real app, you could be using the config class as +a components wrapper. You'll just need to add references to many +important components so you don't need to repeat yourself by +importing many different components in every route. diff --git a/docs/contributing.md b/docs/docs/contributing.rst similarity index 88% rename from docs/contributing.md rename to docs/docs/contributing.rst index 16af9a4..1f66138 100644 --- a/docs/contributing.md +++ b/docs/docs/contributing.rst @@ -1,4 +1,5 @@ -### Contributing +Contributing +============ Vibora is developed on GitHub and pull requests are welcome but there are few guidelines: @@ -15,7 +16,8 @@ to be merged. 4) PEP 8 must be followed with the exception of the max line size which is currently 120 instead of 80 chars wide. -### Reporting an issue +Reporting an issue +------------------ 1) Describe what you expected to happen and what actually happens. @@ -26,7 +28,8 @@ to help us reproduce the issue. Vibora is open source and you can probably submit a pull request to fix it even faster. -### First time setup +First time setup +---------------- 1) Clone Vibora repository. @@ -35,4 +38,4 @@ requirements.txt 3) Run build.py (Vibora has a lot of cython extensions and this file helps to build them so you can test your code without the need to -install or compile libraries manually. \ No newline at end of file +install or compile libraries manually. diff --git a/docs/deploy.md b/docs/docs/deploy.rst similarity index 97% rename from docs/deploy.md rename to docs/docs/deploy.rst index 4a1f0d9..c77af0e 100644 --- a/docs/deploy.md +++ b/docs/docs/deploy.rst @@ -1,4 +1,5 @@ -### Deployment +Deployment +========== Vibora is not a WSGI compatible framework because of its async nature. Its own http server is built to battle so deployment is far easier diff --git a/docs/events.md b/docs/docs/events.rst similarity index 59% rename from docs/events.md rename to docs/docs/events.rst index 6c38b06..67d99c8 100644 --- a/docs/events.md +++ b/docs/docs/events.rst @@ -1,4 +1,8 @@ -### Hooks (Listeners) +Events +====== + +Hooks (Listeners) +----------------- Hooks are functions that are called after an event. @@ -6,25 +10,23 @@ Let's suppose you want to add a header to every response in your app. Instead of manually editing every single route in your app you can just register a listener to the event "BeforeResponse" and inject the desired headers. -Below is a fully working example: +Below is a fully working example::: -```py -from vibora import Vibora, Response -from vibora.hooks import Events + from vibora import Vibora, Response + from vibora.hooks import Events -app = Vibora() + app = Vibora() -@app.route('/') -async def home(): - return Response(b'Hello World') + @app.route('/') + async def home(): + return Response(b'Hello World') -@app.handle(Events.BEFORE_RESPONSE) -async def before_response(response: Response): - response.headers['x-my-custom-header'] = 'Hello :)' + @app.handle(Events.BEFORE_RESPONSE) + async def before_response(response: Response): + response.headers['x-my-custom-header'] = 'Hello :)' -if __name__ == '__main__': - app.run() -``` + if __name__ == '__main__': + app.run() Hooks can halt a request and prevent a route from being called, completely modify the response, handle app start/stop functionalities, diff --git a/docs/docs/extensions.rst b/docs/docs/extensions.rst new file mode 100644 index 0000000..8f4100c --- /dev/null +++ b/docs/docs/extensions.rst @@ -0,0 +1,4 @@ +Extensions +========== + +Under construction diff --git a/docs/faq.md b/docs/docs/faq.rst similarity index 76% rename from docs/faq.md rename to docs/docs/faq.rst index 3873beb..e89b824 100644 --- a/docs/faq.md +++ b/docs/docs/faq.rst @@ -1,6 +1,8 @@ -### Frequently Asked Questions +Frequently Asked Questions +========================== -#### Why Vibora ? +Why Vibora ? +------------ - I needed a framework like Flask but async by design. @@ -10,7 +12,7 @@ interfaces and I think many of them could be user-friendlier. - I was unaware of Quart and I have mixed feelings about - being __compatible__ with Flask. + being *compatible* with Flask. - Japronto is currently a proof of concept, a very impressive one. @@ -28,21 +30,24 @@ - And finally because history always repeats itself and here we are, again, with another framework. -#### Where the performance comes from ? +Where the performance comes from ? +---------------------------------- - Cython. Critical framework pieces are written Cython so it can leverage "C speed" in critical stuff. - Common tasks as schema validation, template rendering and other stuff were made builtin in the framework, written from scratch with performance in mind. -#### Is it compatible with PyPy ? +Is it compatible with PyPy ? +---------------------------- - No. PyPy's poor C extensions compatibility (performance-wise) is it's biggest problem. Vibora would need to drop its C extensions or have duplicate implementations (Cython powered X pure Python). In the end I would bet that Vibora on PyPy would still be slower than the Cython-powered version. I'm open to suggestions and I'm watching PyPy closely so who knows. -#### Why not use Jinja2 ? +Why not use Jinja2 ? +-------------------- - Jinja2 was not built with async in mind. @@ -52,7 +57,8 @@ - And of course: because it looked like an exciting challenge. -#### Where is Japronto on benchmarks ? +Where is Japronto on benchmarks ? +--------------------------------- - Vibora was almost twice as fast before network flow control was a concern, what that means is that it is very easy to write a fast server but not so easy to build a stable one. @@ -72,19 +78,22 @@ willing to replace Cython with baby cared C code. Still I'm willing to replace Cython with Rust extensions if they get stable enough. -#### Why don't you export the template engine into a new project ? +Why don't you export the template engine into a new project ? +------------------------------------------------------------- - If people show interest, why not. -#### What about Trio ? +What about Trio ? +----------------- - Trio has some interesting concepts and although it's better - than asyncio in overall I'm not sure about it. The python async community - is still young and splitting it is not good. We already have a bunch - of libraries and uvloop so it's hard to move now. I would like to see - some of it's concepts implemented on top of asyncio but that needs some - serious creativity because of asyncio design. - -#### Can we make Vibora faster ? + than asyncio in overall I'm not sure about it. The python async community + is still young and splitting it is not good. We already have a bunch + of libraries and uvloop so it's hard to move now. I would like to see + some of it's concepts implemented on top of asyncio but that needs some + serious creativity because of asyncio design. + +Can we make Vibora faster ? +--------------------------- - Sure. I have a bunch of ideas but I'm a one man army. Are you willing to help me ? :) diff --git a/docs/docs/logging.rst b/docs/docs/logging.rst new file mode 100644 index 0000000..2e68485 --- /dev/null +++ b/docs/docs/logging.rst @@ -0,0 +1,27 @@ +Logging +======= + +Vibora has a simple logging mechanism to avoid locking you into our library of choice. + +You must provide a function that receives two parameters: a msg and a logging level (that matches logging standard library for usability sake). + +That's all. + +It's up to you to choose what to do with logging messages.:: + + import logging + from vibora import Vibora, Response + + app = Vibora() + + @app.route('/') + def home(): + return Response(b'Hello World') + + if __name__ == '__main__': + def log_handler(msg, level): + # Redirecting the msg and level to logging library. + getattr(logging, level)(msg) + print(f'Msg: {msg} / Level: {level}') + + app.run(logging=log_handler) diff --git a/docs/responses.md b/docs/docs/responses.rst similarity index 66% rename from docs/responses.md rename to docs/docs/responses.rst index 520c4b1..e85805a 100644 --- a/docs/responses.md +++ b/docs/docs/responses.rst @@ -1,4 +1,5 @@ -### Responses +Responses +========= Each route must return a Response object, the protocol will use these objects to encode the HTTP response and @@ -9,21 +10,21 @@ the base Response class. Bellow there are the most important ones: -### JSON Response +JSON Response +------------- -Automatically dumps Python objects and adds the correct headers to match the JSON format. +Automatically dumps Python objects and adds the correct headers to match the JSON format.:: -```py -from vibora import Vibora, JsonResponse + from vibora import Vibora, JsonResponse -app = Vibora() + app = Vibora() -@app.route('/') -async def home(): - return JsonResponse({'hello': 'world'}) -``` + @app.route('/') + async def home(): + return JsonResponse({'hello': 'world'}) -### Streaming Response +Streaming Response +------------------ Whenever you don't have the response already completely ready, be it because you don't want to waste memory by buffering, @@ -46,37 +47,34 @@ when chunk_timeout is properly configured this is a reasonable choice. > **chunk_timeout: int**: How many seconds the client have to consume each response chunk. Lets say your function produces 30 bytes per yield and the chunk_timeout is 10 seconds. -The client will have 10 seconds to consume the 30 bytes, in case not, the connection will be closed abruptly to avoid DOS attacks. +The client will have 10 seconds to consume the 30 bytes, in case not, the connection will be closed abruptly to avoid DOS attacks.:: -```py -import asyncio -from vibora import Vibora, StreamingResponse + import asyncio + from vibora import Vibora, StreamingResponse -app = Vibora() + app = Vibora() -@app.route('/') -async def home(): - async def stream_builder(): - for x in range(0, 5): - yield str(x).encode() - await asyncio.sleep(1) + @app.route('/') + async def home(): + async def stream_builder(): + for x in range(0, 5): + yield str(x).encode() + await asyncio.sleep(1) - return StreamingResponse( - stream_builder, chunk_timeout=10, complete_timeout=30 - ) -``` + return StreamingResponse( + stream_builder, chunk_timeout=10, complete_timeout=30 + ) -### Response +Response +-------- A raw Response object would fit whenever you need a more -customized response. +customized response.:: -```py -from vibora import Vibora, Response + from vibora import Vibora, Response -app = Vibora() + app = Vibora() -@app.route('/') -async def home(): - return Response(b'Hello World', headers={'content-type': 'html'}) -``` + @app.route('/') + async def home(): + return Response(b'Hello World', headers={'content-type': 'html'}) diff --git a/docs/routing.md b/docs/docs/routing.rst similarity index 53% rename from docs/routing.md rename to docs/docs/routing.rst index 1ed5b93..76b6bb8 100644 --- a/docs/routing.md +++ b/docs/docs/routing.rst @@ -1,80 +1,74 @@ -### Routing +Routing +======= Routing is the core of any web framework because it allows the user -to map URL endpoints to functions. +to map URL endpoints to functions.:: -```py -@app.route("/home", methods=['GET']) -async def home(): - return Response(b'123') -``` + @app.route("/home", methods=['GET']) + async def home(): + return Response(b'123') > In this example you are mapping every HTTP request with a `GET` method and a path equals to `/home` to `async def home()`. -### Request Parameters +Request Parameters +------------------ Often parts of an URL have a special meaning, for example, -specifying which product should be displayed. +specifying which product should be displayed.:: -```py -@app.route('/product/') -async def show_product(product_id: int): - return Response(f'Chosen product: {product_id}'.encode()) -``` + @app.route('/product/') + async def show_product(product_id: int): + return Response(f'Chosen product: {product_id}'.encode()) Not usually you'll need something more sophisticated. -Vibora allows regular expressions as route patterns. +Vibora allows regular expressions as route patterns.:: -```py -import re + import re -@app.route(re.compile('/product/(?P[0-9]+)')) -async def show_product(product_id: int): - return Response(f'Chosen product: {product_id}'.encode()) -``` + @app.route(re.compile('/product/(?P[0-9]+)')) + async def show_product(product_id: int): + return Response(f'Chosen product: {product_id}'.encode()) -### Virtual Hosts +Virtual Hosts +------------- Maybe you have different domains and you want to host them all with a single Vibora application. So `http://docs.vibora.io/` and `http://vibora.io/` would hit the same application but return different responses based on the `HTTP host header`. Vibora makes it very easy -thanks to the `hosts` attribute. +thanks to the `hosts` attribute.:: -```py -@app.route('/', hosts=['docs.vibora.io']) -async def docs(): - return Response(b'Docs') + @app.route('/', hosts=['docs.vibora.io']) + async def docs(): + return Response(b'Docs') -@app.route('/', hosts=['vibora.io']) -async def home(): - return Response(b'Home') -``` + @app.route('/', hosts=['vibora.io']) + async def home(): + return Response(b'Home') To avoid repeating the `hosts` attribute for every route, -you can group routes using a Blueprint. +you can group routes using a Blueprint.:: -```py -from vibora.blueprints import Blueprint -from vibora.responses import Response + from vibora.blueprints import Blueprint + from vibora.responses import Response -docs = Blueprint(hosts=['docs.vibora.io']) -main = Blueprint(hosts=['vibora.io']) + docs = Blueprint(hosts=['docs.vibora.io']) + main = Blueprint(hosts=['vibora.io']) -@docs.route('/') -async def docs(): - return Response(b'docs') + @docs.route('/') + async def docs(): + return Response(b'docs') -@main.route('/') -async def home(): - return Response(b'main') -``` + @main.route('/') + async def home(): + return Response(b'main') -### Router Strategies +Router Strategies +----------------- A common source of headaches in URL routing are ending slashes. @@ -98,16 +92,15 @@ Vibora has three different strategies to deal with this problem: 3. **Clone**. This one is similar to redirect but instead of a 302 it'll return the same response for both routes. -Configuration example: +Configuration example::: -```py -from vibora import Vibora -from vibora.router import RouterStrategy + from vibora import Vibora + from vibora.router import RouterStrategy -app = Vibora(router_strategy=RouterStrategy.STRICT) -``` + app = Vibora(router_strategy=RouterStrategy.STRICT) -### Caching +Caching +------- Caching can be a tremendous ally when handling performance issues. Imagine an API that does a read-only query being hit by 10k requests/sec, @@ -118,33 +111,32 @@ you drop from 10k queries/sec to 1 query per second. That's a huge improvement with almost no effort. Vibora has some internal optimizations to speed-up cached APIs -so instead of handling it all by ourselves, you should use the `CacheEngine`. +so instead of handling it all by ourselves, you should use the `CacheEngine`.:: -```py -from vibora import Vibora, Response, Request -from vibora.cache import CacheEngine + from vibora import Vibora, Response, Request + from vibora.cache import CacheEngine -app = Vibora() + app = Vibora() -class YourCacheEngine(CacheEngine): - async def get(self, request: Request): - return self.cache.get(request.url) + class YourCacheEngine(CacheEngine): + async def get(self, request: Request): + return self.cache.get(request.url) - async def store(self, request: Request, response): - self.cache[request.url] = response + async def store(self, request: Request, response): + self.cache[request.url] = response -@app.route('/', cache=YourCacheEngine(skip_hooks=True)) -def home(): - return Response(b'Hello World') -``` + @app.route('/', cache=YourCacheEngine(skip_hooks=True)) + def home(): + return Response(b'Hello World') > Notice the "skip_hooks" parameter which makes cached responses to skip any listeners/hooks. Sometimes this is useful, often not, use wisely. -### Static Files +Static Files +------------ Vibora is fast enough to host static files and it tries hard to implement the same features as some battle proven solutions like Nginx. @@ -155,20 +147,18 @@ created Vibora app instance. You can configure the `StaticHandler` as bellow: -> All parameters are optional. +> All parameters are optional.:: -```py -from vibora.static import StaticHandler + from vibora.static import StaticHandler -app = Vibora( - static=StaticHandler( - paths=['/your_static_dir', '/second_static_dir'], - host='static.vibora.io', - url_prefix='/static', - max_cache_size=1 * 1024 * 1024 + app = Vibora( + static=StaticHandler( + paths=['/your_static_dir', '/second_static_dir'], + host='static.vibora.io', + url_prefix='/static', + max_cache_size=1 * 1024 * 1024 + ) ) -) -``` > **Host** parameter can be used to only serve static files when the Host header matches this specific host. diff --git a/docs/schemas/fields.md b/docs/docs/schemas/fields.rst similarity index 76% rename from docs/schemas/fields.md rename to docs/docs/schemas/fields.rst index 964eb16..ea9747e 100644 --- a/docs/schemas/fields.md +++ b/docs/docs/schemas/fields.rst @@ -1,4 +1,5 @@ -### Fields +Fields +====== Vibora has a special class called "Field" to represent each field of a schema. You can build any kind of validation rules using this class @@ -25,24 +26,23 @@ In case it receive two parameters the context of the schema will be also provide The exception `ValidationError` must be raised to notify the schema that this field is invalid, returning values are ignored. -### StringField +StringField +----------- -Validates if the given value is a valid string. +Validates if the given value is a valid string.:: -```py -import uuid -from vibora.schemas import Schema, fields -from vibora.schemas.validators import Length + import uuid + from vibora.schemas import Schema, fields + from vibora.schemas.validators import Length -class NewUserSchema(Schema): + class NewUserSchema(Schema): - name: str = fields.String( - required=False, - validators=[Length(min=3, max=30)], - default=lambda: str(uuid.uuid4()), - strict=False - ) -``` + name: str = fields.String( + required=False, + validators=[Length(min=3, max=30)], + default=lambda: str(uuid.uuid4()), + strict=False + ) > There is a special attribute called `strict` to allow this field to cast -integers and similar types to a string instead of raising an error. \ No newline at end of file +integers and similar types to a string instead of raising an error. diff --git a/docs/docs/schemas/initial.rst b/docs/docs/schemas/initial.rst new file mode 100644 index 0000000..02caa63 --- /dev/null +++ b/docs/docs/schemas/initial.rst @@ -0,0 +1,89 @@ +Data Validation +=============== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + fields + +Data validation is a common task in any web related activity. +Vibora has a module called `schemas` to build, guess what, +schemas, and validate your data against them. +They are very similar to `marshmallow` and +other famous libraries except they have some speedups written in Cython +for amazing performance. + +Schemas are also asynchronous meaning that you can do +database checkups and everything in a single place, +something that cannot be done in other libraries which forces you to +split your validation logic between different places. + +Usage Example +------------- + +Declaring your schema +^^^^^^^^^^^^^^^^^^^^^ +:: + + from vibora.schemas import Schema, fields + from vibora.schemas.exceptions import ValidationError + from vibora.schemas.validators import Length, Email + from vibora.context import get_component + from .database import Database + + + class AddUserSchema(Schema): + + @staticmethod + async def unique_email(email: str): + # You can get any existent component by using "vibora.context" + database = get_component(Database) + if await database.exists_user(email): + raise ValidationError( + 'There is already a registered user with this e-mail' + ) + + # Custom validations can be done by passing a list of functions + # to the validators keyword param. + email: str = fields.Email(pattern='.*@vibora.io', + validators=[unique_email] + ) + + # There are many builtin validation helpers as Length(). + password: str = fields.String(validators=[Length(min=6, max=20)]) + + # In case you just want to enforce the type of a given field, + # a type hint is enough. + name: str + +Using your schema +^^^^^^^^^^^^^^^^^ +:: + + from vibora import Request, Blueprint, JsonResponse + from .schemas import AddUserSchema + from .database import Database + + users_api = Blueprint() + + @users_api.route('/add') + async def add_user(request: Request, database: Database): + + # In case the schema is invalid an exception will be raised + # and catched by an exception handler, this means you don't need to + # repeat yourself about handling errors. But in case you want to + # customize the error message feel free to catch the exception + # and handle it your way. "from_request" method is just syntatic sugar + # to avoid calling request.json() yourself. + schema = await AddUserSchema.from_request(request) + + # By now our data is already valid and clean, + # so lets add our user to the database. + database.add_user(schema) + + return JsonResponse({'msg': 'User added successfully'}) + +> Type hints must always be provided for each field. In case the field is always +required and do not have any custom validation the type hint alone +will be enough to Vibora build your schema. diff --git a/docs/started.md b/docs/docs/started.rst similarity index 63% rename from docs/started.md rename to docs/docs/started.rst index aa879e1..a1459eb 100644 --- a/docs/started.md +++ b/docs/docs/started.rst @@ -1,4 +1,8 @@ -## Getting Started +Introduction +============ + +Getting Started +--------------- Make sure you are using `Python 3.6+` because Vibora takes advantage of some new Python features. @@ -10,22 +14,19 @@ advantage of some new Python features. > In case you have trouble with Vibora dependencies: `pip install vibora` to install it without the extra libraries. -2. Create a file called `anything.py` with the following code: - +2. Create a file called `anything.py` with the following code::: -```py -from vibora import Vibora, JsonResponse + from vibora import Vibora, JsonResponse -app = Vibora() + app = Vibora() -@app.route('/') -async def home(): - return JsonResponse({'hello': 'world'}) + @app.route('/') + async def home(): + return JsonResponse({'hello': 'world'}) -if __name__ == '__main__': - app.run(host="0.0.0.0", port=8000) -``` + if __name__ == '__main__': + app.run(host="0.0.0.0", port=8000) 3. Run the server: `python3 anything.py` @@ -33,7 +34,8 @@ if __name__ == '__main__': 4. Open your browser at `http://127.0.0.1:8000` -### Creating a project +Creating a project +------------------ The previous example was just to show off how easy is it to spin up a server. diff --git a/docs/templates/engine.md b/docs/docs/templates/engine.rst similarity index 86% rename from docs/templates/engine.md rename to docs/docs/templates/engine.rst index 8f28c30..f4137f1 100644 --- a/docs/templates/engine.md +++ b/docs/docs/templates/engine.rst @@ -1,4 +1,13 @@ -### Vibora Template Engine (VTE) +Vibora Template Engine (VTE) +============================ + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + syntax + extending + performance Although server-side rendering is not main-stream nowadays, Vibora has its own template engine. The idea was to build something like Jinja2 diff --git a/docs/docs/templates/extending.rst b/docs/docs/templates/extending.rst new file mode 100644 index 0000000..a9ec867 --- /dev/null +++ b/docs/docs/templates/extending.rst @@ -0,0 +1,4 @@ +Extending +========= + +WIP... diff --git a/docs/docs/templates/performance.rst b/docs/docs/templates/performance.rst new file mode 100644 index 0000000..8869dd0 --- /dev/null +++ b/docs/docs/templates/performance.rst @@ -0,0 +1,4 @@ +Performance +=========== + +WIP... diff --git a/docs/templates/syntax.md b/docs/docs/templates/syntax.rst similarity index 61% rename from docs/templates/syntax.md rename to docs/docs/templates/syntax.rst index 912df30..04e91fd 100644 --- a/docs/templates/syntax.md +++ b/docs/docs/templates/syntax.rst @@ -1,21 +1,22 @@ +Syntax +====== + ### Template Syntax -VTE syntax is basically split between two things: Tags and Expressions. +VTE syntax is basically split between two things: Tags and Expressions.:: -```html - - - {{ title }} - - -
    - {% for user in users %} -
  • {{ user.name}}
  • - {% endfor %} -
- - -``` + + + {{ title }} + + +
    + {% for user in users %} +
  • {{ user.name}}
  • + {% endfor %} +
+ + 1) Expressions are delimited by "{ { variable_name } }" and they are used to print data. diff --git a/docs/docs/testing/advanced.rst b/docs/docs/testing/advanced.rst new file mode 100644 index 0000000..3cdd019 --- /dev/null +++ b/docs/docs/testing/advanced.rst @@ -0,0 +1,4 @@ +Advanced Tips +============= + +Under construction diff --git a/docs/docs/testing/started.rst b/docs/docs/testing/started.rst new file mode 100644 index 0000000..ceff038 --- /dev/null +++ b/docs/docs/testing/started.rst @@ -0,0 +1,33 @@ +Testing +======= + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + advanced + +Testing is the most important part of any project with considerably +size and yet of one of the most ignored steps. + +Vibora has a builtin and fully featured async HTTP client and +a simple test framework to make it easier for you as in the example bellow::: + + from vibora import Vibora, Response + from vibora.tests import TestSuite + + app = Vibora() + + + @app.route('/') + async def home(): + return Response(b'Hello World') + + + class HomeTestCase(TestSuite): + def setUp(self): + self.client = app.test_client() + + async def test_home(self): + response = await self.client.get('/') + self.assertEqual(response.content, b'Hello World') diff --git a/docs/extensions.md b/docs/extensions.md deleted file mode 100644 index b9c4d8d..0000000 --- a/docs/extensions.md +++ /dev/null @@ -1 +0,0 @@ -Under construction \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..5ec2554 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,16 @@ +Welcome to Vibora's documentation! +================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + docs + api + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/logging.md b/docs/logging.md deleted file mode 100644 index 33d01ba..0000000 --- a/docs/logging.md +++ /dev/null @@ -1,29 +0,0 @@ -### Logging - -Vibora has a simple logging mechanism to avoid locking you into our library of choice. - -You must provide a function that receives two parameters: a msg and a logging level (that matches logging standard library for usability sake). - -That's all. - -It's up to you to choose what to do with logging messages. - - -```py -import logging -from vibora import Vibora, Response - -app = Vibora() - -@app.route('/') -def home(): - return Response(b'Hello World') - -if __name__ == '__main__': - def log_handler(msg, level): - # Redirecting the msg and level to logging library. - getattr(logging, level)(msg) - print(f'Msg: {msg} / Level: {level}') - - app.run(logging=log_handler) -``` diff --git a/docs/redirects/page.md b/docs/redirects/page.md deleted file mode 100644 index 577f1ce..0000000 --- a/docs/redirects/page.md +++ /dev/null @@ -1 +0,0 @@ -redirect \ No newline at end of file diff --git a/docs/schemas/initial.md b/docs/schemas/initial.md deleted file mode 100644 index 0075c6c..0000000 --- a/docs/schemas/initial.md +++ /dev/null @@ -1,80 +0,0 @@ -### Data Validation - -Data validation is a common task in any web related activity. -Vibora has a module called `schemas` to build, guess what, -schemas, and validate your data against them. -They are very similar to `marshmallow` and -other famous libraries except they have some speedups written in Cython -for amazing performance. - -Schemas are also asynchronous meaning that you can do -database checkups and everything in a single place, -something that cannot be done in other libraries which forces you to -split your validation logic between different places. - -### Usage Example - -###### Declaring your schema -```py -from vibora.schemas import Schema, fields -from vibora.schemas.exceptions import ValidationError -from vibora.schemas.validators import Length, Email -from vibora.context import get_component -from .database import Database - - -class AddUserSchema(Schema): - - @staticmethod - async def unique_email(email: str): - # You can get any existent component by using "vibora.context" - database = get_component(Database) - if await database.exists_user(email): - raise ValidationError( - 'There is already a registered user with this e-mail' - ) - - # Custom validations can be done by passing a list of functions - # to the validators keyword param. - email: str = fields.Email(pattern='.*@vibora.io', - validators=[unique_email] - ) - - # There are many builtin validation helpers as Length(). - password: str = fields.String(validators=[Length(min=6, max=20)]) - - # In case you just want to enforce the type of a given field, - # a type hint is enough. - name: str -``` - -###### Using your schema - -```py -from vibora import Request, Blueprint, JsonResponse -from .schemas import AddUserSchema -from .database import Database - -users_api = Blueprint() - -@users_api.route('/add') -async def add_user(request: Request, database: Database): - - # In case the schema is invalid an exception will be raised - # and catched by an exception handler, this means you don't need to - # repeat yourself about handling errors. But in case you want to - # customize the error message feel free to catch the exception - # and handle it your way. "from_request" method is just syntatic sugar - # to avoid calling request.json() yourself. - schema = await AddUserSchema.from_request(request) - - # By now our data is already valid and clean, - # so lets add our user to the database. - database.add_user(schema) - - return JsonResponse({'msg': 'User added successfully'}) -``` - -> Type hints must always be provided for each field. In case the field is always -required and do not have any custom validation the type hint alone -will be enough to Vibora build your schema. diff --git a/docs/summary.md b/docs/summary.md deleted file mode 100644 index 6f6a2b3..0000000 --- a/docs/summary.md +++ /dev/null @@ -1,23 +0,0 @@ -* [Introduction](started.md) -* [Routing](routing.md) -* [Components](components/initial.md) - * [Request Component](components/request.md) -* [Responses](responses.md) -* [Data Validation](schemas/initial.md) - * [Fields](schemas/fields.md) -* [Events](events.md) -* [Testing](testing/started.md) - * [Advanced Tips](testing/advanced.md) -* [Template Engine](templates/engine.md) - * [Syntax](templates/syntax.md) - * [Extending](templates/extending.md) - * [Performance](templates/performance.md) -* [Logging](logging.md) -* [Configuration](configs.md) -* [Deployment](deploy.md) -* [HTTP Client](client/initial.md) - * [Session](client/session.md) - * [Useful Examples](client/examples.md) -* [Extensions](extensions.md) -* [Contributing](contributing.md) -* [FAQ](faq.md) \ No newline at end of file diff --git a/docs/templates/extending.md b/docs/templates/extending.md deleted file mode 100644 index 1fc1779..0000000 --- a/docs/templates/extending.md +++ /dev/null @@ -1 +0,0 @@ -WIP... \ No newline at end of file diff --git a/docs/templates/performance.md b/docs/templates/performance.md deleted file mode 100644 index 1fc1779..0000000 --- a/docs/templates/performance.md +++ /dev/null @@ -1 +0,0 @@ -WIP... \ No newline at end of file diff --git a/docs/testing/advanced.md b/docs/testing/advanced.md deleted file mode 100644 index b9c4d8d..0000000 --- a/docs/testing/advanced.md +++ /dev/null @@ -1 +0,0 @@ -Under construction \ No newline at end of file diff --git a/docs/testing/started.md b/docs/testing/started.md deleted file mode 100644 index cea0539..0000000 --- a/docs/testing/started.md +++ /dev/null @@ -1,28 +0,0 @@ -### Testing - -Testing is the most important part of any project with considerably -size and yet of one of the most ignored steps. - -Vibora has a builtin and fully featured async HTTP client and -a simple test framework to make it easier for you as in the example bellow: - -```py -from vibora import Vibora, Response -from vibora.tests import TestSuite - -app = Vibora() - - -@app.route('/') -async def home(): - return Response(b'Hello World') - - -class HomeTestCase(TestSuite): - def setUp(self): - self.client = app.test_client() - - async def test_home(self): - response = await self.client.get('/') - self.assertEqual(response.content, b'Hello World') -``` diff --git a/setup.py b/setup.py index 825979c..e34f375 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ 'Programming Language :: Python :: 3.6' ], extras_require={ - 'dev': ['flake8', 'pytest', 'tox'], + 'dev': ['flake8', 'pytest', 'tox', 'Sphinx', 'sphinx-autodoc-typehints'], 'fast': ['ujson==1.35', 'uvloop==0.10.2'] }, ext_modules=[