Skip to content

Commit 8ebcf1e

Browse files
authored
Merge branch 'main' into enumerate-duplicate-model-names
2 parents 3320062 + c0f76a3 commit 8ebcf1e

File tree

155 files changed

+3059
-2049
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

155 files changed

+3059
-2049
lines changed

.github/workflows/checks.yml

+6-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
steps:
1818
- uses: actions/[email protected]
1919
- name: Set up Python
20-
uses: actions/setup-python@v5.4.0
20+
uses: actions/setup-python@v5.5.0
2121
with:
2222
python-version: ${{ matrix.python }}
2323

@@ -60,7 +60,7 @@ jobs:
6060
if: matrix.os == 'ubuntu-latest'
6161

6262
- name: Store coverage report
63-
uses: actions/[email protected].1
63+
uses: actions/[email protected].2
6464
if: matrix.os == 'ubuntu-latest'
6565
with:
6666
name: coverage-${{ matrix.python }}
@@ -76,7 +76,7 @@ jobs:
7676
steps:
7777
- uses: actions/[email protected]
7878
- name: Set up Python
79-
uses: actions/setup-python@v5.4.0
79+
uses: actions/setup-python@v5.5.0
8080
with:
8181
python-version: "3.9"
8282

@@ -117,7 +117,7 @@ jobs:
117117
with:
118118
python-version: "3.12"
119119
- name: Download coverage reports
120-
uses: actions/download-artifact@v4.1.9
120+
uses: actions/download-artifact@v4.2.1
121121
with:
122122
merge-multiple: true
123123

@@ -142,7 +142,7 @@ jobs:
142142
.venv/bin/python -m coverage report --fail-under=100
143143
144144
- name: Upload HTML report if check failed.
145-
uses: actions/[email protected].1
145+
uses: actions/[email protected].2
146146
with:
147147
name: html-report
148148
path: htmlcov
@@ -164,7 +164,7 @@ jobs:
164164
steps:
165165
- uses: actions/[email protected]
166166
- name: Set up Python
167-
uses: actions/setup-python@v5.4.0
167+
uses: actions/setup-python@v5.5.0
168168
with:
169169
python-version: "3.9"
170170
- name: Get Python Version

CHANGELOG.md

+71
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,77 @@ Programmatic usage of this project (e.g., importing it as a Python module) and t
1313

