Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ jobs:
- name: Test with pytest
run: |
poetry run pytest -rfs --cov --cov-config=.coveragerc --cov-report="" --disable-warnings
cp .coverage ".coverage.${{ matrix.python-version }}"
cp .coverage ".coverage.${{ matrix.python-version }}-${{ matrix.sqlalchemy-version }}-${{ matrix.django-version }}"
- name: Upload coverage report
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: coverage-reports
name: coverage-reports-${{ matrix.python-version }}-${{ matrix.sqlalchemy-version }}-${{ matrix.django-version }}
include-hidden-files: true
path: ".coverage.${{ matrix.python-version }}"
path: ".coverage.${{ matrix.python-version }}-${{ matrix.sqlalchemy-version }}-${{ matrix.django-version }}"

coverage-check:
name: Coverage check
Expand All @@ -60,9 +60,10 @@ jobs:
run: |
pip3 install coverage==7.2.3
- name: Download coverage reports
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: coverage-reports
pattern: coverage-reports-*
merge-multiple: true
path: coverage-reports
- name: Combine reports
run: |
Expand Down
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).

## [29.0.2] - 2025-02-07
- Add support of UnionType in OpenAPI schema

## [29.0.1] - 2024-12-12
- Make httpx version specification less restrictive

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 = "29.0.1"
version = "29.0.2"
homepage = "https://github.com/WinterFramework/winter"
description = "Web Framework with focus on python typing, dataclasses and modular design"
authors = ["Alexander Egorov <[email protected]>"]
Expand Down
50 changes: 50 additions & 0 deletions tests/winter_openapi/test_api_request_and_response_spec.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import datetime
import decimal
import sys
import uuid
from dataclasses import dataclass
from enum import Enum
Expand Down Expand Up @@ -574,6 +575,55 @@ def simple_method(self, data: type_hint): # pragma: no cover
assert result['components'] == expected_components


@pytest.mark.skipif(
not sys.version_info >= (3, 10),
reason="These tests require Python 3.10 or higher"
)
def test_request_type_with_union_undefined():
@dataclass
class RequestBodyWithUnionUndefined:
"""Some description"""
field_a: str | Undefined
field_b: str | Undefined = Undefined()

class _TestAPI:
@winter.route_post('/types/')
@winter.request_body('data')
def simple_method(self, data: RequestBodyWithUnionUndefined): # pragma: no cover
pass

expected_request_body_spec = {
'schema': {
'$ref': '#/components/schemas/RequestBodyWithUnionUndefinedInput',
},
}
expected_components = {
'parameters': {},
'responses': {},
'schemas': {
'RequestBodyWithUnionUndefinedInput': {
'title': 'RequestBodyWithUnionUndefinedInput',
'description': 'Some description',
'type': 'object',
'properties': {
'field_a': {'type': 'string'},
'field_b': {'type': 'string'},
},
},
},
}

route = get_route(_TestAPI.simple_method)

# Act
result = generate_openapi(title='title', version='1.0.0', routes=[route])

# Assert
method_info = result['paths']['/types/']['post']['requestBody']
assert method_info == {'content': {'application/json': expected_request_body_spec}, 'required': False}
assert result['components'] == expected_components


@dataclass
class DataclassWithUndefined:
"""DataclassWithUndefined description"""
Expand Down
9 changes: 8 additions & 1 deletion winter/core/utils/typing.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import inspect
import sys
import types
from typing import Iterable
from typing import TypeVar
from typing import Union
from typing import get_args

NoneType = type(None)
UnionType = type(Union)
Expand Down Expand Up @@ -37,6 +38,12 @@ def get_union_args(type_: object) -> list:


def get_origin_type(hint_class):
if hasattr(types, 'UnionType') and isinstance(hint_class, types.UnionType):
# Extract the arguments of the union (e.g., `str | int` -> (str, int))
args = get_args(hint_class)
# Convert to the old `typing.Union` style
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To simplify future life, usually it's better to rely on newer style, not vice-versa.

hint_class = Union[args]

return getattr(hint_class, '__origin__', None) or hint_class


Expand Down