Coder Social home page Coder Social logo

playpauseandstop / rororo Goto Github PK

View Code? Open in Web Editor NEW
105.0 6.0 9.0 1.91 MB

Implement aiohttp.web OpenAPI 3 server applications with schema first approach.

Home Page: https://rororo.readthedocs.io

License: BSD 3-Clause "New" or "Revised" License

Python 97.06% Makefile 2.66% Shell 0.28%
python python3 python-3 python-library asyncio aiohttp aiohttp-server python-3-7 openapi3 openapi

rororo's Introduction

Hm...

Not sure what to write here...

rororo's People

Contributors

andreypopp avatar badabump-release-bot[bot] avatar dependabot[bot] avatar fajfer avatar playpauseandstop avatar q0w avatar smirnoffs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

rororo's Issues

Better errors

As of 2.0.0a2 version rororo does not provide any specific error message on validation error, it fully relies on openapi-core and later on which error middleware used in aiohttp.web application (if any).

Need to change this and return the predefined error response if some of request parameters or body is not valid (and same for response after #13 is done).

TODO:

  • Security error
  • Request parameter validation error
  • Request body validation error
  • Response validation error
  • Multiple validation errors
  • Multiple validation errors in case of missing multiple required fields
  • Allow to localize high level error messages
  • Documentation for error handling & custom errors

Handle incorrect empty list as operation security

When OpenAPI operation schema do not define the security such as,

get:
  operationId: "me"
  summary: "Retrieve user profile"
  tags: ["auth"]
  ...

operation.security still equals to empty list [], not None, which result the fix from #115 result in incorrect handling of global defined security.

Need to find a way to fix this and ensure that global defined security work as expected for such cases.

Parent Issue: #114

Error & timeout middleware factories for aiohttp

It's common task to do error/timeout handling for aiohttp requests. But as I now right now there is no handy way to do it. Suggests to implement two middleware factories for aiohttp apps,

  • error_middleware_factory
  • timeout_middleware_factory

Where error middleware factory should be placed in middlewares settings after timeout middleware factory, so it handle timeout errors as well.

Better handle nullable values

Original report by @chobeat at #84 (comment),

I think I've found an unrelated bug. In unmarshallers.py:198 it tries to access the keys of a None. From what I understand, it's doing recursion on nested dicts but it tries on the None value and it fails. This has been introduced between 2.0.0b3 and 2.0.0rc2. Before it was working properly.

I think it's happening on all my responses that contain at least a None value somewhere.

Fix commitizen check in CI workflow

The idea to run commitizen check only for commits from playpauseandstop, but as of now workflow config has wrong conditional:

if: "github.actor = 'playpauseandstop'"

which result in skipped tasks.

Also commitizen job should be required by dev & test jobs.

ps. If possible it would be really nice to provide other updates to Python & poetry steps within the CI jobs.

Use environ-config for settings

Instead of customizing attrs and providing custom env_factory need to use environ-config library instead.

Which means,

  • rororo.BaseSettings should be wrapped into @environ.config decrorator
  • env_factory and all related code removed from the repo
  • Settings documentation updated

build: Do not include changelog in dist files

Right now rororo dist files (both .tar.gz & .whl) contains CHANGELOG.rst file, which after installed into site-packages root, which has no sense at all.

Need to fix current behavior and do not include changelog into the dist.

Upload fails with multipart/form-data

Long story short

Upload with multipart/form-data fails when schema validation is enabled. Exception "ValueError: Could not find starting boundary b'--somestuff' thrown when trying to access multipart info.

Steps to reproduce

curl --location --request POST 'http://localhost:8080/api/v1/'
--form 'someinfo="sdjkhflksjhdflkjhlkjh"'
--form 'file=@"./test.zip"'

Result: exception, no data in StreamReader

  • Run client without validation:

curl --location --request POST 'http://localhost:8080/api/v2/'
--form 'someinfo="sdjkhflksjhdflkjhlkjh"'
--form 'file=@"./test.zip"'

Result: file upload is ok

Environment

Python - 3.8.5
aiohttp - 3.7.3
rororo - 2.2.0

Implement Schemas

Schemas are Python interface to validate request/response data in API or normal views. Schemas are based on JSON Schemas and use jsonschema library for validation.

Below a small example of how Schemas should work inside views,

def view(request):
    schema = Schema(module_with_schemas)
    data = schema.validate_request(request.GET)
    ...
    return schema.make_response({...})

Better handling response data

When response is a tuple, but not a list, next exception raised,

  File "/Users/playpauseandstop/Projects/project/.venv/lib/python3.7/site-packages/rororo/schemas/schema.py", line 114, in make_response
    self._validate(data, response_schema)
  File "/Users/playpauseandstop/Projects/project/.venv/lib/python3.7/site-packages/rororo/schemas/schema.py", line 190, in _validate
    return self.validate_func(schema, self._pure_data(data))
  File "/Users/playpauseandstop/Projects/project/.venv/lib/python3.7/site-packages/rororo/schemas/schema.py", line 178, in _pure_data
    return dict(data)
ValueError: dictionary update sequence element #0 has length 3; 2 is required

This is not fully right as tuple can be validated by jsonschema similarly to list.

Put schema first note to docs

Need to make it clear, that rororo is an aiohttp.web OpenAPI 3 schema first library.

Also put schema first notes to README and docs.

Setup OpenAPI should setup CORS & error middlewares for aiohttp.web app as well

On setting up OpenAPI support for aiohttp.web application via setup_openapi function, need, by default, to enable cors_middleware & error_middleware for the application as well, cause of,

  • In most cases user need to set up CORS middleware to make Swagger UI or Frontend UI work with backend API
  • Error middleware should ignore base exc from #14 as it will contain all necessary JSON data to show

Examples

Need to provide examples of using schemas and other rororo gotchas. I think the most easiest way is porting swapi (or at least some its part) to aiohttp.web & rororo.schemas.

optimize setup_openapi

Problem: Using rororo with aiohttp, I end up reloading the openapi.yaml for every test and populating the app with setup_openapi. This takes up 30% of the total time of my test suite. 15% is just loading the file in read_schema, while another 12% is spent in create_spec.

Proposal: decouple the spec creation from the addition of fields and routes to the app. After this, create a separate setup_openapi (maybe called setup_test_openapi) that can take a Spec object as an additional parameter and set it instead of creating it. The underlying implementation will be the same, but the first half of the current setup_openapi will be optional

I can try to work on this if you don't see conflicts arising

Handle all exceptions on creating schema and spec

Need to ensure that any errors, not only validation errors, handled properly at create_schema_and_spec call. And that any errors there result in human friendly error message about problems with OpenAPI schema.

colorlog support

colorlog is a nifty way to have colored logs for debug environments. rororo.logging.default_logging_dict should be able to configure colored formatter for user and enable it by default for debug environments.

Registering operations from an aiohttp.web.View class

Hello there !

I was wondering if there are ways to register operations from an aiohttp.web.View class instead of doing it method by method?

For example I would like to do something like:

from aiohttp import web
from rororo import OperationTableDef

operations = OperationTableDef()

# @operations.register ?
class LogCollectionView(web.View):
    # @operations.register("log_collection_get") ?
    async def get(self):
        pass

    # @operations.register("log_collection_post") ?
    async def post(self):
        pass

Thank you in advance!

Using rororo 2.0.0b3

Investigate whether it is possible to validate error responses

Right now rororo is able to validate response if view handler has not raised an exception.

Need to investigate whether it is possible to validate error responses. Maybe it is possible after #22 by providing validate response middleware before error middleware, but after CORS middleware.

Guess route prefix from OAS servers schema

Right now, when OAS servers schema looks like:

servers:
  - url: "/api/"
    description: "Test environment"

  - url: "http://localhost:8080/api/"
    description: "Dev environment"

  - url: "https://staging.url/api/"
    description: "Staging environment"

  - url: "https://production.url/api/"
    description: "Production environment"

setup_openapi requires route_prefix="/api" to be passed or it will register OpenAPI operations to unexpected URLs.

Need to find a way to not ask user to pass route_prefix by default and find out if it needed to be used from servers configuration.

Use jsonschema-rs for request/response validation

jsonschema-rs in theory should allow rororo dramatically improve request/response validation speed.

As of 0.1.0 version error handling is not there, but it is still good idea to prepare basis for drop-in replacement for request/response validation via faster jsonschema alternative.

Pass security data into OpenAPI context as well

There is several predefined security schemes, supported by OpenAPI:

  • apiKey
  • http
  • oauth2
  • openIdConnect

Need to support all of given schemes and if the operation is require some of security scheme, parse request and pass parsed data into OpenAPIContext as security immutable dict.

Extending custom validations

Hello,

I cant seem to figure out how to extend Validations with custom validations.
Whenever I raise something, i (default?) error.

Example (for someplicity)

@operations.register
async def register_user(request: web.Request) -> web.Response:
    with openapi_context(request) as context:
        # something in context fails on more detailed validation
        raise ValidationError.from_request_errors([OpenAPIParameterError("Something is wrong"), ], base_loc=["fieldname"])

I end up with this error:
{"detail": [{"loc": ["response", 0], "message": "'detail' is not of type object"}]}

I assume that whenever i use with openapi_context(request) as context: i get into the reponse layer.
Sadly i cant figure out how to combine it with my extra validations

Support jsonschema==3.0.0

Got next error after installing jsonschema==3.0.0 in project with rororo,

  File "/Users/playpauseandstop/Projects/moneytracker/moneytracker/api/schemas/tracker_stories.py", line 10, in <module>
    from rororo.schemas.utils import defaults
  File "/Users/playpauseandstop/Projects/moneytracker/.venv/lib/python3.7/site-packages/rororo/schemas/__init__.py", line 11, in <module>
    from .schema import Schema
  File "/Users/playpauseandstop/Projects/moneytracker/.venv/lib/python3.7/site-packages/rororo/schemas/schema.py", line 18, in <module>
    from .validators import DefaultValidator
  File "/Users/playpauseandstop/Projects/moneytracker/.venv/lib/python3.7/site-packages/rororo/schemas/validators.py", line 18, in <module>
    class Validator(Draft4Validator):
  File "/Users/playpauseandstop/Projects/moneytracker/.venv/lib/python3.7/site-packages/rororo/schemas/validators.py", line 30, in Validator
    }, Draft4Validator.DEFAULT_TYPES)  # type: dict
  File "/Users/playpauseandstop/Projects/moneytracker/.venv/lib/python3.7/site-packages/jsonschema/validators.py", line 131, in _DEFAULT_TYPES
    raise _DontDoThat()