1414
The 0.x prefix used in versions for this project is to indicate that breaking changes are expected frequently (several times a year). Breaking changes will increment the minor number, all other changes will increment the patch number. You can track the progress toward 1.0 [here](https://github.com/openapi-generators/openapi-python-client/projects/2).
1515

16+
## 0.24.3 (2025-03-31)
17+
18+
### Features
19+
20+
#### Adding support for named integer enums
21+
22+
##1214 by @barrybarrette
23+
24+
Adding support for named integer enums via an optional extension, `x-enum-varnames`.
25+
26+
This extension is added to the schema inline with the `enum` definition:
27+
```
28+
"MyEnum": {
29+
"enum": [
30+
0,
31+
1,
32+
2,
33+
3,
34+
4,
35+
5,
36+
6,
37+
99
38+
],
39+
"type": "integer",
40+
"format": "int32",
41+
"x-enum-varnames": [
42+
"Deinstalled",
43+
"Installed",
44+
"Upcoming_Site",
45+
"Lab_Site",
46+
"Pending_Deinstall",
47+
"Suspended",
48+
"Install_In_Progress",
49+
"Unknown"
50+
]
51+
}
52+
```
53+
54+
The result:
55+
![image](https://github.com/user-attachments/assets/780880b3-2f1f-49be-823b-f9abb713a3e1)
56+
57+
## 0.24.2 (2025-03-22)
58+
59+
### Fixes
60+
61+
#### Make lists of models and enums work correctly in custom templates
62+
63+
Lists of model and enum classes should be available to custom templates via the Jinja
64+
variables `openapi.models` and `openapi.enums`, but these were being passed in a way that made
65+
them always appear empty. This has been fixed so a custom template can now iterate over them.
66+
67+
Closes #1188.
68+
69+
## 0.24.1 (2025-03-15)
70+
71+
### Features
72+
73+
- allow Ruff to 0.10 (#1220)
74+
- allow Ruff 0.11 (#1222)
75+
- Allow any `Mapping` in generated `from_dict` functions (#1211)
76+
77+
### Fixes
78+
79+
#### Always parse `$ref` as a reference
80+
81+
If additional attributes were included with a `$ref` (for example `title` or `description`), the property could be
82+
interpreted as a new type instead of a reference, usually resulting in `Any` in the generated code.
83+
Now, any sibling properties to `$ref` will properly be ignored, as per the OpenAPI specification.
84+
85+
Thanks @nkrishnaswami!
86+
1687
## 0.24.0 (2025-03-03)
1788

1889
### Breaking Changes

CONTRIBUTING.md

+22-8
Original file line numberDiff line numberDiff line change
@@ -50,26 +50,40 @@ All changes must be tested, I recommend writing the test first, then writing the
5050

5151
If you think that some of the added code is not testable (or testing it would add little value), mention that in your PR and we can discuss it.
5252

53-
1. If you're adding support for a new OpenAPI feature or covering a new edge case, add an [end-to-end test](#end-to-end-tests)
54-
2. If you're modifying the way an existing feature works, make sure an existing test generates the _old_ code in `end_to_end_tests/golden-record`. You'll use this to check for the new code once your changes are complete.
55-
3. If you're improving an error or adding a new error, add a [unit test](#unit-tests)
53+
1. If you're adding support for a new OpenAPI feature or covering a new edge case, add [functional tests](#functional-tests), and optionally an [end-to-end snapshot test](#end-to-end-snapshot-tests).
54+
2. If you're modifying the way an existing feature works, make sure functional tests cover this case. Existing end-to-end snapshot tests might also be affected if you have changed what generated model/endpoint code looks like.
55+
3. If you're improving error handling or adding a new error, add [functional tests](#functional-tests).
56+
4. For tests of low-level pieces of code that are fairly self-contained, and not tightly coupled to other internal implementation details, you can use regular [unit tests](#unit-tests).
5657

57-
#### End-to-end tests
58+
#### End-to-end snapshot tests
5859

59-
This project aims to have all "happy paths" (types of code which _can_ be generated) covered by end to end tests (snapshot tests). In order to check code changes against the previous set of snapshots (called a "golden record" here), you can run `pdm e2e`. To regenerate the snapshots, run `pdm regen`.
60+
This project aims to have all "happy paths" (types of code which _can_ be generated) covered by end-to-end tests. There are two types of these: snapshot tests, and functional tests.
6061

61-
There are 4 types of snapshots generated right now, you may have to update only some or all of these depending on the changes you're making. Within the `end_to_end_tets` directory:
62+
Snapshot tests verify that the generated code is identical to a previously-committed set of snapshots (called a "golden record" here). They are basically regression tests to catch any unintended changes in the generator output.
63+
64+
In order to check code changes against the previous set of snapshots (called a "golden record" here), you can run `pdm e2e`. To regenerate the snapshots, run `pdm regen`.
65+
66+
There are 4 types of snapshots generated right now, you may have to update only some or all of these depending on the changes you're making. Within the `end_to_end_tests` directory:
6267

6368
1. `baseline_openapi_3.0.json` creates `golden-record` for testing OpenAPI 3.0 features
6469
2. `baseline_openapi_3.1.yaml` is checked against `golden-record` for testing OpenAPI 3.1 features (and ensuring consistency with 3.0)
6570
3. `test_custom_templates` are used with `baseline_openapi_3.0.json` to generate `custom-templates-golden-record` for testing custom templates
6671
4. `3.1_specific.openapi.yaml` is used to generate `test-3-1-golden-record` and test 3.1-specific features (things which do not have a 3.0 equivalent)
6772

73+
#### Functional tests
74+
75+
These are black-box tests that verify the runtime behavior of generated code, as well as the generator's validation behavior. They are also end-to-end tests, since they run the generator as a shell command.
76+
77+
This can sometimes identify issues with error handling, validation logic, module imports, etc., that might be harder to diagnose via the snapshot tests, especially during development of a new feature. For instance, they can verify that JSON data is correctly decoded into model class attributes, or that the generator will emit an appropriate warning or error for an invalid spec.
78+
79+
See [`end_to_end_tests/functional_tests`](./end_to_end_tests/functional_tests).
80+
6881
#### Unit tests
6982

70-
> **NOTE**: Several older-style unit tests using mocks exist in this project. These should be phased out rather than updated, as the tests are brittle and difficult to maintain. Only error cases should be tests with unit tests going forward.
83+
These include:
7184

72-
In some cases, we need to test things which cannot be generated—like validating that errors are caught and handled correctly. These should be tested via unit tests in the `tests` directory, using the `pytest` framework.
85+
* Regular unit tests of basic pieces of fairly self-contained low-level functionality, such as helper functions. These are implemented in the `tests` directory, using the `pytest` framework.
86+
* Older-style unit tests of low-level functions like `property_from_data` that have complex behavior. These are brittle and difficult to maintain, and should not be used going forward. Instead, they should be migrated to functional tests.
7387

7488
### Creating a Pull Request
7589

README.md

+33
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,39 @@ content_type_overrides:
198198
application/zip: application/octet-stream
199199
```
200200

201+
## Supported Extensions
202+
203+
### x-enum-varnames
204+
205+
This extension has been adopted by similar projects such as [OpenAPI Tools](https://github.com/OpenAPITools/openapi-generator/pull/917).
206+
It is intended to provide user-friendly names for integer Enum members that get generated.
207+
It is critical that the length of the array matches that of the enum values.
208+
209+
```
210+
"Colors": {
211+
"type": "integer",
212+
"format": "int32",
213+
"enum": [
214+
0,
215+
1,
216+
2
217+
],
218+
"x-enum-varnames": [
219+
"Red",
220+
"Green",
221+
"Blue"
222+
]
223+
}
224+
```
225+
226+
Results in:
227+
```
228+
class Color(IntEnum):
229+
RED = 0
230+
GREEN = 1
231+
BLUE = 2
232+
```
233+
201234
[changelog.md]: CHANGELOG.md
202235
[poetry]: https://python-poetry.org/
203236
[PDM]: https://pdm-project.org/latest/

end_to_end_tests/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
11
""" Generate a complete client and verify that it is correct """
2+
import pytest
3+
4+
pytest.register_assert_rewrite("end_to_end_tests.end_to_end_test_helpers")
5+
pytest.register_assert_rewrite("end_to_end_tests.functional_tests.helpers")

end_to_end_tests/baseline_openapi_3.0.json

+10-1
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,16 @@
394394
"content": {
395395
"multipart/form-data": {
396396
"schema": {
397-
"$ref": "#/components/schemas/Body_upload_file_tests_upload_post"
397+
"$ref": "#/components/schemas/Body_upload_file_tests_upload_post",
398+
"title": "Body_upload_file_tests_upload_post",
399+
"required": [
400+
"some_file",
401+
"some_object",
402+
"some_nullable_object",
403+
"some_required_number"
404+
],
405+
"properties": {
406+
}
398407
}
399408
}
400409
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Testing that we can access model-related information via Jinja variables.
2+
3+
# To avoid having to update this file in the golden record every time the test specs are changed,
4+
# we won't include all the classes in this output - we'll just look for one of them.
5+
6+
# Using "alls"
7+
# AModel
8+
9+
# Using "imports"
10+
# from .a_model import AModel
11+
12+
# Using "openapi.models"
13+
# AModel (a_model)
14+
15+
# Using "openapi.enums"
16+
# AnEnum (an_enum)

end_to_end_tests/docstrings-on-attributes-golden-record/my_test_api_client/models/model_with_description.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from collections.abc import Mapping
12
from typing import Any, TypeVar, Union
23

34
from attrs import define as _attrs_define
@@ -43,8 +44,8 @@ def to_dict(self) -> dict[str, Any]:
4344
return field_dict
4445

4546
@classmethod
46-
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
47-
d = src_dict.copy()
47+
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
48+
d = dict(src_dict)
4849
prop_with_no_desc = d.pop("propWithNoDesc", UNSET)
4950

5051
prop_with_desc = d.pop("propWithDesc", UNSET)

end_to_end_tests/docstrings-on-attributes-golden-record/my_test_api_client/models/model_with_no_description.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from collections.abc import Mapping
12
from typing import Any, TypeVar, Union
23

34
from attrs import define as _attrs_define
@@ -31,8 +32,8 @@ def to_dict(self) -> dict[str, Any]:
3132
return field_dict
3233

3334
@classmethod
34-
def from_dict(cls: type[T], src_dict: dict[str, Any]) -> T:
35-
d = src_dict.copy()
35+
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
36+
d = dict(src_dict)
3637
prop_with_no_desc = d.pop("propWithNoDesc", UNSET)
3738

3839
prop_with_desc = d.pop("propWithDesc", UNSET)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
## The `functional_tests` module
2+
3+
These are end-to-end tests which run the client generator against many small API documents that are specific to various test cases.
4+
5+
Rather than testing low-level implementation details (like the unit tests in `tests`), or making assertions about the exact content of the generated code (like the "golden record"-based end-to-end tests), these treat both the generator and the generated code as black boxes and make assertions about their behavior.
6+
7+
The tests are in two submodules:
8+
9+
# `generated_code_execution`
10+
11+
These tests use valid API specs, and after running the generator, they _import and execute_ pieces of the generated code to verify that it actually works at runtime.
12+
13+
Each test class follows this pattern:
14+
15+
- Use the decorator `@with_generated_client_fixture`, providing an inline API spec (JSON or YAML) that contains whatever schemas/paths/etc. are relevant to this test class.
16+
- The spec can omit the `openapi:`, `info:`, and `paths:`, blocks, unless those are relevant to the test.
17+
- The decorator creates a temporary file for the inline spec and a temporary directory for the generated code, and runs the client generator.
18+
- It creates a `GeneratedClientContext` object (defined in `end_to_end_test_helpers.py`) to keep track of things like the location of the generated code and the output of the generator command.
19+
- This object is injected into the test class as a fixture called `generated_client`, although most tests will not need to reference the fixture directly.
20+
- `sys.path` is temporarily changed, for the scope of this test class, to allow imports from the generated code.
21+
- Use the decorator `@with_generated_code_imports` or `@with_generated_code_import` to make classes or functions from the generated code available to the tests.
22+
- `@with_generated_code_imports(".models.MyModel1", ".models.MyModel2)` would execute `from [package name].models import MyModel1, MyModel2` and inject the imported classes into the test class as fixtures called `MyModel1` and `MyModel2`.
23+
- `@with_generated_code_import(".api.my_operation.sync", alias="endpoint_method")` would execute `from [package name].api.my_operation import sync`, but the fixture would be named `endpoint_method`.
24+
- After the test class finishes, these imports are discarded.
25+
26+
Example:
27+
28+
```python
29+
@with_generated_client_fixture(
30+
"""
31+
components:
32+
schemas:
33+
MyModel:
34+
type: object
35+
properties:
36+
stringProp: {"type": "string"}
37+
""")
38+
@with_generated_code_import(".models.MyModel")
39+
class TestSimpleJsonObject:
40+
def test_encoding(self, MyModel):
41+
instance = MyModel(string_prop="abc")
42+
assert instance.to_dict() == {"stringProp": "abc"}
43+
```
44+
45+
# `generator_failure_cases`
46+
47+
These run the generator with an invalid API spec and make assertions about the warning/error output. Some of these invalid conditions are expected to only produce warnings about the affected schemas, while others are expected to produce fatal errors that terminate the generator.
48+
49+
For warning conditions, each test class uses `@with_generated_client_fixture` as above, then uses `assert_bad_schema` to parse the output and check for a specific warning message for a specific schema name.
50+
51+
```python
52+
@with_generated_client_fixture(
53+
"""
54+
components:
55+
schemas:
56+
MyModel:
57+
# some kind of invalid schema
58+
""")
59+
class TestBadSchema:
60+
def test_encoding(self, generated_client):
61+
assert_bad_schema(generated_client, "MyModel", "some expected warning text")
62+
```
63+
64+
Or, for fatal error conditions:
65+
66+
- Call `inline_spec_should_fail`, providing an inline API spec (JSON or YAML).
67+
68+
```python
69+
class TestBadSpec:
70+
def test_some_spec_error(self):
71+
result = inline_spec_should_fail("""
72+
# some kind of invalid spec
73+
""")
74+
assert "some expected error text" in result.output
75+
```

0 commit comments

Comments
 (0)