diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 3aa4afd2..607105b0 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -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 @@ -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: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 60ca4c33..90076bba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/pyproject.toml b/pyproject.toml index 604f9b18..62fa3480 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 "] diff --git a/tests/winter_openapi/test_api_request_and_response_spec.py b/tests/winter_openapi/test_api_request_and_response_spec.py index 0823ca63..fef08593 100644 --- a/tests/winter_openapi/test_api_request_and_response_spec.py +++ b/tests/winter_openapi/test_api_request_and_response_spec.py @@ -1,5 +1,6 @@ import datetime import decimal +import sys import uuid from dataclasses import dataclass from enum import Enum @@ -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""" diff --git a/winter/core/utils/typing.py b/winter/core/utils/typing.py index 0c31ea96..2ed09ede 100644 --- a/winter/core/utils/typing.py +++ b/winter/core/utils/typing.py @@ -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) @@ -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 + hint_class = Union[args] + return getattr(hint_class, '__origin__', None) or hint_class