jsonschema.validators._DontDoThat: DEFAULT_TYPES cannot be used on Validators using TypeCheckers

Need to fix the error and ensure that rororo works well with jsonschema>=3.0

Fix release job

As it currently marks any release as pre-release, not only those, which have a, b, or rc in tag name.

Try to load OpenAPI schema content with faster yaml / json loader

As of 2.0.0rc0 version, read_openapi_schema is using,

  • json.loads
  • yaml.safe_load

for loading OpenAPI schema (due to its initial format). But in reality, both of given functions are pretty slow, especially for big schema files. Need to,

  • By default try to load faster yaml loader (CSafeLoader, not SafeLoader)
  • Allow user to pass loader for the schema (in case of using ujson, rapidjson or other JSON library)

This will allow increase startup time of web.Application as, for example, there is 10x difference between SafeLoader and CSafeLoader,

In [1]: import yaml                                                                                                                                                               

In [2]: from api.constants import PROJECT_PATH                                                                                                                                    

In [3]: schema_path = PROJECT_PATH / "openapi.yaml"                                                                                                                               

In [4]: %timeit yaml.load(schema_path.read_text(), Loader=yaml.SafeLoader)                                                                                                        
164 ms ± 10.1 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [5]: %timeit yaml.load(schema_path.read_text(), Loader=yaml.CSafeLoader)                                                                                                       
14.6 ms ± 339 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

