Skip to content

[Python] Add HTTPX2 client library support - #24966

Open
Bernardoow wants to merge 7 commits into
OpenAPITools:masterfrom
Bernardoow:add-support-to-httpx2-python
Open

Bernardoow wants to merge 7 commits into
OpenAPITools:masterfrom
Bernardoow:add-support-to-httpx2-python

Conversation

@Bernardoow

@Bernardoow Bernardoow commented Sep 19, 2026

Copy link
Copy Markdown

Adds httpx2 as a library option for the existing Python generator, allowing clients to use HTTPX2 without custom templates or manual changes after generation.

Closes #24965

Changes

  • Add --library httpx2, using httpx2.AsyncClient.
  • Support optional synchronous wrappers through supportHttpxSync=true.
  • Reuse the existing HTTPX transport templates.
  • Generate HTTPX2-specific packaging with PEP 621 metadata, PEP 735 development dependency groups, and PEP 639 license expressions.
  • Configure Hatchling and setuptools packaging, including py.typed.
  • Add asynchronous and synchronous Petstore samples, tests, documentation, and CI coverage.

Existing Python library defaults and packaging behavior remain unchanged.

Example

java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate \
  -i openapi.yaml \
  -g python \
  --library httpx2 \
  --additional-properties=supportHttpxSync=true \
  -o ./client

Validation

Validated locally with Python 3.13 and HTTPX2 2.13.0:

  • Python generator tests: 72 passed.
  • HTTPX2 async sample: 214 passed, 2 skipped.
  • HTTPX2 sync sample: 277 passed, 2 skipped.
  • Both HTTPX2 samples passed mypy and Poetry installation.
  • Wheel and source distributions built with Hatchling and setuptools.
  • Wheels installed and imported successfully in clean environments.
  • Verified py.typed, runtime dependencies, and SPDX license metadata.
  • Confirmed generated output for HTTPX, urllib3, and asyncio remains unchanged.
  • Integration tests used a local Petstore server.
  • Python 3.10–3.14 coverage is configured in CI; only Python 3.13 was exercised locally.

Summary by cubic

Adds httpx2 as a built-in library option for the Python generator, closing #24965. --library httpx2 now generates a client using httpx2.AsyncClient without custom templates. Packaging for httpx2 is unified with the other Python libraries; non-httpx2 projects were regenerated with the new pyproject.toml and README format, which now uses PEP 621/735/639 metadata, SPDX licenses, and py.typed. Legacy poetry1=true keeps the deprecated [tool.poetry.dev-dependencies] and works for httpx2 as well.

New Features

  • supportHttpxSync=true adds blocking _sync variants alongside async methods for httpx and httpx2.
  • Projects use PEP 621/735/639 metadata, SPDX license expressions, and py.typed; legacy poetry1=true retains deprecated dev-dependencies and is supported for httpx2.
  • Supports Hatchling and setuptools builds.
  • Adds async and sync Petstore samples, generator tests, docs, and CI coverage.

Migration

  • Requires Python 3.10+ and httpx2>=2.13.0,<3.
  • Dev dependency groups require Poetry 2.2+ or pip 25.1+.

Written for commit 7ad5928. Summary will update on new commits.

Review in cubic

- Add httpx2 transport with optional synchronous wrappers
- Reuse HTTPX templates and configure HTTPX2 dependencies
- Add Petstore samples, generation tests, and CI coverage
- Update Python generator documentation
- Add a dedicated pyproject template using PEP 621, 639, and 735
- Handle poetry1=false correctly and reject legacy mode for HTTPX2
- Escape TOML metadata and preserve SPDX license identifiers
- Configure Hatchling and setuptools package discovery and py.typed
- Update generation tests, documentation, and HTTPX2 samples

Keep packaging behavior unchanged for other Python libraries.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

14 issues found across 817 files

