[Python] Add HTTPX2 client library support - #24966
Bernardoow wants to merge 7 commits into
Conversation
- 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.
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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=(), |
There was a problem hiding this comment.
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>
| protected_namespaces=(), | |
| extra="allow", | |
| protected_namespaces=(), |
|
|
||
| _obj = cls.model_validate({ | ||
| "id": obj.get("id"), | ||
| "name": obj.get("name") |
There was a problem hiding this comment.
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>
| # 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()) |
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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>
| from typing import Optional, Set | ||
| from typing_extensions import Self |
There was a problem hiding this comment.
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>
| 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()) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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>
|
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
There was a problem hiding this comment.
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.")); | |||
There was a problem hiding this comment.
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>
| if (poetry1 != null) { | ||
| codegen.additionalProperties().put("poetry1", poetry1); |
There was a problem hiding this comment.
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>
| if (poetry1 != null) { | |
| codegen.additionalProperties().put("poetry1", poetry1); | |
| if (poetry1 != null) { | |
| codegen.additionalProperties().put("poetry1", Boolean.parseBoolean(String.valueOf(poetry1))); | |
| } |
| @@ -227,7 +227,7 @@ public PythonClientCodegen() { | |||
| cliOptions.add(new CliOption(POETRY1_FALLBACK, "Fallback to formatting pyproject.toml to Poetry 1.x format.")); | |||
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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>
| [dependency-groups] | |
| [tool.poetry] | |
| requires-poetry = ">=2.2" | |
| [dependency-groups] |
There was a problem hiding this comment.
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.
* 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
There was a problem hiding this comment.
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
Adds
httpx2as a library option for the existing Python generator, allowing clients to use HTTPX2 without custom templates or manual changes after generation.Closes #24965
Changes
--library httpx2, usinghttpx2.AsyncClient.supportHttpxSync=true.py.typed.Existing Python library defaults and packaging behavior remain unchanged.
Example
Validation
Validated locally with Python 3.13 and HTTPX2 2.13.0:
py.typed, runtime dependencies, and SPDX license metadata.Summary by cubic
Adds
httpx2as a built-in library option for the Python generator, closing #24965.--library httpx2now generates a client usinghttpx2.AsyncClientwithout custom templates. Packaging forhttpx2is unified with the other Python libraries; non-httpx2projects were regenerated with the newpyproject.tomland README format, which now uses PEP 621/735/639 metadata, SPDX licenses, andpy.typed. Legacypoetry1=truekeeps the deprecated[tool.poetry.dev-dependencies]and works forhttpx2as well.New Features
supportHttpxSync=trueadds blocking_syncvariants alongside async methods forhttpxandhttpx2.py.typed; legacypoetry1=trueretains deprecated dev-dependencies and is supported forhttpx2.Migration
httpx2>=2.13.0,<3.Written for commit 7ad5928. Summary will update on new commits.