Skip to content

chore(python) update generator to 7.22#267

Open
cgoetz-inovex wants to merge 1 commit intomainfrom
feat/STACKITSDK-437_python-7.22
Open

chore(python) update generator to 7.22#267
cgoetz-inovex wants to merge 1 commit intomainfrom
feat/STACKITSDK-437_python-7.22

Conversation

@cgoetz-inovex
Copy link
Copy Markdown
Contributor

@cgoetz-inovex cgoetz-inovex commented May 7, 2026

template diff upstream 7.20 to 7.22:

diff --git a/modules/openapi-generator/src/main/resources/python/README_onlypackage.mustache b/modules/openapi-generator/src/main/resources/python/README_onlypackage.mustache
index 430fb722f84..841f33428a2 100644
--- a/modules/openapi-generator/src/main/resources/python/README_onlypackage.mustache
+++ b/modules/openapi-generator/src/main/resources/python/README_onlypackage.mustache
@@ -39,7 +39,7 @@ To be able to use it, you will need these dependencies in your own package that
 * pem >= 19.3.0
 * pycryptodome >= 3.9.0
 {{/hasHttpSignatureMethods}}
-* pydantic >= 2
+* pydantic >= 2.11
 * typing-extensions >= 4.7.1
 
 ## Getting Started
diff --git a/modules/openapi-generator/src/main/resources/python/api_client.mustache b/modules/openapi-generator/src/main/resources/python/api_client.mustache
index aaa5b267a47..e414300cd09 100644
--- a/modules/openapi-generator/src/main/resources/python/api_client.mustache
+++ b/modules/openapi-generator/src/main/resources/python/api_client.mustache
@@ -61,6 +61,7 @@ class ApiClient:
         'date': datetime.date,
         'datetime': datetime.datetime,
         'decimal': decimal.Decimal,
+        'UUID': uuid.UUID,
         'object': object,
     }
     _pool = None
@@ -313,7 +314,7 @@ class ApiClient:
         response_text = None
         return_data = None
         try:
-            if response_type == "bytearray":
+            if response_type in ("bytearray", "bytes"):
                 return_data = response_data.data
             elif response_type == "file":
                 return_data = self.__deserialize_file(response_data)
@@ -378,28 +379,24 @@ class ApiClient:
             return obj.isoformat()
         elif isinstance(obj, decimal.Decimal):
             return str(obj)
-
         elif isinstance(obj, dict):
-            obj_dict = obj
+            return {
+                key: self.sanitize_for_serialization(val)
+                for key, val in obj.items()
+            }
+
+        # Convert model obj to dict except
+        # attributes `openapi_types`, `attribute_map`
+        # and attributes which value is not None.
+        # Convert attribute name to json key in
+        # model definition for request.
+        if hasattr(obj, 'to_dict') and callable(getattr(obj, 'to_dict')):
+            obj_dict = obj.to_dict()
         else:
-            # Convert model obj to dict except
-            # attributes `openapi_types`, `attribute_map`
-            # and attributes which value is not None.
-            # Convert attribute name to json key in
-            # model definition for request.
-            if hasattr(obj, 'to_dict') and callable(getattr(obj, 'to_dict')):
-                obj_dict = obj.to_dict()
-            else:
-                obj_dict = obj.__dict__
+            obj_dict = obj.__dict__
 
-        if isinstance(obj_dict, list):
-            # here we handle instances that can either be a list or something else, and only became a real list by calling to_dict()
-            return self.sanitize_for_serialization(obj_dict)
+        return self.sanitize_for_serialization(obj_dict)
 