This also will require to update the docs, by adding Speedups page to write down, how it is possible to speed up rororo.

Documentation

Need to provide brief documentation for rororo project, the structure of documentation may looks like,

  • Overview
  • Tutorial
  • API
    • rororo.logger
    • rororo.schemas
    • rororo.settings
  • ChangeLog

Provide a documentation about design choices & openapi-core limitiations

Some openapi-core issues, such as,

  • Incomplete support of free-form objects
  • Avoiding validation error data in OpenAPIMediaTypeError exceptions
  • Missing support of operation security

resulted in specific implementation details, which should be covered in documentation to provide better understanding why rororo need to fix given (and other) issues with openapi-core or other tools.

Fix expecting security data for unsecured operations

If security defined for all operations globally as,

security:
  - jwt: []

and some operation declares empty security list like,

paths:
  "/public":
    get:
      operationId: "retrieve_public_data"
      summary: "Retrieve public data"
      security: []
      ...

rororo still expected retrieve_public_data endpoint to receive data from jwt security scheme, which is wrong.

Need to fix and ensure that those operations do not require any secure data to work well.

OpenAPIRequest expects string, not bytes

As of 2.0.0rc1 on converting aiohttp web.Request instance to OpenAPIRequest instance body passed as bytes, not string,

    if request.body_exists and request.can_read_body:
        body = await request.read()

This results in issues of unmarshalling strings, when request body defined as,

      requestBody:
        content:
          text/x-python:
            schema:
              type: "string"

Need to fix it by supplying string as OpenAPIRequest.body, which means using await request.text() instead of await request.read()

Ensure tz aware date times works properly

As openapi-core using strict-rfc7739 under the hood for validating / unmarshaling date time strings, which likely do not support tz aware date time strings, need to ensure that passing tz aware date time strings will be converted into proper Python object.

Allow to disable deliverability check on validating email

As rororo uses email-validator for validating email strings need to allow setup check_deliverability and other possible options to validate_email call.

check_deliverability could be disabled to avoid making extra DNS calls, or when there is no need in caring about deliverability (like in test environments).

Improvements to env factory

