Skip to content

Commit

Permalink
Fix openapi schema sharing for Page types
Browse files Browse the repository at this point in the history
  • Loading branch information
mofr committed Aug 1, 2024
1 parent f0232e8 commit ecac517
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 8 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [25.0.2] - 2024-08-01
- Fix openapi schema sharing for Page types

## [25.0.1] - 2024-07-28
- Fix openapi schema sharing between optional and non-optional types

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "winter"
version = "25.0.1"
version = "25.0.2"
homepage = "https://github.com/WinterFramework/winter"
description = "Web Framework with focus on python typing, dataclasses and modular design"
authors = ["Alexander Egorov <mofr@zond.org>"]
Expand Down
95 changes: 94 additions & 1 deletion tests/winter_openapi/test_api_request_and_response_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ class DataclassWithUndefined:
nested_2: Union[Dataclass, Undefined, None]


def test_reuse_schema():
def test_reuse_dataclass_schema():
class _TestAPI: # pragma: no cover
@winter.route_get('/method_return_1/')
def method_return_1(self) -> Dataclass:
Expand Down Expand Up @@ -936,6 +936,99 @@ def method_request_body_undefined_2(self, data: DataclassWithUndefined):
}


def test_reuse_page_schema():
class _TestAPI: # pragma: no cover
@winter.route_get('/method_1/')
def method_1(self) -> Page[str]:
pass

@winter.route_get('/method_2/')
def method_2(self) -> Page[str]:
pass

result = generate_openapi(
title='title',
version='1.0.0',
routes=[
get_route(_TestAPI.method_1),
get_route(_TestAPI.method_2),
],
)
assert result == {
'components': {
'parameters': {},
'responses': {},
'schemas': {
'PageMetaOfString': {
'properties': {
'limit': {'format': 'int32', 'nullable': True, 'type': 'integer'},
'next': {'nullable': True, 'type': 'string'},
'offset': {'format': 'int32', 'nullable': True, 'type': 'integer'},
'previous': {'nullable': True, 'type': 'string'},
'total_count': {'format': 'int32', 'type': 'integer'},
},
'required': ['total_count', 'limit', 'offset', 'previous', 'next'],
'title': 'PageMetaOfString',
'type': 'object'},
'PageOfString': {
'properties': {
'meta': {'$ref': '#/components/schemas/PageMetaOfString'},
'objects': {
'items': {'type': 'string'},
'type': 'array'
}
},
'required': ['meta', 'objects'],
'title': 'PageOfString',
'type': 'object',
},
},
},
'info': {'title': 'title', 'version': '1.0.0'},
'openapi': '3.0.3',
'paths': {
'/method_1/': {
'get': {
'deprecated': False,
'operationId': '_TestAPI.method_1',
'parameters': [],
'responses': {
'200': {
'content': {
'application/json': {
'schema': {'$ref': '#/components/schemas/PageOfString'},
},
},
'description': '',
},
},
'tags': ['method_1'],
},
},
'/method_2/': {
'get': {
'deprecated': False,
'operationId': '_TestAPI.method_2',
'parameters': [],
'responses': {
'200': {
'content': {
'application/json': {
'schema': {'$ref': '#/components/schemas/PageOfString'},
},
},
'description': '',
},
},
'tags': ['method_2'],
},
},
},
'servers': [{'url': '/'}],
'tags': [],
}


def test_raises_for_type_duplicates():
class DuplicateTypes:
@dataclass
Expand Down
23 changes: 17 additions & 6 deletions winter_openapi/inspectors/page_inspector.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import dataclasses
from typing import Dict
from typing import List
from typing import Optional
from typing import Type

from winter.data.pagination import Page
from winter_openapi.inspection.type_info import TypeInfo
from winter_openapi.inspectors.standard_types_inspectors import inspect_type
from winter_openapi.inspectors.standard_types_inspectors import register_type_inspector


# noinspection PyUnusedLocal
@register_type_inspector(Page)
def inspect_page(hint_class) -> TypeInfo:
args = getattr(hint_class, '__args__', None)
def create_dataclass(page_type: Type) -> Type:
args = getattr(page_type, '__args__', None)
child_class = args[0] if args else str
extra_fields = set(dataclasses.fields(hint_class.__origin__)) - set(dataclasses.fields(Page))
extra_fields = set(dataclasses.fields(page_type.__origin__)) - set(dataclasses.fields(Page))
child_type_info = inspect_type(child_class)
title = child_type_info.title or child_type_info.type_.capitalize()

Expand Down Expand Up @@ -47,5 +47,16 @@ def inspect_page(hint_class) -> TypeInfo:
),
)
PageDataclass.__doc__ = ''
return PageDataclass


page_to_dataclass_map: Dict[Type, Type] = {}

return inspect_type(PageDataclass)

# noinspection PyUnusedLocal
@register_type_inspector(Page)
def inspect_page(hint_class) -> TypeInfo:
if hint_class not in page_to_dataclass_map:
page_to_dataclass_map[hint_class] = create_dataclass(hint_class)
page_dataclass = page_to_dataclass_map[hint_class]
return inspect_type(page_dataclass)

0 comments on commit ecac517

Please sign in to comment.