-        return {
-            key: self.sanitize_for_serialization(val)
-            for key, val in obj_dict.items()
-        }
 
     def deserialize(self, response_text: str, response_type: str, content_type: Optional[str]):
         """Deserializes response into an object.
@@ -475,6 +472,8 @@ class ApiClient:
             return self.__deserialize_datetime(data)
         elif klass is decimal.Decimal:
             return decimal.Decimal(data)
+        elif klass is uuid.UUID:
+            return uuid.UUID(data)
         elif issubclass(klass, Enum):
             return self.__deserialize_enum(data, klass)
         else:
diff --git a/modules/openapi-generator/src/main/resources/python/asyncio/rest.mustache b/modules/openapi-generator/src/main/resources/python/asyncio/rest.mustache
index 474413e955e..6945dfd4fc9 100644
--- a/modules/openapi-generator/src/main/resources/python/asyncio/rest.mustache
+++ b/modules/openapi-generator/src/main/resources/python/asyncio/rest.mustache
@@ -156,6 +156,8 @@ class RESTClientObject:
             if re.search('json', headers['Content-Type'], re.IGNORECASE):
                 if body is not None:
                     body = json.dumps(body)
+                if body is None and post_params:
+                    body = json.dumps(dict(post_params))
                 args["data"] = body
             elif headers['Content-Type'] == 'application/x-www-form-urlencoded':
                 args["data"] = aiohttp.FormData(post_params)
diff --git a/modules/openapi-generator/src/main/resources/python/configuration.mustache b/modules/openapi-generator/src/main/resources/python/configuration.mustache
index 52fb8a940b6..4027fe59b15 100644
--- a/modules/openapi-generator/src/main/resources/python/configuration.mustache
+++ b/modules/openapi-generator/src/main/resources/python/configuration.mustache
@@ -1,5 +1,8 @@
 {{>partial_header}}
 
+{{#asyncio}}
+import aiohttp_retry
+{{/asyncio}}
 {{#async}}
 import base64
 {{/async}}
@@ -183,11 +186,16 @@ class Configuration:
       string values to replace variables in templated server configuration.
       The validation of enums is performed for variables with defined enum
       values before.
+    :param verify_ssl: bool - Set this to false to skip verifying SSL certificate
+      when calling API from https server.
     :param ssl_ca_cert: str - the path to a file of concatenated CA certificates
       in PEM format.
-{{#async}}
+{{#asyncio}}
     :param retries: int | aiohttp_retry.RetryOptionsBase - Retry configuration.
-{{/async}}
+{{/asyncio}}
+{{#httpx}}
+    :param retries: int - Retry configuration.
+{{/httpx}}
 {{^async}}
     :param retries: int | urllib3.util.retry.Retry - Retry configuration.
 {{/async}}
@@ -195,6 +203,16 @@ class Configuration:
       in PEM (str) or DER (bytes) format.
     :param cert_file: the path to a client certificate file, for mTLS.
     :param key_file: the path to a client key file, for mTLS.
+    :param assert_hostname: Set this to True/False to enable/disable SSL hostname verification.
+    :param tls_server_name: SSL/TLS Server Name Indication (SNI). Set this to the SNI value expected by the server.
+    :param connection_pool_maxsize: Connection pool max size. None in the constructor is coerced to 100 for async and cpu_count * 5 for sync.
+    :param proxy: Proxy URL.
+    :param proxy_headers: Proxy headers.
+    :param safe_chars_for_path_param: Safe characters for path parameter encoding.
+    :param client_side_validation: Enable client-side validation. Default True.
+    :param socket_options: Options to pass down to the underlying urllib3 socket.
+    :param datetime_format: Datetime format string for serialization.
+    :param date_format: Date format string for serialization.
 
 {{#hasAuthMethods}}
     :Example:
@@ -300,10 +318,29 @@ conf = {{{packageName}}}.Configuration(
         server_operation_variables: Optional[Dict[int, ServerVariablesT]]=None,
         ignore_operation_servers: bool=False,
         ssl_ca_cert: Optional[str]=None,
-        retries: Optional[Union[int, Any]] = None,
+{{#asyncio}}
+        retries: Optional[Union[int, aiohttp_retry.RetryOptionsBase]] = None,
+{{/asyncio}}
+{{#httpx}}
+        retries: Optional[int] = None,
+{{/httpx}}
+{{^async}}
+        retries: Optional[Union[int, urllib3.util.retry.Retry]] = None,
+{{/async}}
         ca_cert_data: Optional[Union[str, bytes]] = None,
         cert_file: Optional[str]=None,
         key_file: Optional[str]=None,
+        verify_ssl: bool=True,
+        assert_hostname: Optional[bool]=None,
+        tls_server_name: Optional[str]=None,
+        connection_pool_maxsize: Optional[int]=None,
+        proxy: Optional[str]=None,
+        proxy_headers: Optional[Any]=None,
+        safe_chars_for_path_param: str='',
+        client_side_validation: bool=True,
+        socket_options: Optional[Any]=None,
+        datetime_format: str="{{{datetimeFormat}}}",
+        date_format: str="{{{dateFormat}}}",
         *,
         debug: Optional[bool] = None,
     ) -> None:
@@ -382,7 +419,7 @@ conf = {{{packageName}}}.Configuration(
         """Debug switch
         """
 
-        self.verify_ssl = True
+        self.verify_ssl = verify_ssl
         """SSL/TLS verification
            Set this to false to skip verifying SSL certificate when calling API
            from https server.
