diff --git a/aiohttp_pydantic/oas/__init__.py b/aiohttp_pydantic/oas/__init__.py index ef45dcf..1e26791 100644 --- a/aiohttp_pydantic/oas/__init__.py +++ b/aiohttp_pydantic/oas/__init__.py @@ -1,5 +1,5 @@ from importlib import resources -from typing import Iterable +from typing import Iterable, Optional import jinja2 from aiohttp import web @@ -13,6 +13,8 @@ def setup( apps_to_expose: Iterable[web.Application] = (), url_prefix: str = "/oas", enable: bool = True, + version_spec: Optional[str] = None, + title_spec: Optional[str] = None, ): if enable: oas_app = web.Application() @@ -20,6 +22,9 @@ def setup( oas_app["index template"] = jinja2.Template( resources.read_text("aiohttp_pydantic.oas", "index.j2") ) + oas_app["version_spec"] = version_spec + oas_app["title_spec"] = title_spec + oas_app.router.add_get("/spec", get_oas, name="spec") oas_app.router.add_static("/static", swagger_ui_path, name="static") oas_app.router.add_get("", oas_ui, name="index") diff --git a/aiohttp_pydantic/oas/struct.py b/aiohttp_pydantic/oas/struct.py index 48f9ba9..576079a 100644 --- a/aiohttp_pydantic/oas/struct.py +++ b/aiohttp_pydantic/oas/struct.py @@ -305,7 +305,7 @@ class Components: class OpenApiSpec3: def __init__(self): - self._spec = {"openapi": "3.0.0"} + self._spec = {"openapi": "3.0.0", "info": {"version": "1.0.0", "title": "Aiohttp pydantic application"}} @property def info(self) -> Info: diff --git a/aiohttp_pydantic/oas/view.py b/aiohttp_pydantic/oas/view.py index 4aab93f..d91bb8e 100644 --- a/aiohttp_pydantic/oas/view.py +++ b/aiohttp_pydantic/oas/view.py @@ -1,7 +1,7 @@ import typing from inspect import getdoc from itertools import count -from typing import List, Type +from typing import List, Type, Optional, Dict from aiohttp.web import Response, json_response from aiohttp.web_app import Application @@ -147,11 +147,18 @@ def _add_http_method_to_oas( ) -def generate_oas(apps: List[Application]) -> dict: +def generate_oas(apps: List[Application], version_spec: Optional[str] = None, title_spec: Optional[str] = None) -> dict: """ Generate and return Open Api Specification from PydanticView in application. """ oas = OpenApiSpec3() + + if version_spec is not None: + oas.info.version = version_spec + + if title_spec is not None: + oas.info.title = title_spec + for app in apps: for resources in app.router.resources(): for resource_route in resources: @@ -175,7 +182,9 @@ async def get_oas(request): View to generate the Open Api Specification from PydanticView in application. """ apps = request.app["apps to expose"] - return json_response(generate_oas(apps)) + version_spec = request.app["version_spec"] + title_spec = request.app["title_spec"] + return json_response(generate_oas(apps, version_spec, title_spec)) async def oas_ui(request): diff --git a/demo/main.py b/demo/main.py index ee4902a..0ba8414 100644 --- a/demo/main.py +++ b/demo/main.py @@ -15,7 +15,7 @@ async def pet_not_found_to_404(request, handler): app = Application(middlewares=[pet_not_found_to_404]) -oas.setup(app) +oas.setup(app, version_spec="1.0.1", title_spec="My App") app["model"] = Model() app.router.add_view("/pets", PetCollectionView) diff --git a/tests/test_oas/test_cmd/test_cmd.py b/tests/test_oas/test_cmd/test_cmd.py index c74e3ee..bd50f27 100644 --- a/tests/test_oas/test_cmd/test_cmd.py +++ b/tests/test_oas/test_cmd/test_cmd.py @@ -22,8 +22,12 @@ def test_show_oas_of_app(cmd_line): args.func(args) expected = dedent( - """ - { + """ + { + "info": { + "title": "Aiohttp pydantic application", + "version": "1.0.0" + }, "openapi": "3.0.0", "paths": { "/route-1/{a}": { @@ -69,8 +73,12 @@ def test_show_oas_of_sub_app(cmd_line): args.output = StringIO() args.func(args) expected = dedent( - """ - { + """ + { + "info": { + "title": "Aiohttp pydantic application", + "version": "1.0.0" + }, "openapi": "3.0.0", "paths": { "/sub-app/route-2/{b}": { @@ -110,7 +118,7 @@ def test_show_oas_of_a_callable(cmd_line): """ { "info": { - "title": "MyApp", + "title": "Aiohttp pydantic application", "version": "1.0.0" }, "openapi": "3.0.0", diff --git a/tests/test_oas/test_struct/test_info.py b/tests/test_oas/test_struct/test_info.py index bcd1bf6..937782c 100644 --- a/tests/test_oas/test_struct/test_info.py +++ b/tests/test_oas/test_struct/test_info.py @@ -5,10 +5,16 @@ from aiohttp_pydantic.oas.struct import OpenApiSpec3 def test_info_title(): oas = OpenApiSpec3() - assert oas.info.title is None + assert oas.info.title == "Aiohttp pydantic application" oas.info.title = "Info Title" assert oas.info.title == "Info Title" - assert oas.spec == {"info": {"title": "Info Title"}, "openapi": "3.0.0"} + assert oas.spec == { + "info": { + "title": "Info Title", + "version": "1.0.0", + }, + "openapi": "3.0.0", + } def test_info_description(): @@ -16,15 +22,22 @@ def test_info_description(): 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"} + assert oas.spec == { + "info": { + "description": "info description", + "title": "Aiohttp pydantic application", + "version": "1.0.0", + }, + "openapi": "3.0.0", + } def test_info_version(): oas = OpenApiSpec3() - assert oas.info.version is None + assert oas.info.version == "1.0.0" oas.info.version = "3.14" assert oas.info.version == "3.14" - assert oas.spec == {"info": {"version": "3.14"}, "openapi": "3.0.0"} + assert oas.spec == {"info": {"version": "3.14", "title": "Aiohttp pydantic application"}, "openapi": "3.0.0"} def test_info_terms_of_service(): @@ -33,7 +46,11 @@ def test_info_terms_of_service(): 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/"}, + "info": { + "title": "Aiohttp pydantic application", + "version": "1.0.0", + "termsOfService": "http://example.com/terms/", + }, "openapi": "3.0.0", } diff --git a/tests/test_oas/test_struct/test_paths.py b/tests/test_oas/test_struct/test_paths.py index 7db09f3..f321a62 100644 --- a/tests/test_oas/test_struct/test_paths.py +++ b/tests/test_oas/test_struct/test_paths.py @@ -6,6 +6,7 @@ def test_paths_description(): oas.paths["/users/{id}"].description = "This route ..." assert oas.spec == { "openapi": "3.0.0", + "info": {"title": "Aiohttp pydantic application", "version": "1.0.0"}, "paths": {"/users/{id}": {"description": "This route ..."}}, } @@ -13,7 +14,11 @@ def test_paths_description(): def test_paths_get(): oas = OpenApiSpec3() oas.paths["/users/{id}"].get - assert oas.spec == {"openapi": "3.0.0", "paths": {"/users/{id}": {"get": {}}}} + assert oas.spec == { + "openapi": "3.0.0", + "info": {"title": "Aiohttp pydantic application", "version": "1.0.0"}, + "paths": {"/users/{id}": {"get": {}}}, + } def test_paths_operation_description(): @@ -22,6 +27,7 @@ def test_paths_operation_description(): operation.description = "Long descriptions ..." assert oas.spec == { "openapi": "3.0.0", + "info": {"title": "Aiohttp pydantic application", "version": "1.0.0"}, "paths": {"/users/{id}": {"get": {"description": "Long descriptions ..."}}}, } @@ -32,6 +38,7 @@ def test_paths_operation_summary(): operation.summary = "Updates a pet in the store with form data" assert oas.spec == { "openapi": "3.0.0", + "info": {"title": "Aiohttp pydantic application", "version": "1.0.0"}, "paths": { "/users/{id}": { "get": {"summary": "Updates a pet in the store with form data"} @@ -51,6 +58,7 @@ def test_paths_operation_parameters(): assert oas.spec == { "openapi": "3.0.0", + "info": {"title": "Aiohttp pydantic application", "version": "1.0.0"}, "paths": { "/users/{petId}": { "get": { @@ -86,6 +94,7 @@ def test_paths_operation_requestBody(): request_body.required = True assert oas.spec == { "openapi": "3.0.0", + "info": {"title": "Aiohttp pydantic application", "version": "1.0.0"}, "paths": { "/users/{petId}": { "get": { diff --git a/tests/test_oas/test_struct/test_servers.py b/tests/test_oas/test_struct/test_servers.py index b8e0406..d2bb1a4 100644 --- a/tests/test_oas/test_struct/test_servers.py +++ b/tests/test_oas/test_struct/test_servers.py @@ -9,6 +9,7 @@ def test_sever_url(): oas.servers[1].url = "https://development.gigantic-server.com/v2" assert oas.spec == { "openapi": "3.0.0", + "info": {"title": "Aiohttp pydantic application", "version": "1.0.0"}, "servers": [ {"url": "https://development.gigantic-server.com/v1"}, {"url": "https://development.gigantic-server.com/v2"}, @@ -22,6 +23,7 @@ def test_sever_description(): oas.servers[0].description = "Development server" assert oas.spec == { "openapi": "3.0.0", + "info": {"title": "Aiohttp pydantic application", "version": "1.0.0"}, "servers": [ { "url": "https://development.gigantic-server.com/v1", diff --git a/tests/test_oas/test_view.py b/tests/test_oas/test_view.py index adf653f..364e8eb 100644 --- a/tests/test_oas/test_view.py +++ b/tests/test_oas/test_view.py @@ -8,6 +8,7 @@ from pydantic.main import BaseModel from aiohttp_pydantic import PydanticView, oas from aiohttp_pydantic.oas.typing import r200, r201, r204, r404 +from aiohttp_pydantic.oas.view import generate_oas class Color(str, Enum): @@ -316,3 +317,23 @@ async def test_simple_type_route_should_have_get_method(generated_oas): } }, } + +async def test_generated_view_info_default(): + apps = (web.Application(),) + spec = generate_oas(apps) + + assert spec == {'info': {'title': 'Aiohttp pydantic application', 'version': '1.0.0'}, 'openapi': '3.0.0'} + + +async def test_generated_view_info_as_version(): + apps = (web.Application(),) + spec = generate_oas(apps, version_spec="test version") + + assert spec == {'info': {'title': 'Aiohttp pydantic application', 'version': 'test version'}, 'openapi': '3.0.0'} + + +async def test_generated_view_info_as_title(): + apps = (web.Application(),) + spec = generate_oas(apps, title_spec="test title") + + assert spec == {'info': {'title': 'test title', 'version': '1.0.0'}, 'openapi': '3.0.0'}