There is two necessary things that still missed in current implementation of env_factory helper.

Optional Converter

When attribute type is an optional something, rororo doesn't try to convert the value from env to something, but returns the Optional[str] as os.getenv does. Which results in invalid expectations between type annotation & runtime execution.

Example below should work,

import uuid

import attr
from rororo.settings import env_factory


@attr.dataclass
class InviteSettings:
    secret_key: Optional[uuid.UUID] = env_factory("INVITE_SECRET_KEY")


settings = InviteSettings()
if settings.secret_key:
     assert isinstance(settings.secret_key, uuid.UUID)

but with rororo==2.0.0b3 it doesn't, as last settings.secret_key will be a str instance, not uuid.UUID

Basic support for sequences

Very often it is a case to provide an env sequence value as string joined with comma separator or some other delimiter.

It is understandable that support something like,

@attr.dataclass
class InviteSettings:
     secret_keys: Optional[List[uuid.UUID]] = env_factory("INVITE_SECRET_KEYS")

is a bit complicated and can take a time.

But in same time supporting List[str] via env_factory should be doable and helpful.

Allow to validate responses

While it is not the best for the performance, it is needed to have a way to enable validation of response data against the OpenAPI schema.

openapi-core supports it, so should the rororo.

Support array request body

Ensure to support request body with type: "array".

Currently, in 2.0.0a2, rororo cast request body to dict, but this will not work well for array requests.

Enhancements to aiohttp.web

There are several desired enhancements to aiohttp.web library that widely used in backend applications which should be added to rororo,

  • Improved Application to support debug mode and proper logging while serving with gunicorn
  • add_route context manager
  • Handling single static files, not directories
  • Error handling middleware

Ignore common parameters while attempting to fix security

Due to #114 and related PRs #115, #117 & #120 rororo on setup walks through OpenAPI schema in attempt to fix security defintion for the operations.

However, when some path definition describe common parameters as,

  "/auth/me/email/{request_uid}":
    parameters:
      - $ref: "#/components/parameters/RequestUID"

rororo doesn't recognize this case, which results in,

.venv/lib/python3.9/site-packages/rororo/openapi/openapi.py:675: in setup_openapi
    spec = fix_spec_operations(spec, cast(DictStrAny, schema))

>               mapping[operation_data["operationId"]] = operation_data.get(
                    "security"
                )
E               AttributeError: 'list' object has no attribute 'get'

Need to fix the issue and expect parameters to be defined for entire path.

2.0.0 Release

After a long test phase it is a time to publish final 2.0.0 version of rororo.

This version highlights changing rororo into schema first library for creating OpenAPI 3 aiohttp.web server applications.

At a moment rororo==2.0.0rc3 deployed to production environment for 5 projects and is literally ready to have final 2.0.0 version.

Provide set of meaningful benchmarks

To ensure that performance is not touched between rororo releases.

As of now I'm thinking about using pyperf as benchmark runner, and benchmarking,

  • In todobackend example,
    • setup_openapi performance
    • validate_request performance by creating 100+ todos via create_todo operation (without Redis layer)
    • validate_response performance by emulating list_todos operation with 100+ todos (without Redis layer)
  • In hobotnica example,
    • validate_security performance for basic auth
    • validate_security performance for HTTP auth

To complete task need to store benchmark results somewhere (gh-pages branch or as release artifact) and have ability to compare benchmark results between releases.

Improvements to validation errors

Need to provide various updates to validation errors, such as:

  • Allow to "merge" (__add__) validation errors
  • Simplify raising validation errors for nested data
  • Properly cover all custom OpenAPI errors in documentation (leftover of #100)

As of simplifying raising validation errors, need to introduce validation_error_context (via contextvars library), which allows to raise errors such as:

# In views.py
def create_item(request: web.Request) -> web.Response:
    with validation_error_context("body"):
        item = validate_create_item(get_validated_data(request))

    return web.json_response(asdict(item))

# In validators.py
def validate_create_item(data: MappingStrAny) -> Item:
    with validation_error_context("subitem"):
        subitem = validate_create_subitem(data["subitem"])

    if item["field"] != 42:
        raise ValidationError.from_dict(field="Unsupported field value")

    return Item.from_dict(data, subitem=subitem)


def validate_create_subitem(data: MappingStrAny) -> SubItem:
    if data["enum"] not in [42, 420]:
        raise ValidationError.from_dict(enum="Unsupported enum value")

    return SubItem.from_dict(data)

And if (for example) request body will have invalid field or subitem.enum value validator functions will generate next errors,

For invalid field value:

[
  {"loc": ["body", "field"], "message": "Unsupported field value"}
]

subitem.enum error:

[
  {"loc": ["body", "subitem", "enum"], "message": "Unsupported field value"}
]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.