@@ -400,54 +437,51 @@ conf = {{{packageName}}}.Configuration(
         self.key_file = key_file
         """client key file
         """
-        self.assert_hostname = None
+        self.assert_hostname = assert_hostname
         """Set this to True/False to enable/disable SSL hostname verification.
         """
-        self.tls_server_name = None
+        self.tls_server_name = tls_server_name
         """SSL/TLS Server Name Indication (SNI)
            Set this to the SNI value expected by the server.
         """
 
         {{#async}}
-        self.connection_pool_maxsize = 100
+        self.connection_pool_maxsize = connection_pool_maxsize if connection_pool_maxsize is not None else 100
         """This value is passed to the aiohttp to limit simultaneous connections.
-           Default values is 100, None means no-limit.
+           None in the constructor is coerced to default 100.
         """
         {{/async}}
         {{^async}}
-        self.connection_pool_maxsize = multiprocessing.cpu_count() * 5
+        self.connection_pool_maxsize = connection_pool_maxsize if connection_pool_maxsize is not None else multiprocessing.cpu_count() * 5
         """urllib3 connection pool's maximum number of connections saved
-           per pool. urllib3 uses 1 connection as default value, but this is
-           not the best value when you are making a lot of possibly parallel
-           requests to the same host, which is often the case here.
-           cpu_count * 5 is used as default value to increase performance.
+           per pool. None in the constructor is coerced to cpu_count * 5.
         """
         {{/async}}
 
-        self.proxy: Optional[str] = None
+        self.proxy = proxy
         """Proxy URL
         """
-        self.proxy_headers = None
+        self.proxy_headers = proxy_headers
         """Proxy headers
         """
-        self.safe_chars_for_path_param = ''
+        self.safe_chars_for_path_param = safe_chars_for_path_param
         """Safe chars for path_param
         """
         self.retries = retries
         """Retry configuration
         """
         # Enable client side validation
-        self.client_side_validation = True
+        self.client_side_validation = client_side_validation
 
-        self.socket_options = None
+        self.socket_options = socket_options
         """Options to pass down to the underlying urllib3 socket
         """
 
-        self.datetime_format = "{{{datetimeFormat}}}"
+        self.datetime_format = datetime_format
         """datetime format
         """
 
-        self.date_format = "{{{dateFormat}}}"
+        self.date_format = date_format
         """date format
         """
 
diff --git a/modules/openapi-generator/src/main/resources/python/github-workflow.mustache b/modules/openapi-generator/src/main/resources/python/github-workflow.mustache
index 5ca2c1d0099..f6f72ae6995 100644
--- a/modules/openapi-generator/src/main/resources/python/github-workflow.mustache
+++ b/modules/openapi-generator/src/main/resources/python/github-workflow.mustache
@@ -17,7 +17,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
+        python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
 
     steps:
       - uses: actions/checkout@v4
diff --git a/modules/openapi-generator/src/main/resources/python/gitlab-ci.mustache b/modules/openapi-generator/src/main/resources/python/gitlab-ci.mustache
index f4bea123062..701ffbfd7e8 100644
--- a/modules/openapi-generator/src/main/resources/python/gitlab-ci.mustache
+++ b/modules/openapi-generator/src/main/resources/python/gitlab-ci.mustache
@@ -14,9 +14,6 @@ stages:
    - pip install -r test-requirements.txt
    - pytest --cov={{{packageName}}}
 
-pytest-3.9:
-  extends: .pytest
-  image: python:3.9-alpine
 pytest-3.10:
   extends: .pytest
   image: python:3.10-alpine
@@ -29,3 +26,6 @@ pytest-3.12:
 pytest-3.13:
   extends: .pytest
   image: python:3.13-alpine
+pytest-3.14:
+  extends: .pytest
+  image: python:3.14-alpine
diff --git a/modules/openapi-generator/src/main/resources/python/httpx/rest.mustache b/modules/openapi-generator/src/main/resources/python/httpx/rest.mustache
index 79a70e8815b..fc20b48351b 100644
--- a/modules/openapi-generator/src/main/resources/python/httpx/rest.mustache
+++ b/modules/openapi-generator/src/main/resources/python/httpx/rest.mustache
@@ -128,6 +128,8 @@ class RESTClientObject:
             if re.search('json', headers['Content-Type'], re.IGNORECASE):
                 if body is not None:
                     args["json"] = body
+                if body is None and post_params:
+                    args["json"] = dict(post_params)
             elif headers['Content-Type'] == 'application/x-www-form-urlencoded':  # noqa: E501
                 args["data"] = dict(post_params)
             elif headers['Content-Type'] == 'multipart/form-data':
diff --git a/modules/openapi-generator/src/main/resources/python/model_generic.mustache b/modules/openapi-generator/src/main/resources/python/model_generic.mustache
index 70804d448de..1b9d0f7d3fd 100644
--- a/modules/openapi-generator/src/main/resources/python/model_generic.mustache
+++ b/modules/openapi-generator/src/main/resources/python/model_generic.mustache
@@ -11,6 +11,7 @@ import json
 {{/vendorExtensions.x-py-model-imports}}
 from typing import Optional, Set
 from typing_extensions import Self
+from pydantic_core import to_jsonable_python
 
 {{#hasChildren}}
 {{#discriminator}}
@@ -52,6 +53,9 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
 
         {{/isNullable}}
         {{/required}}
+        if not isinstance(value, str):
+            value = str(value)
+
         if not re.match(r"{{{.}}}", value{{#vendorExtensions.x-modifiers}} ,re.{{{.}}}{{/vendorExtensions.x-modifiers}}):
             raise ValueError(r"must validate the regular expression {{{vendorExtensions.x-pattern}}}")
         return value
@@ -94,7 +98,8 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
 {{/vars}}
 
     model_config = ConfigDict(
-        populate_by_name=True,
+        validate_by_name=True,
+        validate_by_alias=True,
         validate_assignment=True,
         protected_namespaces=(),
     )
@@ -127,8 +132,7 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
 
     def to_json(self) -> str:
         """Returns the JSON representation of the model using alias"""
-        # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
-        return json.dumps(self.to_dict())
+        return json.dumps(to_jsonable_python(self.to_dict()))
 
     @classmethod
     def from_json(cls, json_str: str) -> Optional[{{^hasChildren}}Self{{/hasChildren}}{{#hasChildren}}{{#discriminator}}Union[{{#mappedModels}}{{{modelName}}}{{^-last}}, {{/-last}}{{/mappedModels}}]{{/discriminator}}{{^discriminator}}Self{{/discriminator}}{{/hasChildren}}]:
@@ -181,7 +185,21 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
             _dict['{{{baseName}}}'] = _items
         {{/items.items.isPrimitiveType}}
         {{/items.isArray}}
+        {{#items.isMap}}
+        {{^items.items.isPrimitiveType}}
+        # override the default output from pydantic by calling `to_dict()` of each item in {{{name}}} (list of dict)
+        _items = []
+        if self.{{{name}}}:
+            for _item_{{{name}}} in self.{{{name}}}:
+                if _item_{{{name}}}:
+                    _items.append(
+                         {_inner_key: _inner_value.to_dict() for _inner_key, _inner_value in _item_{{{name}}}.items()}
+                    )
+            _dict['{{{baseName}}}'] = _items
+        {{/items.items.isPrimitiveType}}
+        {{/items.isMap}}
         {{^items.isArray}}
+        {{^items.isMap}}
         {{^items.isPrimitiveType}}
         {{^items.isEnumOrRef}}
         # override the default output from pydantic by calling `to_dict()` of each item in {{{name}}} (list)
@@ -193,6 +211,7 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
             _dict['{{{baseName}}}'] = _items
         {{/items.isEnumOrRef}}
         {{/items.isPrimitiveType}}
+        {{/items.isMap}}
         {{/items.isArray}}
         {{/isArray}}
         {{#isMap}}
@@ -209,6 +228,20 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
             _dict['{{{baseName}}}'] = _field_dict_of_array
         {{/items.items.isPrimitiveType}}
         {{/items.isArray}}
+        {{#items.isMap}}
+        {{^items.items.isPrimitiveType}}
+        # override the default output from pydantic by calling `to_dict()` of each value in {{{name}}} (dict of dict)
+        _field_dict_of_dict = {}
+        if self.{{{name}}}:
+            for _key_{{{name}}}, _value_{{{name}}} in self.{{{name}}}.items():
+                if _value_{{{name}}} is not None:
+                    _field_dict_of_dict[_key_{{{name}}}] = {
+                        _key: _value.to_dict() for _key, _value in _value_{{{name}}}.items()
+                    }
+            _dict['{{{baseName}}}'] = _field_dict_of_dict
+        {{/items.items.isPrimitiveType}}
+        {{/items.isMap}}
+        {{^items.isMap}}
         {{^items.isArray}}
         {{^items.isPrimitiveType}}
         {{^items.isEnumOrRef}}
@@ -222,6 +255,7 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
         {{/items.isEnumOrRef}}
         {{/items.isPrimitiveType}}
         {{/items.isArray}}
+        {{/items.isMap}}
         {{/isMap}}
         {{/isContainer}}
         {{^isContainer}}
@@ -292,6 +326,7 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
             {{#allVars}}
             {{#isContainer}}
             {{#isArray}}
+            {{#items.isContainer}}
             {{#items.isArray}}
             {{#items.items.isPrimitiveType}}
             "{{{baseName}}}": obj.get("{{{baseName}}}"){{^-last}},{{/-last}}
@@ -303,7 +338,19 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
                 ] if obj.get("{{{baseName}}}") is not None else None{{^-last}},{{/-last}}
             {{/items.items.isPrimitiveType}}
             {{/items.isArray}}
-            {{^items.isArray}}
+            {{#items.isMap}}
+            {{#items.items.isPrimitiveType}}
+            "{{{baseName}}}": obj.get("{{{baseName}}}"){{^-last}},{{/-last}}
+            {{/items.items.isPrimitiveType}}
+            {{^items.items.isPrimitiveType}}
+            "{{{baseName}}}": [
+                    {_inner_key: {{{items.items.dataType}}}.from_dict(_inner_value) for _inner_key, _inner_value in _item.items()}
+                    for _item in obj["{{{baseName}}}"]
+                ] if obj.get("{{{baseName}}}") is not None else None{{^-last}},{{/-last}}
+            {{/items.items.isPrimitiveType}}
+            {{/items.isMap}}
+            {{/items.isContainer}}
+            {{^items.isContainer}}
             {{^items.isPrimitiveType}}
             {{#items.isEnumOrRef}}
             "{{{baseName}}}": obj.get("{{{baseName}}}"){{^-last}},{{/-last}}
@@ -315,7 +362,7 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
             {{#items.isPrimitiveType}}
             "{{{baseName}}}": obj.get("{{{baseName}}}"){{^-last}},{{/-last}}
             {{/items.isPrimitiveType}}
-            {{/items.isArray}}
+            {{/items.isContainer}}
             {{/isArray}}
             {{#isMap}}
             {{^items.isPrimitiveType}}
@@ -330,20 +377,18 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
                     if _v is not None
                     else None
                 )
-                for _k, _v in obj.get("{{{baseName}}}").items()
+                for _k, _v in obj["{{{baseName}}}"].items()
             )
             if obj.get("{{{baseName}}}") is not None
             else None{{^-last}},{{/-last}}
             {{/items.isMap}}
             {{#items.isArray}}
-            "{{{baseName}}}": dict(
-                (_k,
-                        [{{{items.items.dataType}}}.from_dict(_item) for _item in _v]
-                        if _v is not None
-                        else None
-                )
-                for _k, _v in obj.get("{{{baseName}}}", {}).items()
-            ){{^-last}},{{/-last}}
+            "{{{baseName}}}": {
+                _k: [{{{items.items.dataType}}}.from_dict(_item) for _item in _v] if _v is not None else None
+                for _k, _v in obj["{{{baseName}}}"].items()
+            }
+            if obj.get("{{{baseName}}}") is not None
+            else None{{^-last}},{{/-last}}
             {{/items.isArray}}
             {{/items.isContainer}}
             {{^items.isContainer}}
diff --git a/modules/openapi-generator/src/main/resources/python/pyproject.mustache b/modules/openapi-generator/src/main/resources/python/pyproject.mustache
index 7eed997a3f4..315d750bacd 100644
--- a/modules/openapi-generator/src/main/resources/python/pyproject.mustache
+++ b/modules/openapi-generator/src/main/resources/python/pyproject.mustache
@@ -32,7 +32,7 @@ keywords = ["OpenAPI", "OpenAPI-Generator", "{{{appName}}}"]
 include = ["{{packageName}}/py.typed"]
 
 [tool.poetry.dependencies]
-python = "^3.9"
+python = "^3.10"
 {{^async}}
 urllib3 = ">= 2.1.0, < 3.0.0"
 {{/async}}
@@ -51,7 +51,7 @@ tornado = ">=4.2, <5"
 pem = ">= 19.3.0"
 pycryptodome = ">= 3.9.0"
 {{/hasHttpSignatureMethods}}
-pydantic = ">= 2"
+pydantic = ">= 2.11"
 typing-extensions = ">= 4.7.1"
 {{#lazyImports}}
 lazy-imports = ">= 1, < 2"
@@ -79,7 +79,7 @@ dependencies = [
   "pem (>=19.3.0)",
   "pycryptodome (>=3.9.0)",
 {{/hasHttpSignatureMethods}}
-  "pydantic (>=2)",
+  "pydantic (>=2.11)",
   "typing-extensions (>=4.7.1)",
 {{#lazyImports}}
   "lazy-imports (>=1,<2)"
@@ -110,8 +110,14 @@ mypy = ">= 1.5"
 
 
 [build-system]
+{{#hatchling}}
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+{{/hatchling}}
+{{^hatchling}}
 requires = ["setuptools"]
 build-backend = "setuptools.build_meta"
+{{/hatchling}}
 
 [tool.pylint.'MESSAGES CONTROL']
 extension-pkg-whitelist = "pydantic"
diff --git a/modules/openapi-generator/src/main/resources/python/requirements.mustache b/modules/openapi-generator/src/main/resources/python/requirements.mustache
index aef16e4cb7a..2bd27cba9b8 100644
--- a/modules/openapi-generator/src/main/resources/python/requirements.mustache
+++ b/modules/openapi-generator/src/main/resources/python/requirements.mustache
@@ -16,7 +16,7 @@ tornado = ">= 4.2, < 5"
 pem >= 19.3.0
 pycryptodome >= 3.9.0
 {{/hasHttpSignatureMethods}}
-pydantic >= 2
+pydantic >= 2.11
 typing-extensions >= 4.7.1
 {{#lazyImports}}
 lazy-imports >= 1, < 2
diff --git a/modules/openapi-generator/src/main/resources/python/setup.mustache b/modules/openapi-generator/src/main/resources/python/setup.mustache
index 7304d0e8828..9506fd9c825 100644
--- a/modules/openapi-generator/src/main/resources/python/setup.mustache
+++ b/modules/openapi-generator/src/main/resources/python/setup.mustache
@@ -10,7 +10,7 @@ from setuptools import setup, find_packages  # noqa: H301
 # http://pypi.python.org/pypi/setuptools
 NAME = "{{{projectName}}}"
 VERSION = "{{packageVersion}}"
-PYTHON_REQUIRES = ">= 3.9"
+PYTHON_REQUIRES = ">= 3.10"
 REQUIRES = [
 {{^async}}
     "urllib3 >= 2.1.0, < 3.0.0",
@@ -30,7 +30,7 @@ REQUIRES = [
     "pem >= 19.3.0",
     "pycryptodome >= 3.9.0",
 {{/hasHttpSignatureMethods}}
-    "pydantic >= 2",
+    "pydantic >= 2.11",
     "typing-extensions >= 4.7.1",
 {{#lazyImports}}
     "lazy-imports >= 1, < 2",
diff --git a/modules/openapi-generator/src/main/resources/python/tornado/rest.mustache b/modules/openapi-generator/src/main/resources/python/tornado/rest.mustache
index 401f6a0b351..713defa5542 100644
--- a/modules/openapi-generator/src/main/resources/python/tornado/rest.mustache
+++ b/modules/openapi-generator/src/main/resources/python/tornado/rest.mustache
@@ -122,6 +122,8 @@ class RESTClientObject:
             if re.search('json', headers['Content-Type'], re.IGNORECASE):
                 if body:
                     body = json.dumps(body)
+                if body is None and post_params:
+                    body = json.dumps(dict(post_params))
                 request.body = body
             elif headers['Content-Type'] == 'application/x-www-form-urlencoded':
                 request.body = urlencode(post_params)
diff --git a/modules/openapi-generator/src/main/resources/python/travis.mustache b/modules/openapi-generator/src/main/resources/python/travis.mustache
index fd436438592..983251a4640 100644
--- a/modules/openapi-generator/src/main/resources/python/travis.mustache
+++ b/modules/openapi-generator/src/main/resources/python/travis.mustache
@@ -1,13 +1,13 @@
 # ref: https://docs.travis-ci.com/user/languages/python
 language: python
 python:
-  - "3.9"
   - "3.10"
   - "3.11"
   - "3.12"
   - "3.13"
+  - "3.14"
   # uncomment the following if needed
-  #- "3.13-dev"  # 3.13 development branch
+  #- "3.14-dev"  # 3.14 development branch
   #- "nightly"  # nightly build
 # command to install dependencies
 install:

@cgoetz-inovex cgoetz-inovex requested a review from a team as a code owner May 7, 2026 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant