Compare commits
	
		
			12 Commits
		
	
	
		
			v1.4.1
			...
			v1.5.1-fix
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | efbaaa5e6f | ||
|  | 6211c71875 | ||
|  | 5567d73952 | ||
|  | 67a95ec9c9 | ||
|  | 93ec0f6c80 | ||
|  | a6d96d711b | ||
|  | 8aee135f95 | ||
|  | 462d8d8b98 | ||
|  | 0d3a33c964 | ||
|  | 22979b7e59 | ||
|  | b9519bb868 | ||
|  | 913f50298c | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,9 @@ | |||||||
|  | .coverage | ||||||
| .idea/ | .idea/ | ||||||
| .pytest_cache | .pytest_cache | ||||||
| __pycache__ | __pycache__ | ||||||
| aiohttp_pydantic.egg-info/ | aiohttp_pydantic.egg-info/ | ||||||
| build/ | build/ | ||||||
|  | coverage.xml | ||||||
| dist/ | dist/ | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,11 +2,14 @@ language: python | |||||||
| python: | python: | ||||||
| - '3.8' | - '3.8' | ||||||
| script: | script: | ||||||
| - pytest tests/ | - pytest --cov-report=xml --cov=aiohttp_pydantic tests/ | ||||||
| install: | install: | ||||||
| - pip install -U setuptools wheel pip | - pip install -U setuptools wheel pip | ||||||
| - pip install -r test_requirements.txt | - pip install -r requirements/test.txt | ||||||
|  | - pip install -r requirements/ci.txt | ||||||
| - pip install . | - pip install . | ||||||
|  | after_success: | ||||||
|  |   - codecov | ||||||
| deploy: | deploy: | ||||||
|   provider: pypi |   provider: pypi | ||||||
|   username: __token__ |   username: __token__ | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.rst
									
									
									
									
									
								
							| @@ -1,6 +1,16 @@ | |||||||
