Skip to content

Commit 2c1a042

Browse files
Merge pull request #322 from EvgenySmekalin/add-onpenapi-union-type-support
Add support of UnionType in OpenAPI schema
2 parents 9292eca + 419e9c8 commit 2c1a042

File tree

5 files changed

+69
-8
lines changed

5 files changed

+69
-8
lines changed

.github/workflows/testing.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ jobs:
3838
- name: Test with pytest
3939
run: |
4040
poetry run pytest -rfs --cov --cov-config=.coveragerc --cov-report="" --disable-warnings
41-
cp .coverage ".coverage.${{ matrix.python-version }}"
41+
cp .coverage ".coverage.${{ matrix.python-version }}-${{ matrix.sqlalchemy-version }}-${{ matrix.django-version }}"
4242
- name: Upload coverage report
43-
uses: actions/upload-artifact@v3
43+
uses: actions/upload-artifact@v4
4444
with:
45-
name: coverage-reports
45+
name: coverage-reports-${{ matrix.python-version }}-${{ matrix.sqlalchemy-version }}-${{ matrix.django-version }}
4646
include-hidden-files: true
47-
path: ".coverage.${{ matrix.python-version }}"
47+
path: ".coverage.${{ matrix.python-version }}-${{ matrix.sqlalchemy-version }}-${{ matrix.django-version }}"
4848

4949
coverage-check:
5050
name: Coverage check
@@ -60,9 +60,10 @@ jobs:
6060
run: |
6161
pip3 install coverage==7.2.3
6262
- name: Download coverage reports
63-
uses: actions/download-artifact@v3
63+
uses: actions/download-artifact@v4
6464
with:
65-
name: coverage-reports
65+
pattern: coverage-reports-*
66+
merge-multiple: true
6667
path: coverage-reports
6768
- name: Combine reports
6869
run: |

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [29.0.2] - 2025-02-07
8+
- Add support of UnionType in OpenAPI schema
9+
710
## [29.0.1] - 2024-12-12
811
- Make httpx version specification less restrictive
912

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "winter"
3-
version = "29.0.1"
3+
version = "29.0.2"
44
homepage = "https://github.com/WinterFramework/winter"
55
description = "Web Framework with focus on python typing, dataclasses and modular design"
66
authors = ["Alexander Egorov <[email protected]>"]

tests/winter_openapi/test_api_request_and_response_spec.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import datetime
22
import decimal
3+
import sys
34
import uuid
45
from dataclasses import dataclass
56
from enum import Enum
@@ -574,6 +575,55 @@ def simple_method(self, data: type_hint): # pragma: no cover
574575
assert result['components'] == expected_components
575576

576577

578+
@pytest.mark.skipif(
579+
not sys.version_info >= (3, 10),
580+
reason="These tests require Python 3.10 or higher"
581+
)
582+
def test_request_type_with_union_undefined():
583+
@dataclass
584+
class RequestBodyWithUnionUndefined:
585+
"""Some description"""
586+
field_a: str | Undefined
587+
field_b: str | Undefined = Undefined()
588+
589+
class _TestAPI:
590+
@winter.route_post('/types/')
591+
@winter.request_body('data')
592+
def simple_method(self, data: RequestBodyWithUnionUndefined): # pragma: no cover
593+
pass
594+
595+
expected_request_body_spec = {
596+
'schema': {
597+
'$ref': '#/components/schemas/RequestBodyWithUnionUndefinedInput',
598+
},
599+
}
600+
expected_components = {
601+
'parameters': {},
602+
'responses': {},
603+
'schemas': {
604+
'RequestBodyWithUnionUndefinedInput': {
605+
'title': 'RequestBodyWithUnionUndefinedInput',
606+
'description': 'Some description',
607+
'type': 'object',
608+
'properties': {
609+
'field_a': {'type': 'string'},
610+
'field_b': {'type': 'string'},
611+
},
612+
},
613+
},
614+
}
615+
616+
route = get_route(_TestAPI.simple_method)
617+
618+
# Act
619+
result = generate_openapi(title='title', version='1.0.0', routes=[route])
620+
621+
# Assert
622+
method_info = result['paths']['/types/']['post']['requestBody']
623+
assert method_info == {'content': {'application/json': expected_request_body_spec}, 'required': False}
624+
assert result['components'] == expected_components
625+
626+
577627
@dataclass
578628
class DataclassWithUndefined:
579629
"""DataclassWithUndefined description"""

winter/core/utils/typing.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import inspect
2-
import sys
2+
import types
33
from typing import Iterable
44
from typing import TypeVar
55
from typing import Union
6+
from typing import get_args
67

78
NoneType = type(None)
89
UnionType = type(Union)
@@ -37,6 +38,12 @@ def get_union_args(type_: object) -> list:
3738

3839

3940
def get_origin_type(hint_class):
41+
if hasattr(types, 'UnionType') and isinstance(hint_class, types.UnionType):
42+
# Extract the arguments of the union (e.g., `str | int` -> (str, int))
43+
args = get_args(hint_class)
44+
# Convert to the old `typing.Union` style
45+
hint_class = Union[args]
46+
4047
return getattr(hint_class, '__origin__', None) or hint_class
4148

4249

0 commit comments

Comments
 (0)