Skip to content

Commit fa44ade

Browse files
authored
Support PDM (#920)
A couple of things drive this change—first, PEP621 has been around for a while now and Poetry shows no signs of adopting it, so while I don't want to ditch Poetry support (it's very popular), I do want a way to generate that style of metadata. PDM seems like a good target for that. Second, Poetry has deprecated some plugins we use... or something. I didn't look into it, but we're getting warnings on every project build now. So, seems like time to switch over to something else. Conveniently, this will make the development commands shorter (`poetry run task check` becomes `pdm check`). TODO: - [x] Convert this project to PDM - [x] Support PDM metadata (which is partially [PEP 621](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#writing-pyproject-toml)) - [x] Update release workflows to use PDM as well - [x] Document using PDM metadata in README - [x] Double-check that `py.typed` is included with a generated PDM project - [x] Add snapshot tests for different metadata types and test them as much as possible --------- Co-authored-by: Dylan Anthony <[email protected]>
1 parent ce7b06f commit fa44ade

25 files changed

+819
-952
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
default: minor
3+
---
4+
5+
# Add `--meta=pdm` option for generating PEP621 + PDM metadata
6+
7+
The default metadata is still `--meta=poetry`, which generates a `pyproject.toml` file with Poetry-specific metadata.
8+
This change adds the `--meta=pdm` option which includes [PDM](https://pdm-project.org/latest/)-specific metadata, but also
9+
standard [PEP621](https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#writing-pyproject-toml)
10+
metadata. This may be useful as a starting point for other dependency managers & build tools (like Hatch).

.github/workflows/checks.yml

+22-33
Original file line numberDiff line numberDiff line change
@@ -30,42 +30,36 @@ jobs:
3030
uses: actions/cache@v3
3131
with:
3232
path: .venv
33-
key: ${{ runner.os }}-${{ steps.get_python_version.outputs.python_version }}-dependencies-${{ hashFiles('**/poetry.lock') }}
33+
key: ${{ runner.os }}-${{ steps.get_python_version.outputs.python_version }}-dependencies-${{ hashFiles('**/pdm.lock') }}
3434
restore-keys: |
3535
${{ runner.os }}-${{ steps.get_python_version.outputs.python_version }}-dependencies
36-
- name: Install Poetry
37-
run: pip install poetry
38-
39-
- name: Create Virtual Environment
40-
run: python -m venv .venv
41-
42-
- name: Upgrade pip
43-
run: poetry run python -m pip install --upgrade pip
36+
- name: Install PDM
37+
run: pip install pdm
4438

4539
- name: Install Dependencies
46-
run: poetry install
40+
run: pdm install
4741

4842
- name: Check formatting
49-
run: poetry run ruff format . --check
43+
run: pdm run ruff format . --check
5044

5145
- name: Run safety
52-
run: poetry export -f requirements.txt | poetry run safety check --bare --stdin
46+
run: pdm safety_check
5347

5448
- name: Run mypy
55-
run: poetry run mypy --show-error-codes openapi_python_client
49+
run: pdm mypy --show-error-codes
5650

5751
- name: Lint
58-
run: poetry run ruff check .
52+
run: pdm run ruff check .
5953

6054
- name: Run pytest without coverage
6155
if: matrix.os != 'ubuntu-latest'
62-
run: poetry run pytest tests end_to_end_tests/test_end_to_end.py --basetemp=tests/tmp
56+
run: pdm test
6357
env:
6458
TASKIPY: true
6559

6660
- name: Run pytest with coverage
6761
if: matrix.os == 'ubuntu-latest'
68-
run: poetry run pytest --cov=openapi_python_client --cov-report=term-missing tests end_to_end_tests/test_end_to_end.py --basetemp=tests/tmp
62+
run: pdm test_with_coverage
6963
env:
7064
TASKIPY: true
7165

@@ -147,42 +141,37 @@ jobs:
147141
uses: actions/cache@v3
148142
with:
149143
path: .venv
150-
key: ${{ runner.os }}-${{ steps.get_python_version.outputs.python_version }}-dependencies-${{ hashFiles('**/poetry.lock') }}
144+
key: ${{ runner.os }}-${{ steps.get_python_version.outputs.python_version }}-dependencies-${{ hashFiles('**/pdm.lock') }}
151145
restore-keys: |
152146
${{ runner.os }}-${{ steps.get_python_version.outputs.python_version }}-dependencies
153147
- name: Install dependencies
154148
run: |
155-
pip install poetry
149+
pip install pdm
156150
python -m venv .venv
157-
poetry run python -m pip install --upgrade pip
158-
poetry install
151+
pdm install
159152
- name: Regenerate Integration Client
160153
run: |
161-
poetry run openapi-python-client update --url http://localhost:3000/openapi.json --config integration-tests-config.yaml
154+
pdm run openapi-python-client update --url http://localhost:3000/openapi.json --config integration-tests-config.yaml --meta pdm
162155
- name: Check for any file changes
163156
run: python .github/check_for_changes.py
164157
- name: Cache Generated Client Dependencies
165158
uses: actions/cache@v3
166159
with:
167160
path: integration-tests/.venv
168-
key: ${{ runner.os }}-${{ steps.get_python_version.outputs.python_version }}-integration-dependencies-${{ hashFiles('**/poetry.lock') }}
161+
key: ${{ runner.os }}-${{ steps.get_python_version.outputs.python_version }}-integration-dependencies-${{ hashFiles('**/pdm.lock') }}
169162
restore-keys: |
170163
${{ runner.os }}-${{ steps.get_python_version.outputs.python_version }}-integration-dependencies
171-
- name: Install Integration Dependencies
172-
run: |
173-
cd integration-tests
174-
python -m venv .venv
175-
poetry run python -m pip install --upgrade pip
176-
poetry install
177164
- name: Set httpx version
178165
if: matrix.httpx_version != ''
179166
run: |
180167
cd integration-tests
181-
poetry run pip install httpx==${{ matrix.httpx_version }}
168+
pdm add httpx==${{ matrix.httpx_version }}
169+
- name: Install Integration Dependencies
170+
run: |
171+
cd integration-tests
172+
pdm install
182173
- name: Run Tests
183174
run: |
184175
cd integration-tests
185-
poetry run pytest
186-
poetry run mypy . --strict
187-
188-
176+
pdm run pytest
177+
pdm run mypy . --strict

.github/workflows/release.yml

+7-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ jobs:
99
release:
1010
if: github.head_ref == 'release' && github.event.pull_request.merged == true
1111
runs-on: ubuntu-latest
12+
permissions:
13+
id-token: write
1214
steps:
1315
- uses: actions/[email protected]
1416
with:
@@ -18,10 +20,12 @@ jobs:
1820
uses: knope-dev/[email protected]
1921
with:
2022
version: 0.13.3
21-
- name: Install Poetry
22-
run: pip install --upgrade poetry
23+
- name: Install Hatchling
24+
run: pip install --upgrade hatchling
25+
- name: Build
26+
run: hatchling build
2327
- name: Push to PyPI
24-
run: poetry publish --build -u __token__ -p ${{ secrets.PYPI_TOKEN }}
28+
uses: pypa/[email protected]
2529
- name: Create GitHub Release
2630
run: knope release
2731
env:

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
.pdm-python
12
__pycache__/
23
build/
34
dist/
45
*.egg-info/
56
.pytest_cache/
67

8+
# macOS
9+
.DS_Store
10+
711
# pyenv
812
.python-version
913

CONTRIBUTING.md

+4-5
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,9 @@ To request a feature:
4040

4141
### Setting up a Dev Environment
4242

43-
1. Make sure you have [Poetry](https://python-poetry.org/) installed and up to date.
44-
2. Make sure you have a supported Python version (e.g. 3.8) installed and accessible to Poetry (e.g. with [pyenv](https://github.com/pyenv/pyenv)).
45-
3. Use `poetry install` in the project directory to create a virtual environment with the relevant dependencies.
46-
4. Enter a `poetry shell` to make running commands easier.
43+
1. Make sure you have [PDM](https://pdm-project.org) installed and up to date.
44+
2. Make sure you have a supported Python version (e.g. 3.8) installed.
45+
3. Use `pdm install` in the project directory to create a virtual environment with the relevant dependencies.
4746

4847
### Writing tests
4948

@@ -57,7 +56,7 @@ If you think that some of the added code is not testable (or testing it would ad
5756

5857
#### End-to-end tests
5958

60-
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 `poetry run task e2e`. To regenerate the snapshots, run `poetry run task regen`.
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`.
6160

6261
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:
6362

README.md

+6-5
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ This tool focuses on creating the best developer experience for Python developer
2626

2727
I recommend you install with [pipx](https://pipxproject.github.io/pipx/) so you don't conflict with any other packages you might have: `pipx install openapi-python-client --include-deps`.
2828

29-
> Note the `--include-deps` option which will also make `black` and `ruff` available in your path so that `openapi-python-client` can use them to clean up the generated code.
29+
> Note the `--include-deps` option makes `ruff` available in your path so that `openapi-python-client` can use it to clean up the generated code.
3030
3131
**If you use `pipx run` then the post-generation hooks will not be available unless you install them manually.**
3232

@@ -52,8 +52,6 @@ If you have an `openapi.json` file available on disk, in any CLI invocation you
5252

5353
`openapi-python-client update --url https://my.api.com/openapi.json`
5454

55-
> For more usage details run `openapi-python-client --help` or read [usage](usage.md)
56-
5755
### Using custom templates
5856

5957
This feature leverages Jinja2's [ChoiceLoader](https://jinja.palletsprojects.com/en/2.11.x/api/#jinja2.ChoiceLoader) and [FileSystemLoader](https://jinja.palletsprojects.com/en/2.11.x/api/#jinja2.FileSystemLoader). This means you do _not_ need to customize every template. Simply copy the template(s) you want to customize from [the default template directory](openapi_python_client/templates) to your own custom template directory (file names _must_ match exactly) and pass the template directory through the `custom-template-path` flag to the `generate` and `update` commands. For instance,
@@ -68,14 +66,15 @@ _Be forewarned, this is a beta-level feature in the sense that the API exposed i
6866

6967
## What You Get
7068

71-
1. A `pyproject.toml` file with some basic metadata intended to be used with [Poetry].
69+
1. A `pyproject.toml` file, optionally with [Poetry] metadata (default), [PDM] (with `--meta=pdm`), or only [Ruff] config.
7270
2. A `README.md` you'll most definitely need to update with your project's details
7371
3. A Python module named just like the auto-generated project name (e.g. "my_api_client") which contains:
7472
1. A `client` module which will have both a `Client` class and an `AuthenticatedClient` class. You'll need these
7573
for calling the functions in the `api` module.
7674
2. An `api` module which will contain one module for each tag in your OpenAPI spec, as well as a `default` module
7775
for endpoints without a tag. Each of these modules in turn contains one function for calling each endpoint.
7876
3. A `models` module which has all the classes defined by the various schemas in your OpenAPI spec
77+
4. A `setup.py` file _if_ you use `--meta=setup` (default is `--meta=poetry`)
7978

8079
For a full example you can look at the `end_to_end_tests` directory which has `baseline_openapi_3.0.json` and `baseline_openapi_3.1.yaml` files.
8180
The "golden-record" in that same directory is the generated client from either of those OpenAPI documents.
@@ -137,7 +136,7 @@ package_version_override: 1.2.3
137136

138137
### post_hooks
139138

140-
In the config file, there's an easy way to tell `openapi-python-client` to run additional commands after generation. Here's an example showing the default commands that will run if you don't override them in config:
139+
In the config file, there's an easy way to tell `openapi-python-client` to run additional commands after generation. Here's an example showing the default commands (using [Ruff]) that will run if you don't override them in config:
141140

142141
```yaml
143142
post_hooks:
@@ -159,3 +158,5 @@ By default, the timeout for retrieving the schema file via HTTP is 5 seconds. In
159158

160159
[changelog.md]: CHANGELOG.md
161160
[poetry]: https://python-poetry.org/
161+
[PDM]: https://pdm-project.org/latest/
162+
[Ruff]: https://docs.astral.sh/ruff/

end_to_end_tests/golden-record/pyproject.toml

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22
name = "my-test-api-client"
33
version = "0.1.0"
44
description = "A client library for accessing My Test API"
5-
65
authors = []
7-
86
readme = "README.md"
97
packages = [
108
{include = "my_test_api_client"},
119
]
1210
include = ["CHANGELOG.md", "my_test_api_client/py.typed"]
1311

12+
1413
[tool.poetry.dependencies]
1514
python = "^3.8"
1615
httpx = ">=0.20.0,<0.27.0"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[project]
2+
name = "test-3-1-features-client"
3+
version = "0.1.0"
4+
description = "A client library for accessing Test 3.1 Features"
5+
authors = []
6+
readme = "README.md"
7+
requires-python = ">=3.8,<4.0"
8+
dependencies = [
9+
"httpx>=0.20.0,<0.27.0",
10+
"attrs>=21.3.0",
11+
"python-dateutil>=2.8.0",
12+
]
13+
14+
[tool.pdm]
15+
package-type = "library"
16+
17+
[build-system]
18+
requires = ["pdm-backend"]
19+
build-backend = "pdm.backend"
20+
21+
[tool.ruff]
22+
select = ["F", "I"]
23+
line-length = 120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[tool.poetry]
2+
name = "test-3-1-features-client"
3+
version = "0.1.0"
4+
description = "A client library for accessing Test 3.1 Features"
5+
authors = []
6+
readme = "README.md"
7+
packages = [
8+
{include = "test_3_1_features_client"},
9+
]
10+
include = ["CHANGELOG.md", "test_3_1_features_client/py.typed"]
11+
12+
13+
[tool.poetry.dependencies]
14+
python = "^3.8"
15+
httpx = ">=0.20.0,<0.27.0"
16+
attrs = ">=21.3.0"
17+
python-dateutil = "^2.8.0"
18+
19+
[build-system]
20+
requires = ["poetry-core>=1.0.0"]
21+
build-backend = "poetry.core.masonry.api"
22+
23+
[tool.ruff]
24+
select = ["F", "I"]
25+
line-length = 120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import pathlib
2+
3+
from setuptools import find_packages, setup
4+
5+
here = pathlib.Path(__file__).parent.resolve()
6+
long_description = (here / "README.md").read_text(encoding="utf-8")
7+
8+
setup(
9+
name="test-3-1-features-client",
10+
version="0.1.0",
11+
description="A client library for accessing Test 3.1 Features",
12+
long_description=long_description,
13+
long_description_content_type="text/markdown",
14+
packages=find_packages(),
15+
python_requires=">=3.8, <4",
16+
install_requires=["httpx >= 0.20.0, < 0.27.0", "attrs >= 21.3.0", "python-dateutil >= 2.8.0, < 3"],
17+
package_data={"test_3_1_features_client": ["py.typed"]},
18+
)

end_to_end_tests/regen_golden_record.py

+21
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,26 @@ def regen_golden_record_3_1_features():
5151
output_path.rename(gr_path)
5252

5353

54+
def regen_metadata_snapshots():
55+
runner = CliRunner()
56+
openapi_path = Path(__file__).parent / "3.1_specific.openapi.yaml"
57+
output_path = Path.cwd() / "test-3-1-features-client"
58+
snapshots_dir = Path(__file__).parent / "metadata_snapshots"
59+
60+
for (meta, file, rename_to) in (("setup", "setup.py", "setup.py"), ("pdm", "pyproject.toml", "pdm.pyproject.toml"), ("poetry", "pyproject.toml", "poetry.pyproject.toml")):
61+
shutil.rmtree(output_path, ignore_errors=True)
62+
result = runner.invoke(app, ["generate", f"--path={openapi_path}", f"--meta={meta}"])
63+
64+
if result.stdout:
65+
print(result.stdout)
66+
if result.exception:
67+
raise result.exception
68+
69+
(output_path / file).rename(snapshots_dir / rename_to)
70+
71+
shutil.rmtree(output_path, ignore_errors=True)
72+
73+
5474
def regen_custom_template_golden_record():
5575
runner = CliRunner()
5676
openapi_path = Path(__file__).parent / "baseline_openapi_3.0.json"
@@ -104,4 +124,5 @@ def regen_custom_template_golden_record():
104124
if __name__ == "__main__":
105125
regen_golden_record()
106126
regen_golden_record_3_1_features()
127+
regen_metadata_snapshots()
107128
regen_custom_template_golden_record()

end_to_end_tests/test-3-1-golden-record/pyproject.toml

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22
name = "test-3-1-features-client"
33
version = "0.1.0"
44
description = "A client library for accessing Test 3.1 Features"
5-
65
authors = []
7-
86
readme = "README.md"
97
packages = [
108
{include = "test_3_1_features_client"},
119
]
1210
include = ["CHANGELOG.md", "test_3_1_features_client/py.typed"]
1311

12+
1413
[tool.poetry.dependencies]
1514
python = "^3.8"
1615
httpx = ">=0.20.0,<0.27.0"

0 commit comments

Comments
 (0)