| Aiohttp pydantic - Aiohttp View to validate and parse request | Aiohttp pydantic - Aiohttp View to validate and parse request | ||||||
| ============================================================= | ============================================================= | ||||||
|  |  | ||||||
|  | .. image:: https://travis-ci.org/Maillol/aiohttp-pydantic.svg?branch=main | ||||||
|  |   :target: https://travis-ci.org/Maillol/aiohttp-pydantic | ||||||
|  |  | ||||||
|  | .. image:: https://img.shields.io/pypi/v/aiohttp-pydantic | ||||||
|  |   :target: https://img.shields.io/pypi/v/aiohttp-pydantic | ||||||
|  |   :alt: Latest PyPI package version | ||||||
|  |  | ||||||
|  | .. image:: https://codecov.io/gh/Maillol/aiohttp-pydantic/branch/main/graph/badge.svg | ||||||
|  |   :target: https://codecov.io/gh/Maillol/aiohttp-pydantic | ||||||
|  |   :alt: codecov.io status for master branch | ||||||
|  |  | ||||||
| Aiohttp pydantic is an `aiohttp view`_ to easily parse and validate request. | Aiohttp pydantic is an `aiohttp view`_ to easily parse and validate request. | ||||||
| You define using the function annotations what your methods for handling HTTP verbs expects and Aiohttp pydantic parses the HTTP request | You define using the function annotations what your methods for handling HTTP verbs expects and Aiohttp pydantic parses the HTTP request | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| from .view import PydanticView | from .view import PydanticView | ||||||
|  |  | ||||||
| __version__ = "1.4.1" | __version__ = "1.5.1" | ||||||
|  |  | ||||||
| __all__ = ("PydanticView", "__version__") | __all__ = ("PydanticView", "__version__") | ||||||
|   | |||||||
| @@ -1,3 +1,7 @@ | |||||||
|  | """ | ||||||
|  | Utility to write Open Api Specifications using the Python language. | ||||||
|  | """ | ||||||
|  |  | ||||||
| from typing import Union | from typing import Union | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -7,7 +11,7 @@ class Info: | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def title(self): |     def title(self): | ||||||
|         return self._spec["title"] |         return self._spec.get("title") | ||||||
|  |  | ||||||
|     @title.setter |     @title.setter | ||||||
|     def title(self, title): |     def title(self, title): | ||||||
| @@ -15,7 +19,7 @@ class Info: | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def description(self): |     def description(self): | ||||||
|         return self._spec["description"] |         return self._spec.get("description") | ||||||
|  |  | ||||||
|     @description.setter |     @description.setter | ||||||
|     def description(self, description): |     def description(self, description): | ||||||
| @@ -23,12 +27,20 @@ class Info: | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def version(self): |     def version(self): | ||||||
|         return self._spec["version"] |         return self._spec.get("version") | ||||||
|  |  | ||||||
|     @version.setter |     @version.setter | ||||||
|     def version(self, version): |     def version(self, version): | ||||||
|         self._spec["version"] = version |         self._spec["version"] = version | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def terms_of_service(self): | ||||||
|  |         return self._spec.get("termsOfService") | ||||||
|  |  | ||||||
|  |     @terms_of_service.setter | ||||||
|  |     def terms_of_service(self, terms_of_service): | ||||||
|  |         self._spec["termsOfService"] = terms_of_service | ||||||
|  |  | ||||||
|  |  | ||||||
| class RequestBody: | class RequestBody: | ||||||
|     def __init__(self, spec: dict): |     def __init__(self, spec: dict): | ||||||
| @@ -43,8 +55,8 @@ class RequestBody: | |||||||
|         self._spec["description"] = description |         self._spec["description"] = description | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def required(self): |     def required(self) -> bool: | ||||||
|         return self._spec["required"] |         return self._spec.get("required", False) | ||||||
|  |  | ||||||
|     @required.setter |     @required.setter | ||||||
|     def required(self, required: bool): |     def required(self, required: bool): | ||||||
| @@ -220,6 +232,22 @@ class PathItem: | |||||||
|     def trace(self) -> OperationObject: |     def trace(self) -> OperationObject: | ||||||
|         return OperationObject(self._spec.setdefault("trace", {})) |         return OperationObject(self._spec.setdefault("trace", {})) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def description(self) -> str: | ||||||
|  |         return self._spec["description"] | ||||||
|  |  | ||||||
|  |     @description.setter | ||||||
|  |     def description(self, description: str): | ||||||
|  |         self._spec["description"] = description | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def summary(self) -> str: | ||||||
|  |         return self._spec["summary"] | ||||||
|  |  | ||||||
|  |     @summary.setter | ||||||
|  |     def summary(self, summary: str): | ||||||
|  |         self._spec["summary"] = summary | ||||||
|  |  | ||||||
|  |  | ||||||
| class Paths: | class Paths: | ||||||
|     def __init__(self, spec: dict): |     def __init__(self, spec: dict): | ||||||
| @@ -244,7 +272,7 @@ class Server: | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def description(self) -> str: |     def description(self) -> str: | ||||||
|         return self._spec["url"] |         return self._spec["description"] | ||||||
|  |  | ||||||
|     @description.setter |     @description.setter | ||||||
|     def description(self, description: str): |     def description(self, description: str): | ||||||
| @@ -284,3 +312,8 @@ class OpenApiSpec3: | |||||||
|     @property |     @property | ||||||
|     def spec(self): |     def spec(self): | ||||||
|         return self._spec |         return self._spec | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def definitions(self): | ||||||
|  |         self._spec.setdefault('definitions', {}) | ||||||
|  |         return self._spec['definitions'] | ||||||
| @@ -13,7 +13,7 @@ Example: | |||||||
|  |  | ||||||
| from functools import lru_cache | from functools import lru_cache | ||||||
| from types import new_class | from types import new_class | ||||||
| from typing import Protocol, TypeVar | from typing import Protocol, TypeVar, Optional, Type | ||||||
|  |  | ||||||
| RespContents = TypeVar("RespContents", covariant=True) | RespContents = TypeVar("RespContents", covariant=True) | ||||||
|  |  | ||||||
| @@ -24,9 +24,10 @@ _status_code = frozenset(f"r{code}" for code in range(100, 600)) | |||||||
| def _make_status_code_type(status_code): | def _make_status_code_type(status_code): | ||||||
|     if status_code in _status_code: |     if status_code in _status_code: | ||||||
|         return new_class(status_code, (Protocol[RespContents],)) |         return new_class(status_code, (Protocol[RespContents],)) | ||||||
|  |     return None | ||||||
|  |  | ||||||
|  |  | ||||||
| def is_status_code_type(obj): | def is_status_code_type(obj) -> bool: | ||||||
|     """ |     """ | ||||||
|     Return True if obj is a status code type such as _200 or _404. |     Return True if obj is a status code type such as _200 or _404. | ||||||
|     """ |     """ | ||||||
|   | |||||||
| @@ -1,5 +1,9 @@ | |||||||
| import typing | import typing | ||||||
|  | from datetime import date, datetime | ||||||
|  | from inspect import getdoc | ||||||
|  | from itertools import count | ||||||
| from typing import List, Type | from typing import List, Type | ||||||
|  | from uuid import UUID | ||||||
|  |  | ||||||
| from aiohttp.web import Response, json_response | from aiohttp.web import Response, json_response | ||||||
| from aiohttp.web_app import Application | from aiohttp.web_app import Application | ||||||
| @@ -11,7 +15,30 @@ from ..utils import is_pydantic_base_model | |||||||
| from ..view import PydanticView, is_pydantic_view | from ..view import PydanticView, is_pydantic_view | ||||||
| from .typing import is_status_code_type | from .typing import is_status_code_type | ||||||
|  |  | ||||||
| JSON_SCHEMA_TYPES = {float: "number", str: "string", int: "integer"} | JSON_SCHEMA_TYPES = { | ||||||
|  |     float: {"type": "number"}, | ||||||
|  |     str: {"type": "string"}, | ||||||
|  |     int: {"type": "integer"}, | ||||||
|  |     UUID: {"type": "string", "format": "uuid"}, | ||||||
|  |     bool: {"type": "boolean"}, | ||||||
|  |     datetime: {"type": "string", "format": "date-time"}, | ||||||
|  |     date: {"type": "string", "format": "date"}, | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _handle_optional(type_): | ||||||
|  |     """ | ||||||
|  |     Returns the type wrapped in Optional or None. | ||||||
|  |  | ||||||
|  |     >>>  _handle_optional(int) | ||||||
|  |     >>>  _handle_optional(Optional[str]) | ||||||
|  |     <class 'str'> | ||||||
|  |     """ | ||||||
|  |     if typing.get_origin(type_) is typing.Union: | ||||||
|  |         args = typing.get_args(type_) | ||||||
|  |         if len(args) == 2 and type(None) in args: | ||||||
|  |             return next(iter(set(args) - {type(None)})) | ||||||
|  |     return None | ||||||
|  |  | ||||||
|  |  | ||||||
| class _OASResponseBuilder: | class _OASResponseBuilder: | ||||||
| @@ -20,13 +47,21 @@ class _OASResponseBuilder: | |||||||
|     generate the OAS operation response. |     generate the OAS operation response. | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     def __init__(self, oas_operation): |     def __init__(self, oas_operation, definitions): | ||||||
|         self._oas_operation = oas_operation |         self._oas_operation = oas_operation | ||||||
|  |         self._definitions = definitions | ||||||
|  |  | ||||||
|     @staticmethod |     def _process_definitions(self, schema): | ||||||
|     def _handle_pydantic_base_model(obj): |         if 'definitions' in schema: | ||||||
|  |             for k, v in schema['definitions'].items(): | ||||||
|  |                 self._definitions[k] = v | ||||||
|  |  | ||||||
|  |         return {i:schema[i] for i in schema if i!='definitions'} | ||||||
|  |  | ||||||
|  |     def _handle_pydantic_base_model(self, obj): | ||||||
|         if is_pydantic_base_model(obj): |         if is_pydantic_base_model(obj): | ||||||
|             return obj.schema() |             return self._process_definitions(obj.schema()) | ||||||
|  |  | ||||||
|         return {} |         return {} | ||||||
|  |  | ||||||
|     def _handle_list(self, obj): |     def _handle_list(self, obj): | ||||||
| @@ -61,40 +96,42 @@ class _OASResponseBuilder: | |||||||
|  |  | ||||||
|  |  | ||||||
| def _add_http_method_to_oas( | def _add_http_method_to_oas( | ||||||
|     oas_path: PathItem, http_method: str, view: Type[PydanticView] |     oas_path: PathItem, http_method: str, view: Type[PydanticView], definitions: dict | ||||||
| ): | ): | ||||||
|     http_method = http_method.lower() |     http_method = http_method.lower() | ||||||
|     oas_operation: OperationObject = getattr(oas_path, http_method) |     oas_operation: OperationObject = getattr(oas_path, http_method) | ||||||
|     handler = getattr(view, http_method) |     handler = getattr(view, http_method) | ||||||
|     path_args, body_args, qs_args, header_args = _parse_func_signature(handler) |     path_args, body_args, qs_args, header_args = _parse_func_signature(handler) | ||||||
|  |     description = getdoc(handler) | ||||||
|  |     if description: | ||||||
|  |         oas_operation.description = description | ||||||
|  |  | ||||||
|     if body_args: |     if body_args: | ||||||
|         oas_operation.request_body.content = { |         oas_operation.request_body.content = { | ||||||
|             "application/json": {"schema": next(iter(body_args.values())).schema()} |             "application/json": {"schema": next(iter(body_args.values())).schema()} | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     i = 0 |     indexes = count() | ||||||
|     for i, (name, type_) in enumerate(path_args.items()): |     for args_location, args in ( | ||||||
|         oas_operation.parameters[i].required = True |         ("path", path_args.items()), | ||||||
|         oas_operation.parameters[i].in_ = "path" |         ("query", qs_args.items()), | ||||||
|         oas_operation.parameters[i].name = name |         ("header", header_args.items()), | ||||||
|         oas_operation.parameters[i].schema = {"type": JSON_SCHEMA_TYPES[type_]} |     ): | ||||||
|  |         for name, type_ in args: | ||||||
|     for i, (name, type_) in enumerate(qs_args.items(), i + 1): |             i = next(indexes) | ||||||
|         oas_operation.parameters[i].required = False |             oas_operation.parameters[i].in_ = args_location | ||||||
|         oas_operation.parameters[i].in_ = "query" |             oas_operation.parameters[i].name = name | ||||||
|         oas_operation.parameters[i].name = name |             optional_type = _handle_optional(type_) | ||||||
|         oas_operation.parameters[i].schema = {"type": JSON_SCHEMA_TYPES[type_]} |             if optional_type is None: | ||||||
|  |                 oas_operation.parameters[i].schema = JSON_SCHEMA_TYPES[type_] | ||||||
|     for i, (name, type_) in enumerate(header_args.items(), i + 1): |                 oas_operation.parameters[i].required = True | ||||||
|         oas_operation.parameters[i].required = False |             else: | ||||||
|         oas_operation.parameters[i].in_ = "header" |                 oas_operation.parameters[i].schema = JSON_SCHEMA_TYPES[optional_type] | ||||||
|         oas_operation.parameters[i].name = name |                 oas_operation.parameters[i].required = False | ||||||
|         oas_operation.parameters[i].schema = {"type": JSON_SCHEMA_TYPES[type_]} |  | ||||||
|  |  | ||||||
|     return_type = handler.__annotations__.get("return") |     return_type = handler.__annotations__.get("return") | ||||||
|     if return_type is not None: |     if return_type is not None: | ||||||
|         _OASResponseBuilder(oas_operation).build(return_type) |         _OASResponseBuilder(oas_operation, definitions).build(return_type) | ||||||
|  |  | ||||||
|  |  | ||||||
| def generate_oas(apps: List[Application]) -> dict: | def generate_oas(apps: List[Application]) -> dict: | ||||||
| @@ -102,6 +139,7 @@ def generate_oas(apps: List[Application]) -> dict: | |||||||
|     Generate and return Open Api Specification from PydanticView in application. |     Generate and return Open Api Specification from PydanticView in application. | ||||||
|     """ |     """ | ||||||
|     oas = OpenApiSpec3() |     oas = OpenApiSpec3() | ||||||
|  |  | ||||||
|     for app in apps: |     for app in apps: | ||||||
|         for resources in app.router.resources(): |         for resources in app.router.resources(): | ||||||
|             for resource_route in resources: |             for resource_route in resources: | ||||||
| @@ -111,9 +149,9 @@ def generate_oas(apps: List[Application]) -> dict: | |||||||
|                     path = oas.paths[info.get("path", info.get("formatter"))] |                     path = oas.paths[info.get("path", info.get("formatter"))] | ||||||
|                     if resource_route.method == "*": |                     if resource_route.method == "*": | ||||||
|                         for method_name in view.allowed_methods: |                         for method_name in view.allowed_methods: | ||||||
|                             _add_http_method_to_oas(path, method_name, view) |                             _add_http_method_to_oas(path, method_name, view, oas.definitions) | ||||||
|                     else: |                     else: | ||||||
|                         _add_http_method_to_oas(path, resource_route.method, view) |                         _add_http_method_to_oas(path, resource_route.method, view, oas.definitions) | ||||||
|  |  | ||||||
|     return oas.spec |     return oas.spec | ||||||
|  |  | ||||||
| @@ -134,6 +172,9 @@ async def oas_ui(request): | |||||||
|  |  | ||||||
|     static_url = request.app.router["static"].url_for(filename="") |     static_url = request.app.router["static"].url_for(filename="") | ||||||
|     spec_url = request.app.router["spec"].url_for() |     spec_url = request.app.router["spec"].url_for() | ||||||
|  |  | ||||||
|  |     if request.scheme != request.headers.get('x-forwarded-proto', request.scheme): | ||||||
|  |         request = request.clone(scheme=request.headers['x-forwarded-proto']) | ||||||
|     host = request.url.origin() |     host = request.url.origin() | ||||||
|  |  | ||||||
|     return Response( |     return Response( | ||||||
|   | |||||||
| @@ -9,8 +9,14 @@ from aiohttp.web_exceptions import HTTPMethodNotAllowed | |||||||
| from aiohttp.web_response import StreamResponse | from aiohttp.web_response import StreamResponse | ||||||
| from pydantic import ValidationError | from pydantic import ValidationError | ||||||
|  |  | ||||||
| from .injectors import (AbstractInjector, BodyGetter, HeadersGetter, | from .injectors import ( | ||||||
|                         MatchInfoGetter, QueryGetter, _parse_func_signature) |     AbstractInjector, | ||||||
|  |     BodyGetter, | ||||||
|  |     HeadersGetter, | ||||||
|  |     MatchInfoGetter, | ||||||
|  |     QueryGetter, | ||||||
|  |     _parse_func_signature, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
| class PydanticView(AbstractView): | class PydanticView(AbstractView): | ||||||
|   | |||||||
							
								
								
									
										7
									
								
								requirements/ci.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								requirements/ci.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | certifi==2020.11.8 | ||||||
|  | chardet==3.0.4 | ||||||
|  | codecov==2.1.10 | ||||||
|  | coverage==5.3 | ||||||
|  | idna==2.10 | ||||||
|  | requests==2.25.0 | ||||||
|  | urllib3==1.26.2 | ||||||
							
								
								
									
										13
									
								
								requirements/test.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								requirements/test.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | attrs==20.3.0 | ||||||
|  | coverage==5.3 | ||||||
|  | iniconfig==1.1.1 | ||||||
|  | packaging==20.4 | ||||||
|  | pluggy==0.13.1 | ||||||
|  | py==1.9.0 | ||||||
|  | pyparsing==2.4.7 | ||||||
|  | pytest==6.1.2 | ||||||
|  | pytest-aiohttp==0.3.0 | ||||||
|  | pytest-cov==2.10.1 | ||||||
|  | six==1.15.0 | ||||||
|  | toml==0.10.2 | ||||||
|  | typing-extensions==3.7.4.3 | ||||||
| @@ -35,8 +35,8 @@ install_requires = | |||||||
|     swagger-ui-bundle |     swagger-ui-bundle | ||||||
|  |  | ||||||
| [options.extras_require] | [options.extras_require] | ||||||
| test = pytest; pytest-aiohttp | test = pytest==6.1.2; pytest-aiohttp==0.3.0; pytest-cov==2.10.1 | ||||||
|  | ci = pytest==6.1.2; pytest-aiohttp==0.3.0; pytest-cov==2.10.1; codecov==2.1.10 | ||||||
|  |  | ||||||
| [options.packages.find] | [options.packages.find] | ||||||
| exclude = | exclude = | ||||||
|   | |||||||
| @@ -1,3 +0,0 @@ | |||||||
| pytest==6.1.1 |  | ||||||
| pytest-aiohttp==0.3.0 |  | ||||||
| typing_extensions>=3.6.5 |  | ||||||
							
								
								
									
										0
									
								
								tests/test_oas/test_struct/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/test_oas/test_struct/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										54
									
								
								tests/test_oas/test_struct/test_info.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								tests/test_oas/test_struct/test_info.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | import pytest | ||||||
|  |  | ||||||
|  | from aiohttp_pydantic.oas.struct import OpenApiSpec3 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_info_title(): | ||||||
|  |     oas = OpenApiSpec3() | ||||||
|  |     assert oas.info.title is None | ||||||
|  |     oas.info.title = "Info Title" | ||||||
|  |     assert oas.info.title == "Info Title" | ||||||
|  |     assert oas.spec == {"info": {"title": "Info Title"}, "openapi": "3.0.0"} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_info_description(): | ||||||
|  |     oas = OpenApiSpec3() | ||||||
|  |     assert oas.info.description is None | ||||||
|  |     oas.info.description = "info description" | ||||||
|  |     assert oas.info.description == "info description" | ||||||
|  |     assert oas.spec == {"info": {"description": "info description"}, "openapi": "3.0.0"} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_info_version(): | ||||||
|  |     oas = OpenApiSpec3() | ||||||
|  |     assert oas.info.version is None | ||||||
|  |     oas.info.version = "3.14" | ||||||
|  |     assert oas.info.version == "3.14" | ||||||
|  |     assert oas.spec == {"info": {"version": "3.14"}, "openapi": "3.0.0"} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_info_terms_of_service(): | ||||||
|  |     oas = OpenApiSpec3() | ||||||
|  |     assert oas.info.terms_of_service is None | ||||||
|  |     oas.info.terms_of_service = "http://example.com/terms/" | ||||||
|  |     assert oas.info.terms_of_service == "http://example.com/terms/" | ||||||
|  |     assert oas.spec == { | ||||||
|  |         "info": {"termsOfService": "http://example.com/terms/"}, | ||||||
|  |         "openapi": "3.0.0", | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.skip("Not yet implemented") | ||||||
|  | def test_info_license(): | ||||||
|  |     oas = OpenApiSpec3() | ||||||
|  |     oas.info.license.name = "Apache 2.0" | ||||||
|  |     oas.info.license.url = "https://www.apache.org/licenses/LICENSE-2.0.html" | ||||||
|  |     assert oas.spec == { | ||||||
|  |         "info": { | ||||||
|  |             "license": { | ||||||
|  |                 "name": "Apache 2.0", | ||||||
|  |                 "url": "https://www.apache.org/licenses/LICENSE-2.0.html", | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         "openapi": "3.0.0", | ||||||
|  |     } | ||||||
							
								
								
									
										124
									
								
								tests/test_oas/test_struct/test_paths.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								tests/test_oas/test_struct/test_paths.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | |||||||
|  | from aiohttp_pydantic.oas.struct import OpenApiSpec3 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_paths_description(): | ||||||
|  |     oas = OpenApiSpec3() | ||||||
|  |     oas.paths["/users/{id}"].description = "This route ..." | ||||||
|  |     assert oas.spec == { | ||||||
|  |         "openapi": "3.0.0", | ||||||
|  |         "paths": {"/users/{id}": {"description": "This route ..."}}, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_paths_get(): | ||||||
|  |     oas = OpenApiSpec3() | ||||||
|  |     oas.paths["/users/{id}"].get | ||||||
|  |     assert oas.spec == {"openapi": "3.0.0", "paths": {"/users/{id}": {"get": {}}}} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_paths_operation_description(): | ||||||
|  |     oas = OpenApiSpec3() | ||||||
|  |     operation = oas.paths["/users/{id}"].get | ||||||
|  |     operation.description = "Long descriptions ..." | ||||||
|  |     assert oas.spec == { | ||||||
|  |         "openapi": "3.0.0", | ||||||
|  |         "paths": {"/users/{id}": {"get": {"description": "Long descriptions ..."}}}, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_paths_operation_summary(): | ||||||
|  |     oas = OpenApiSpec3() | ||||||
|  |     operation = oas.paths["/users/{id}"].get | ||||||
|  |     operation.summary = "Updates a pet in the store with form data" | ||||||
|  |     assert oas.spec == { | ||||||
|  |         "openapi": "3.0.0", | ||||||
|  |         "paths": { | ||||||
|  |             "/users/{id}": { | ||||||
|  |                 "get": {"summary": "Updates a pet in the store with form data"} | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_paths_operation_parameters(): | ||||||
|  |     oas = OpenApiSpec3() | ||||||
|  |     operation = oas.paths["/users/{petId}"].get | ||||||
|  |     parameter = operation.parameters[0] | ||||||
|  |     parameter.name = "petId" | ||||||
|  |     parameter.description = "ID of pet that needs to be updated" | ||||||
|  |     parameter.in_ = "path" | ||||||
|  |     parameter.required = True | ||||||
|  |  | ||||||
|  |     assert oas.spec == { | ||||||
|  |         "openapi": "3.0.0", | ||||||
|  |         "paths": { | ||||||
|  |             "/users/{petId}": { | ||||||
|  |                 "get": { | ||||||
|  |                     "parameters": [ | ||||||
|  |                         { | ||||||
|  |                             "description": "ID of pet that needs to be updated", | ||||||
|  |                             "in": "path", | ||||||
|  |                             "name": "petId", | ||||||
|  |                             "required": True, | ||||||
|  |                         } | ||||||
|  |                     ] | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_paths_operation_requestBody(): | ||||||
|  |     oas = OpenApiSpec3() | ||||||
|  |     request_body = oas.paths["/users/{petId}"].get.request_body | ||||||
|  |     request_body.description = "user to add to the system" | ||||||
|  |     request_body.content = { | ||||||
|  |         "application/json": { | ||||||
|  |             "schema": {"$ref": "#/components/schemas/User"}, | ||||||
|  |             "examples": { | ||||||
|  |                 "user": { | ||||||
|  |                     "summary": "User Example", | ||||||
|  |                     "externalValue": "http://foo.bar/examples/user-example.json", | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     request_body.required = True | ||||||
|  |     assert oas.spec == { | ||||||
|  |         "openapi": "3.0.0", | ||||||
|  |         "paths": { | ||||||
|  |             "/users/{petId}": { | ||||||
|  |                 "get": { | ||||||
|  |                     "requestBody": { | ||||||
|  |                         "content": { | ||||||
|  |                             "application/json": { | ||||||
|  |                                 "examples": { | ||||||
|  |                                     "user": { | ||||||
|  |                                         "externalValue": "http://foo.bar/examples/user-example.json", | ||||||
|  |                                         "summary": "User Example", | ||||||
|  |                                     } | ||||||
|  |                                 }, | ||||||
|  |                                 "schema": {"$ref": "#/components/schemas/User"}, | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         "description": "user to add to the system", | ||||||
|  |                         "required": True, | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_paths_operation_responses(): | ||||||
|  |     oas = OpenApiSpec3() | ||||||
|  |     response = oas.paths["/users/{petId}"].get.responses[200] | ||||||
|  |     response.description = "A complex object array response" | ||||||
|  |     response.content = { | ||||||
|  |         "application/json": { | ||||||
|  |             "schema": { | ||||||
|  |                 "type": "array", | ||||||
|  |                 "items": {"$ref": "#/components/schemas/VeryComplexType"}, | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
							
								
								
									
										36
									
								
								tests/test_oas/test_struct/test_servers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								tests/test_oas/test_struct/test_servers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | import pytest | ||||||
|  |  | ||||||
|  | from aiohttp_pydantic.oas.struct import OpenApiSpec3 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_sever_url(): | ||||||
|  |     oas = OpenApiSpec3() | ||||||
|  |     oas.servers[0].url = "https://development.gigantic-server.com/v1" | ||||||
|  |     oas.servers[1].url = "https://development.gigantic-server.com/v2" | ||||||
|  |     assert oas.spec == { | ||||||
|  |         "openapi": "3.0.0", | ||||||
|  |         "servers": [ | ||||||
|  |             {"url": "https://development.gigantic-server.com/v1"}, | ||||||
|  |             {"url": "https://development.gigantic-server.com/v2"}, | ||||||
|  |         ], | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_sever_description(): | ||||||
|  |     oas = OpenApiSpec3() | ||||||
|  |     oas.servers[0].url = "https://development.gigantic-server.com/v1" | ||||||
|  |     oas.servers[0].description = "Development server" | ||||||
|  |     assert oas.spec == { | ||||||
|  |         "openapi": "3.0.0", | ||||||
|  |         "servers": [ | ||||||
|  |             { | ||||||
|  |                 "url": "https://development.gigantic-server.com/v1", | ||||||
|  |                 "description": "Development server", | ||||||
|  |             } | ||||||
|  |         ], | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.skip("Not yet implemented") | ||||||
|  | def test_sever_variables(): | ||||||
|  |     oas = OpenApiSpec3() | ||||||
| @@ -1,4 +1,5 @@ | |||||||
| from typing import List, Union | from typing import List, Optional, Union | ||||||
|  | from uuid import UUID | ||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
| from aiohttp import web | from aiohttp import web | ||||||
| @@ -14,10 +15,16 @@ class Pet(BaseModel): | |||||||
|  |  | ||||||
|  |  | ||||||
| class PetCollectionView(PydanticView): | class PetCollectionView(PydanticView): | ||||||
|     async def get(self) -> r200[List[Pet]]: |     async def get( | ||||||
|  |         self, format: str, name: Optional[str] = None, *, promo: Optional[UUID] = None | ||||||
|  |     ) -> r200[List[Pet]]: | ||||||
|  |         """ | ||||||
|  |         Get a list of pets | ||||||
|  |         """ | ||||||
|         return web.json_response() |         return web.json_response() | ||||||
|  |  | ||||||
|     async def post(self, pet: Pet) -> r201[Pet]: |     async def post(self, pet: Pet) -> r201[Pet]: | ||||||
|  |         """Create a Pet""" | ||||||
|         return web.json_response() |         return web.json_response() | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -52,31 +59,53 @@ async def test_generated_oas_should_have_pets_paths(generated_oas): | |||||||
|  |  | ||||||
| async def test_pets_route_should_have_get_method(generated_oas): | async def test_pets_route_should_have_get_method(generated_oas): | ||||||
|     assert generated_oas["paths"]["/pets"]["get"] == { |     assert generated_oas["paths"]["/pets"]["get"] == { | ||||||
|  |         "description": "Get a list of pets", | ||||||
|  |         "parameters": [ | ||||||
|  |             { | ||||||
|  |                 "in": "query", | ||||||
|  |                 "name": "format", | ||||||
|  |                 "required": True, | ||||||
|  |                 "schema": {"type": "string"}, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 "in": "query", | ||||||
|  |                 "name": "name", | ||||||
|  |                 "required": False, | ||||||
|  |                 "schema": {"type": "string"}, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 "in": "header", | ||||||
|  |                 "name": "promo", | ||||||
|  |                 "required": False, | ||||||
|  |                 "schema": {"format": "uuid", "type": "string"}, | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|         "responses": { |         "responses": { | ||||||
|             "200": { |             "200": { | ||||||
|                 "content": { |                 "content": { | ||||||
|                     "application/json": { |                     "application/json": { | ||||||
|                         "schema": { |                         "schema": { | ||||||
|                             "type": "array", |  | ||||||
|                             "items": { |                             "items": { | ||||||
|                                 "title": "Pet", |  | ||||||
|                                 "type": "object", |  | ||||||
|                                 "properties": { |                                 "properties": { | ||||||
|                                     "id": {"title": "Id", "type": "integer"}, |                                     "id": {"title": "Id", "type": "integer"}, | ||||||
|                                     "name": {"title": "Name", "type": "string"}, |                                     "name": {"title": "Name", "type": "string"}, | ||||||
|                                 }, |                                 }, | ||||||
|                                 "required": ["id", "name"], |                                 "required": ["id", "name"], | ||||||
|  |                                 "title": "Pet", | ||||||
|  |                                 "type": "object", | ||||||
|                             }, |                             }, | ||||||
|  |                             "type": "array", | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         }, | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
| async def test_pets_route_should_have_post_method(generated_oas): | async def test_pets_route_should_have_post_method(generated_oas): | ||||||
|     assert generated_oas["paths"]["/pets"]["post"] == { |     assert generated_oas["paths"]["/pets"]["post"] == { | ||||||
|  |         "description": "Create a Pet", | ||||||
|         "requestBody": { |         "requestBody": { | ||||||
|             "content": { |             "content": { | ||||||
|                 "application/json": { |                 "application/json": { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user