Not reviewed (too large): samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/api/fake_api.py (~14,014 lines), samples/openapi3/client/petstore/python-httpx2/petstore_api/api/fake_api.py (~10,119 lines), samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/api/pet_api.py (~3,539 lines), samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/api/user_api.py (~2,983 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="samples/openapi3/client/petstore/python-httpx2-sync/docs/NumberOnly.md">

<violation number="1" location="samples/openapi3/client/petstore/python-httpx2-sync/docs/NumberOnly.md:20">
P3: The doc example calls the instance method `to_json()` on the class instead of the instance, so copying this example raises `TypeError: to_json() missing 1 required positional argument: 'self'` (confirmed: `number_only.py` declares `def to_json(self)`). The same bug is in the generating template `modules/openapi-generator/src/main/resources/python/model_doc.mustache` line 24 (`print({{classname}}.to_json())`), where the instance `{{#lambda.snakecase}}{{classname}}{{/lambda.snakecase}}_instance` is already available. Fix the template, then regenerate the samples.</violation>
</file>

<file name="samples/openapi3/client/petstore/python-httpx2-sync/docs/Bathing.md">

<violation number="1" location="samples/openapi3/client/petstore/python-httpx2-sync/docs/Bathing.md:22">
P3: The runnable example in this generated doc is broken: `to_json()` is an instance method (`def to_json(self)` in petstore_api/models/bathing.py), so `print(Bathing.to_json())` raises `TypeError` when copied and run. The example should print `bathing_instance.to_json()` — the comment above it says "print the JSON string representation of the object", and `bathing_instance` is the object created two lines earlier. Root cause is the shared template `modules/openapi-generator/src/main/resources/python/model_doc.mustache:24`, which emits `print({{classname}}.to_json())` and makes the same broken example appear in every Python sample; fix it there if this PR touches that template, otherwise correct the generated output here.</violation>
</file>

<file name="samples/openapi3/client/petstore/python-httpx2-sync/docs/Name.md">

<violation number="1" location="samples/openapi3/client/petstore/python-httpx2-sync/docs/Name.md:24">
P3: The docs example calls `Name.to_json()` on the class, but `to_json` is an instance method (`def to_json(self)` in `petstore_api/models/name.py`), so a user copying this example gets `TypeError: to_json() missing 1 required positional argument: 'self'`. Call it on the instance, as already done for `from_json`/`to_dict`.</violation>
</file>

<file name="samples/openapi3/client/petstore/python-httpx2-sync/docs/ClassModel.md">

<violation number="1" location="samples/openapi3/client/petstore/python-httpx2-sync/docs/ClassModel.md:21">
P3: The generated example calls `to_json()` on the class, but `to_json` is an instance method (`def to_json(self)` in `petstore_api/models/class_model.py`), so running `print(ClassModel.to_json())` raises `TypeError: to_json() missing 1 required positional argument: 'self'`. Use the instance created two lines above instead. Root cause is in `modules/openapi-generator/src/main/resources/python/model_doc.mustache` (line: `print({{classname}}.to_json())`), so also update the template and regenerate the samples, otherwise any manual edit here is overwritten.</violation>
</file>

<file name="samples/openapi3/client/petstore/python-httpx2-sync/docs/SpecialModelName.md">

<violation number="1" location="samples/openapi3/client/petstore/python-httpx2-sync/docs/SpecialModelName.md:20">
P3: The example code crashes if copied: `SpecialModelName.to_json()` calls the instance method `to_json(self)` on the class, raising `TypeError: missing 1 required positional argument: 'self'`. Call it on the instance created above: `print(special_model_name_instance.to_json())`. The defect comes from model_doc.mustache line 24, so fix the template and regenerate instead of hand-editing this generated file.</violation>
</file>

<file name="samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/models/file.py">

<violation number="1" location="samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/models/file.py:22">
P3: `Optional` is imported twice (`from typing import Any, ClassVar, Dict, List, Optional` then `from typing import Optional, Set`), which flake8/ruff flag as F811 (redefinition of unused import). This is generated from the shared Python model template, so the fix belongs in the template (`modules/openapi-generator/src/main/resources/python/model.mustache`), not by hand-editing this sample. Merge `Optional` into the first import.</violation>
</file>

<file name="samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/models/additional_properties_with_description_only.py">

<violation number="1" location="samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/models/additional_properties_with_description_only.py:38">
P2: When callers construct this model with an allowed property such as `AdditionalPropertiesWithDescriptionOnly(foo=1)`, Pydantic's default `extra="ignore"` drops `foo`; only `from_dict` preserves it. Configure `extra="allow"` in the generated model so normal construction honors the schema's `additionalProperties`.</violation>
</file>

<file name="samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/models/nullable_property.py">

<violation number="1" location="samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/models/nullable_property.py:102">
P2: When `from_dict` receives a payload without required `name`, this `.get()` turns the missing property into `None`, which nullable validation accepts. Preserve the input mapping when validating this model, or otherwise distinguish an omitted key from an explicit null.</violation>
</file>

<file name="samples/openapi3/client/petstore/python-httpx2-sync/docs/AdditionalPropertiesAnyType.md">

<violation number="1" location="samples/openapi3/client/petstore/python-httpx2-sync/docs/AdditionalPropertiesAnyType.md:20">
P2: The model examples call `to_json` on the class, but `to_json` is an instance method, so copying the example raises `TypeError`. Call `to_json()` on the created instance in the generated model examples.</violation>
</file>

<file name="samples/openapi3/client/petstore/python-httpx2-sync/git_push.sh">

<violation number="1" location="samples/openapi3/client/petstore/python-httpx2-sync/git_push.sh:46">
P1: When `GIT_TOKEN` is set, this command persists the token in `.git/config` as the `origin` URL. Use a temporary credential helper or per-command authentication instead of storing the token in the remote URL.</violation>

<violation number="2" location="samples/openapi3/client/petstore/python-httpx2-sync/git_push.sh:50">
P2: When the destination repository does not use `master`, this script pulls from and pushes to the wrong branch. Derive the current/default branch and use it for both commands, or push `HEAD` rather than hard-coding `master`.</violation>

<violation number="3" location="samples/openapi3/client/petstore/python-httpx2-sync/git_push.sh:54">
P2: When `git push` fails with an error other than `To https`, this pipeline exits successfully because the shell returns `grep`'s status. Run `git push` without the pipeline or explicitly preserve its exit status.</violation>
</file>

<file name="samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/models/enum_arrays.py">

<violation number="1" location="samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/models/enum_arrays.py:22">
P3: `Optional` is imported twice: once combined (`Any, ClassVar, Dict, List, Optional`) and again here with `Set`. Flake8 flags this as F811. Merge the two lines into one `from typing import` line in the model template (and this generated sample), so regeneration keeps a single combined typing import.</violation>
</file>

<file name="samples/openapi3/client/petstore/python-httpx2-sync/docs/FakeClassnameTags123Api.md">

<violation number="1" location="samples/openapi3/client/petstore/python-httpx2-sync/docs/FakeClassnameTags123Api.md:41">
P2: The API-key example uses `os.environ` without importing `os`, so it fails with `NameError` before making the request. Add `import os` to the example imports.</violation>
</file>

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Re-trigger cubic

echo "[INFO] \${GIT_TOKEN} (environment variable) is not set. Using the git credential in your environment."
git remote add origin "https://${git_host}/${git_user_id}/${git_repo_id}.git"
else
git remote add origin "https://${git_user_id}:${GIT_TOKEN}@${git_host}/${git_user_id}/${git_repo_id}.git"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: When GIT_TOKEN is set, this command persists the token in .git/config as the origin URL. Use a temporary credential helper or per-command authentication instead of storing the token in the remote URL.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/openapi3/client/petstore/python-httpx2-sync/git_push.sh, line 46:

<comment>When `GIT_TOKEN` is set, this command persists the token in `.git/config` as the `origin` URL. Use a temporary credential helper or per-command authentication instead of storing the token in the remote URL.</comment>

<file context>
@@ -0,0 +1,54 @@
+        echo "[INFO] \${GIT_TOKEN} (environment variable) is not set. Using the git credential in your environment."
+        git remote add origin "https://${git_host}/${git_user_id}/${git_repo_id}.git"
+    else
+        git remote add origin "https://${git_user_id}:${GIT_TOKEN}@${git_host}/${git_user_id}/${git_repo_id}.git"
+    fi
+fi
</file context>

validate_by_name=True,
validate_by_alias=True,
validate_assignment=True,
protected_namespaces=(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: When callers construct this model with an allowed property such as AdditionalPropertiesWithDescriptionOnly(foo=1), Pydantic's default extra="ignore" drops foo; only from_dict preserves it. Configure extra="allow" in the generated model so normal construction honors the schema's additionalProperties.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/models/additional_properties_with_description_only.py, line 38:

<comment>When callers construct this model with an allowed property such as `AdditionalPropertiesWithDescriptionOnly(foo=1)`, Pydantic's default `extra="ignore"` drops `foo`; only `from_dict` preserves it. Configure `extra="allow"` in the generated model so normal construction honors the schema's `additionalProperties`.</comment>

<file context>
@@ -0,0 +1,101 @@
+        validate_by_name=True,
+        validate_by_alias=True,
+        validate_assignment=True,
+        protected_namespaces=(),
+    )
+
</file context>
Suggested change
protected_namespaces=(),
extra="allow",
protected_namespaces=(),


_obj = cls.model_validate({
"id": obj.get("id"),
"name": obj.get("name")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: When from_dict receives a payload without required name, this .get() turns the missing property into None, which nullable validation accepts. Preserve the input mapping when validating this model, or otherwise distinguish an omitted key from an explicit null.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/models/nullable_property.py, line 102:

<comment>When `from_dict` receives a payload without required `name`, this `.get()` turns the missing property into `None`, which nullable validation accepts. Preserve the input mapping when validating this model, or otherwise distinguish an omitted key from an explicit null.</comment>

<file context>
@@ -0,0 +1,106 @@
+
+        _obj = cls.model_validate({
+            "id": obj.get("id"),
+            "name": obj.get("name")
+        })
+        return _obj
</file context>

Comment thread modules/openapi-generator/src/main/resources/python/httpx2/pyproject.mustache Outdated
# create an instance of AdditionalPropertiesAnyType from a JSON string
additional_properties_any_type_instance = AdditionalPropertiesAnyType.from_json(json)
# print the JSON string representation of the object
print(AdditionalPropertiesAnyType.to_json())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The model examples call to_json on the class, but to_json is an instance method, so copying the example raises TypeError. Call to_json() on the created instance in the generated model examples.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/openapi3/client/petstore/python-httpx2-sync/docs/AdditionalPropertiesAnyType.md, line 20:

<comment>The model examples call `to_json` on the class, but `to_json` is an instance method, so copying the example raises `TypeError`. Call `to_json()` on the created instance in the generated model examples.</comment>

<file context>
@@ -0,0 +1,29 @@
+# create an instance of AdditionalPropertiesAnyType from a JSON string
+additional_properties_any_type_instance = AdditionalPropertiesAnyType.from_json(json)
+# print the JSON string representation of the object
+print(AdditionalPropertiesAnyType.to_json())
+
+# convert the object into a dict
</file context>

# create an instance of ClassModel from a JSON string
class_model_instance = ClassModel.from_json(json)
# print the JSON string representation of the object
print(ClassModel.to_json())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The generated example calls to_json() on the class, but to_json is an instance method (def to_json(self) in petstore_api/models/class_model.py), so running print(ClassModel.to_json()) raises TypeError: to_json() missing 1 required positional argument: 'self'. Use the instance created two lines above instead. Root cause is in modules/openapi-generator/src/main/resources/python/model_doc.mustache (line: print({{classname}}.to_json())), so also update the template and regenerate the samples, otherwise any manual edit here is overwritten.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/openapi3/client/petstore/python-httpx2-sync/docs/ClassModel.md, line 21:

<comment>The generated example calls `to_json()` on the class, but `to_json` is an instance method (`def to_json(self)` in `petstore_api/models/class_model.py`), so running `print(ClassModel.to_json())` raises `TypeError: to_json() missing 1 required positional argument: 'self'`. Use the instance created two lines above instead. Root cause is in `modules/openapi-generator/src/main/resources/python/model_doc.mustache` (line: `print({{classname}}.to_json())`), so also update the template and regenerate the samples, otherwise any manual edit here is overwritten.</comment>

<file context>
@@ -0,0 +1,30 @@
+# create an instance of ClassModel from a JSON string
+class_model_instance = ClassModel.from_json(json)
+# print the JSON string representation of the object
+print(ClassModel.to_json())
+
+# convert the object into a dict
</file context>

Comment on lines +22 to +23
from typing import Optional, Set
from typing_extensions import Self

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Optional is imported twice: once combined (Any, ClassVar, Dict, List, Optional) and again here with Set. Flake8 flags this as F811. Merge the two lines into one from typing import line in the model template (and this generated sample), so regeneration keeps a single combined typing import.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/models/enum_arrays.py, line 22:

<comment>`Optional` is imported twice: once combined (`Any, ClassVar, Dict, List, Optional`) and again here with `Set`. Flake8 flags this as F811. Merge the two lines into one `from typing import` line in the model template (and this generated sample), so regeneration keeps a single combined typing import.</comment>

<file context>
@@ -0,0 +1,111 @@
+
+from pydantic import BaseModel, ConfigDict, StrictStr, field_validator
+from typing import Any, ClassVar, Dict, List, Optional
+from typing import Optional, Set
+from typing_extensions import Self
+from pydantic_core import to_jsonable_python
</file context>
Suggested change
from typing import Optional, Set
from typing_extensions import Self
from typing import Any, ClassVar, Dict, List, Optional, Set

# create an instance of SpecialModelName from a JSON string
special_model_name_instance = SpecialModelName.from_json(json)
# print the JSON string representation of the object
print(SpecialModelName.to_json())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The example code crashes if copied: SpecialModelName.to_json() calls the instance method to_json(self) on the class, raising TypeError: missing 1 required positional argument: 'self'. Call it on the instance created above: print(special_model_name_instance.to_json()). The defect comes from model_doc.mustache line 24, so fix the template and regenerate instead of hand-editing this generated file.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/openapi3/client/petstore/python-httpx2-sync/docs/SpecialModelName.md, line 20:

<comment>The example code crashes if copied: `SpecialModelName.to_json()` calls the instance method `to_json(self)` on the class, raising `TypeError: missing 1 required positional argument: 'self'`. Call it on the instance created above: `print(special_model_name_instance.to_json())`. The defect comes from model_doc.mustache line 24, so fix the template and regenerate instead of hand-editing this generated file.</comment>

<file context>
@@ -0,0 +1,29 @@
+# create an instance of SpecialModelName from a JSON string
+special_model_name_instance = SpecialModelName.from_json(json)
+# print the JSON string representation of the object
+print(SpecialModelName.to_json())
+
+# convert the object into a dict
</file context>


from pydantic import BaseModel, ConfigDict, Field, StrictStr
from typing import Any, ClassVar, Dict, List, Optional
from typing import Optional, Set

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Optional is imported twice (from typing import Any, ClassVar, Dict, List, Optional then from typing import Optional, Set), which flake8/ruff flag as F811 (redefinition of unused import). This is generated from the shared Python model template, so the fix belongs in the template (modules/openapi-generator/src/main/resources/python/model.mustache), not by hand-editing this sample. Merge Optional into the first import.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/openapi3/client/petstore/python-httpx2-sync/petstore_api/models/file.py, line 22:

<comment>`Optional` is imported twice (`from typing import Any, ClassVar, Dict, List, Optional` then `from typing import Optional, Set`), which flake8/ruff flag as F811 (redefinition of unused import). This is generated from the shared Python model template, so the fix belongs in the template (`modules/openapi-generator/src/main/resources/python/model.mustache`), not by hand-editing this sample. Merge `Optional` into the first import.</comment>

<file context>
@@ -0,0 +1,88 @@
+
+from pydantic import BaseModel, ConfigDict, Field, StrictStr
+from typing import Any, ClassVar, Dict, List, Optional
+from typing import Optional, Set
+from typing_extensions import Self
+from pydantic_core import to_jsonable_python
</file context>

@wing328

wing328 commented Sep 20, 2026

Copy link
Copy Markdown
Member

thanks for the PR to add HTTPX2 support

cc @cbornet (2017/09) @tomplus (2018/10) @krjakbrjak (2023/02) @fa0311 (2023/10)

reuse the shared pyproject.mustache for HTTPX2
add HTTPX2 dependencies to the common Python template
use PEP 735 dependency groups for modern packaging
retain legacy Poetry 1 dev-dependencies support
remove HTTPX2-specific pyproject metadata handling
update packaging tests for Python client libraries
regenerate affected Python samples

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

4 issues found across 25 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java">

<violation number="1" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java:419">
P2: When an OAS 3.1 license provides an SPDX identifier, this selection makes HTTPX2 emit the shared template's `license = { text = ... }` instead of PEP 639 license-expression metadata. Preserve HTTPX2's license-expression rendering in the shared template or keep the HTTPX2-specific template.</violation>

<violation number="2" location="modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java:419">
P2: When an HTTPX2 user supplies metadata containing quotes or control characters, this line selects a template that inserts `licenseInfo` without TOML escaping. Preserve the HTTPX2 metadata escaping or escape these values before rendering.</violation>
</file>

<file name="modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientCodegenTest.java">

<violation number="1" location="modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientCodegenTest.java:67">
P2: In `testPythonPackagingModes`, the rows where `poetry1` is the String `"false"` (urllib3/httpx/asyncio) will not produce the asserted output. The test derives `legacy` with `Boolean.parseBoolean(String.valueOf(poetry1))`, but the codegen only normalizes `poetry1` for the httpx2 library (`convertPropertyToBooleanAndWriteBack` under the `"httpx2".equals(getLibrary())` guard in PythonClientCodegen.processOpts). For urllib3/httpx/asyncio the raw String `"false"` flows directly into the `{{#poetry1}}`/`{{^poetry1}}` sections of pyproject.mustache, where jmustache treats any non-empty string as truthy, so `[tool.poetry]` is emitted and the non-legacy assertions (`assertFileContains(pyproject, "[project]", "[dependency-groups]")`, `Assert.assertFalse(content.contains("[tool.poetry"))`) fail for those rows. Coerce the stored value to a Boolean, matching the httpx2 branch, or add the same `poetry1` normalization for every library so the documented `poetry1=false` CLI option works for existing libraries too.</violation>
</file>

<file name="samples/openapi3/client/petstore/python/pyproject.toml">

<violation number="1" location="samples/openapi3/client/petstore/python/pyproject.toml:25">
P3: This delta replaces the `[tool.poetry]` table (with `requires-poetry = ">=2.0"`) and `[tool.poetry.group.dev.dependencies]` with PEP 735 `[dependency-groups]`, silently raising the effective minimum Poetry version. Poetry 2.0.x satisfied the removed `>=2.0` guard and installed the dev tools, but it predates `[dependency-groups]` support (added in Poetry 2.1.0); on Poetry 2.0.x `poetry install` now skips pytest/mypy/flake8 without any error, so `poetry run pytest` fails with a confusing missing-command error instead of a clear version message. The requirement is only documented in prose in README.md ("Poetry 2.2 or newer") and is no longer machine-enforced. Consider keeping a minimal `[tool.poetry]` block that only carries `requires-poetry = ">=2.2"` so incompatible Poetry versions fail fast and explicitly.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@@ -227,7 +227,7 @@ public PythonClientCodegen() {
cliOptions.add(new CliOption(POETRY1_FALLBACK, "Fallback to formatting pyproject.toml to Poetry 1.x format."));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: When an OAS 3.1 license provides an SPDX identifier, this selection makes HTTPX2 emit the shared template's license = { text = ... } instead of PEP 639 license-expression metadata. Preserve HTTPX2's license-expression rendering in the shared template or keep the HTTPX2-specific template.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java, line 419:

<comment>When an OAS 3.1 license provides an SPDX identifier, this selection makes HTTPX2 emit the shared template's `license = { text = ... }` instead of PEP 639 license-expression metadata. Preserve HTTPX2's license-expression rendering in the shared template or keep the HTTPX2-specific template.</comment>

<file context>
@@ -416,7 +416,7 @@ && convertPropertyToBooleanAndWriteBack(POETRY1_FALLBACK)) {
             supportingFiles.add(new SupportingFile("gitlab-ci.mustache", "", ".gitlab-ci.yml"));
             supportingFiles.add(new SupportingFile("setup.mustache", "", "setup.py"));
-            supportingFiles.add(new SupportingFile("httpx2".equals(getLibrary()) ? "httpx2/pyproject.mustache" : "pyproject.mustache", "", "pyproject.toml"));
+            supportingFiles.add(new SupportingFile("pyproject.mustache", "", "pyproject.toml"));
             supportingFiles.add(new SupportingFile("py.typed.mustache", packagePath(), "py.typed"));
         }
</file context>

Comment thread samples/openapi3/client/petstore/python-httpx2-sync/pyproject.toml
Comment on lines +67 to +68
if (poetry1 != null) {
codegen.additionalProperties().put("poetry1", poetry1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: In testPythonPackagingModes, the rows where poetry1 is the String "false" (urllib3/httpx/asyncio) will not produce the asserted output. The test derives legacy with Boolean.parseBoolean(String.valueOf(poetry1)), but the codegen only normalizes poetry1 for the httpx2 library (convertPropertyToBooleanAndWriteBack under the "httpx2".equals(getLibrary()) guard in PythonClientCodegen.processOpts). For urllib3/httpx/asyncio the raw String "false" flows directly into the {{#poetry1}}/{{^poetry1}} sections of pyproject.mustache, where jmustache treats any non-empty string as truthy, so [tool.poetry] is emitted and the non-legacy assertions (assertFileContains(pyproject, "[project]", "[dependency-groups]"), Assert.assertFalse(content.contains("[tool.poetry"))) fail for those rows. Coerce the stored value to a Boolean, matching the httpx2 branch, or add the same poetry1 normalization for every library so the documented poetry1=false CLI option works for existing libraries too.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientCodegenTest.java, line 67:

<comment>In `testPythonPackagingModes`, the rows where `poetry1` is the String `"false"` (urllib3/httpx/asyncio) will not produce the asserted output. The test derives `legacy` with `Boolean.parseBoolean(String.valueOf(poetry1))`, but the codegen only normalizes `poetry1` for the httpx2 library (`convertPropertyToBooleanAndWriteBack` under the `"httpx2".equals(getLibrary())` guard in PythonClientCodegen.processOpts). For urllib3/httpx/asyncio the raw String `"false"` flows directly into the `{{#poetry1}}`/`{{^poetry1}}` sections of pyproject.mustache, where jmustache treats any non-empty string as truthy, so `[tool.poetry]` is emitted and the non-legacy assertions (`assertFileContains(pyproject, "[project]", "[dependency-groups]")`, `Assert.assertFalse(content.contains("[tool.poetry"))`) fail for those rows. Coerce the stored value to a Boolean, matching the httpx2 branch, or add the same `poetry1` normalization for every library so the documented `poetry1=false` CLI option works for existing libraries too.</comment>

<file context>
@@ -49,6 +49,38 @@
+    public void testPythonPackagingModes(String library, Object poetry1) throws IOException {
+        PythonClientCodegen codegen = new PythonClientCodegen();
+        codegen.setLibrary(library);
+        if (poetry1 != null) {
+            codegen.additionalProperties().put("poetry1", poetry1);
+        }
</file context>
Suggested change
if (poetry1 != null) {
codegen.additionalProperties().put("poetry1", poetry1);
if (poetry1 != null) {
codegen.additionalProperties().put("poetry1", Boolean.parseBoolean(String.valueOf(poetry1)));
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fix on this commit c57ebbf

@@ -227,7 +227,7 @@ public PythonClientCodegen() {
cliOptions.add(new CliOption(POETRY1_FALLBACK, "Fallback to formatting pyproject.toml to Poetry 1.x format."));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: When an HTTPX2 user supplies metadata containing quotes or control characters, this line selects a template that inserts licenseInfo without TOML escaping. Preserve the HTTPX2 metadata escaping or escape these values before rendering.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java, line 419:

<comment>When an HTTPX2 user supplies metadata containing quotes or control characters, this line selects a template that inserts `licenseInfo` without TOML escaping. Preserve the HTTPX2 metadata escaping or escape these values before rendering.</comment>

<file context>
@@ -416,7 +416,7 @@ && convertPropertyToBooleanAndWriteBack(POETRY1_FALLBACK)) {
             supportingFiles.add(new SupportingFile("gitlab-ci.mustache", "", ".gitlab-ci.yml"));
             supportingFiles.add(new SupportingFile("setup.mustache", "", "setup.py"));
-            supportingFiles.add(new SupportingFile("httpx2".equals(getLibrary()) ? "httpx2/pyproject.mustache" : "pyproject.mustache", "", "pyproject.toml"));
+            supportingFiles.add(new SupportingFile("pyproject.mustache", "", "pyproject.toml"));
             supportingFiles.add(new SupportingFile("py.typed.mustache", packagePath(), "py.typed"));
         }
</file context>

flake8 = ">= 4.0.0"
types-python-dateutil = ">= 2.8.19.14"
mypy = ">= 1.5"
[dependency-groups]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: This delta replaces the [tool.poetry] table (with requires-poetry = ">=2.0") and [tool.poetry.group.dev.dependencies] with PEP 735 [dependency-groups], silently raising the effective minimum Poetry version. Poetry 2.0.x satisfied the removed >=2.0 guard and installed the dev tools, but it predates [dependency-groups] support (added in Poetry 2.1.0); on Poetry 2.0.x poetry install now skips pytest/mypy/flake8 without any error, so poetry run pytest fails with a confusing missing-command error instead of a clear version message. The requirement is only documented in prose in README.md ("Poetry 2.2 or newer") and is no longer machine-enforced. Consider keeping a minimal [tool.poetry] block that only carries requires-poetry = ">=2.2" so incompatible Poetry versions fail fast and explicitly.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/openapi3/client/petstore/python/pyproject.toml, line 25:

<comment>This delta replaces the `[tool.poetry]` table (with `requires-poetry = ">=2.0"`) and `[tool.poetry.group.dev.dependencies]` with PEP 735 `[dependency-groups]`, silently raising the effective minimum Poetry version. Poetry 2.0.x satisfied the removed `>=2.0` guard and installed the dev tools, but it predates `[dependency-groups]` support (added in Poetry 2.1.0); on Poetry 2.0.x `poetry install` now skips pytest/mypy/flake8 without any error, so `poetry run pytest` fails with a confusing missing-command error instead of a clear version message. The requirement is only documented in prose in README.md ("Poetry 2.2 or newer") and is no longer machine-enforced. Consider keeping a minimal `[tool.poetry]` block that only carries `requires-poetry = ">=2.2"` so incompatible Poetry versions fail fast and explicitly.</comment>

<file context>
@@ -22,16 +22,15 @@ dependencies = [
-flake8 = ">= 4.0.0"
-types-python-dateutil = ">= 2.8.19.14"
-mypy = ">= 1.5"
+[dependency-groups]
+dev = [
+  "pytest>=9.0.3",
</file context>
Suggested change
[dependency-groups]
[tool.poetry]
requires-poetry = ">=2.2"
[dependency-groups]

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm not sure this change is necessary. Poetry 2.1 [1], released on February 15, 2025, already supports this behavior.

Since we're currently using Poetry 2.5, I don't think we need to add special handling for older Poetry versions unless backward compatibility is a requirement.

[1] https://python-poetry.org/blog/announcing-poetry-2.1.0/

* allow `poetry1=true` when using the HTTPX2 library
* include HTTPX2 in Python packaging mode tests
* verify HTTPX2 dependency syntax for legacy and modern packaging
* remove the obsolete legacy Poetry rejection test
* regenerate HTTPX2 samples without duplicate dependencies

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 5 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

* remove the outdated note that HTTPX2 does not support `poetry1=true`
* regenerate HTTPX2 README samples
@Bernardoow

Copy link
Copy Markdown
Author

Hey, @wing328 good morning!

Sorry about that. I hadn’t run the command to regenerate the Python samples, which caused this step to fail.

I’ve run it now and pushed the fix in 7ad5928.

Have an excellent and blessed week!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[REQ] Feature Request Description

2 participants