Skip to content

Commit

Permalink
feat(python): switch Python spec from poetry to uv
Browse files Browse the repository at this point in the history
  • Loading branch information
ahal committed Aug 28, 2024
1 parent 89aecca commit 0e7e3a1
Show file tree
Hide file tree
Showing 12 changed files with 60 additions and 99 deletions.
11 changes: 5 additions & 6 deletions STANDARD.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,11 +281,11 @@ standards.

### Packaging

Projects should use [Poetry] to manage dependencies, run builds and publish
packages. Running `poetry init` should be sufficient to generate the initial
Projects should use [uv] to manage dependencies, virtualenvs and publish
packages. Running `uv init` should be sufficient to generate the initial
configuration in the top-level `pyproject.toml` file.

[Poetry]: https://python-poetry.org/
[uv]: https://docs.astral.sh/uv/

### Testing

Expand Down Expand Up @@ -467,15 +467,14 @@ type-check:
cwd: '{checkout}'
cache-dotcache: true
command: >-
poetry install --only main --only type &&
poetry run pyright
uv run pyright
```

While it's possible to run as a [pre-commit hook], this method isn't
recommended as Pyright needs to run in an environment where the project's
dependencies are installed. This means either the dependencies need to be
listed a second time in `pre-commit-config.yaml`, or Pyright needs to be
explicitly told about Poetry's virtualenv (which varies from person to person
explicitly told about uv's virtualenv (which varies from person to person
and shouldn't be committed in the config file).

[Pyright]: https://github.com/Microsoft/pyright
Expand Down
37 changes: 22 additions & 15 deletions reps/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,38 +86,45 @@ def merge_pre_commit(items: CookiecutterContext):


@hook("post-gen-py")
def add_poetry_dependencies(items: CookiecutterContext):
def add_uv_dependencies(items: CookiecutterContext):
# Build constraints to ensure we don't try to add versions
# that are incompatible with the minimum Python.
min_python = items["min_python_version"]
constraints = defaultdict(dict)
constraints["coverage"] = {"3.7": "coverage@<7.3.0"}
constraints["tox"] = {"3.7": "tox@<4.9.0"}
constraints["coverage"] = {"3.7": "coverage<7.3.0"}
constraints["tox"] = {"3.7": "tox<4.9.0"}
constraints["sphinx-book-theme"] = {
"3.7": "sphinx-book-theme@<=1.0.1",
"3.8": "sphinx-book-theme@<=1.0.1",
"3.7": "sphinx-book-theme<=1.0.1",
"3.8": "sphinx-book-theme<=1.0.1",
}
constraints["sphinx-autobuild"] = {
"3.7": "sphinx-autobuild@<=2021.3.14",
"3.8": "sphinx-autobuild@<=2021.3.14",
"3.7": "sphinx-autobuild<=2021.3.14",
"3.8": "sphinx-autobuild<=2021.3.14",
}

def build_specifiers(*packages: str) -> Generator[str, None, None]:
for p in packages:
yield constraints[p].get(min_python, p)

run(
["poetry", "add", "--group=test"]
# Until the pyproject.toml spec and/or uv supports dependency groups, we
# need to add these all to "dev-dependencies"
# See: https://github.com/astral-sh/uv/issues/5632
["uv", "add", "--dev"]
+ list(
build_specifiers("coverage", "pytest", "pytest-mock", "responses", "tox")
build_specifiers(
"coverage",
"pyright",
"pytest",
"pytest-mock",
"responses",
"sphinx<7",
"sphinx-autobuild",
"sphinx-book-theme",
"tox",
)
)
)
run(
["poetry", "add", "--group=docs"]
+ list(build_specifiers("sphinx<7", "sphinx-autobuild", "sphinx-book-theme"))
)

run(["poetry", "add", "--group=type"] + list(build_specifiers("pyright")))


@hook("post-gen-py")
Expand Down
3 changes: 2 additions & 1 deletion reps/templates/python/cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"__project_slug": "{{cookiecutter.project_name|lower|replace(' ', '-')}}",
"__package_name": "{{cookiecutter.project_name|lower|replace(' ', '_')|replace('-', '_')}}",
"short_description": "",
"author": "Mozilla Release Engineering <[email protected]>",
"author_name": "Mozilla Release Engineering",
"author_email": "[email protected]",
"github_slug": "mozilla-releng/{{cookiecutter.__project_slug}}",
"__github_org": "{{cookiecutter.github_slug[:cookiecutter.github_slug.find('/')]}}",
"__github_project": "{{cookiecutter.github_slug[cookiecutter.github_slug.find('/')+1:]}}",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
[tool.poetry]
[project]
name = "{{cookiecutter.__project_slug}}"
version = "0.1.0"
description = "{{cookiecutter.short_description}}"
authors = ["{{cookiecutter.author}}"]
license = "MPL-2.0"
requires-python = ">={{cookiecutter.min_python_version}}"
authors = [
{ name = "{{cookiecutter.author_name}}", email = "{{cookiecutter.author_email}}" }
]
classifiers = [
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
]
readme = "README.md"

[tool.poetry.dependencies]
python = "^{{cookiecutter.min_python_version}}"

[tool.pytest.ini_options]
xfail_strict = true

Expand Down Expand Up @@ -42,5 +44,5 @@ include = ["src/{{cookiecutter.__package_name}}"]
reportUnknownParameterType = "error"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
requires = ["hatchling"]
build-backend = "hatchling.build"
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,4 @@ tasks:
using: run-task
cwd: '{checkout}'
command: >-
poetry install --only test &&
poetry run python taskcluster/scripts/codecov-upload.py
uv run python taskcluster/scripts/codecov-upload.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{%- set pylist -%}
{%- for i in range(cookiecutter.__max_tox_python_version[1:]|int, cookiecutter.__min_tox_python_version[1:]|int - 1, -1) -%}
3.{{i}}
{%- if not loop.last %},{% endif -%}
{%- if not loop.last %} {% endif -%}
{%- endfor -%}
{%- endset -%}
---
Expand All @@ -16,4 +16,4 @@ tasks:
fetch: {}
python:
args:
PYENV_VERSIONS: "{{pylist}}"
PYTHON_VERSIONS: "{{pylist}}"
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,12 @@ tasks:
TOX_PARALLEL_NO_SPINNER: "1"
run:
command: >-
poetry install --only test &&
poetry run tox --parallel
uv run tox --parallel
type-check:
description: "Run pyright type checking against code base"
worker:
max-run-time: 300
run:
command: >-
poetry install --only main --only type &&
poetry run pyright
uv run pyright
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,10 @@ ENV SHELL=/bin/bash \
VOLUME /builds/worker/checkouts
VOLUME /builds/worker/.cache

# pyenv
# %ARG PYENV_VERSIONS
ENV PYENV_ROOT=/builds/worker/.pyenv \
PATH=/builds/worker/.pyenv/bin:/builds/worker/.pyenv/shims:$PATH
# %include taskcluster/scripts/pyenv-setup
ADD topsrcdir/taskcluster/scripts/pyenv-setup /builds/worker/pyenv-setup
RUN /builds/worker/pyenv-setup "$PYENV_VERSIONS"

# %include taskcluster/scripts/poetry-setup
ADD topsrcdir/taskcluster/scripts/poetry-setup /builds/worker/poetry-setup
RUN /builds/worker/poetry-setup
# uv
COPY --from=ghcr.io/astral-sh/uv:0.3.5 /uv /bin/uv
# %ARG PYTHON_VERSIONS
RUN uv python install $PYTHON_VERSIONS

RUN chown -R worker:worker /builds/worker

Expand Down

This file was deleted.

This file was deleted.

19 changes: 8 additions & 11 deletions reps/templates/python/{{cookiecutter.__project_slug}}/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,24 @@
envlist = clean,{{pylist}},report

[testenv]
allowlist_externals = poetry
allowlist_externals = uv
parallel_show_output = true
depends =
{{pylist}}: clean
report: {{pylist}}
commands =
poetry install --with test
poetry run python --version
poetry run coverage run --context={envname} -p -m pytest -vv {posargs}
uv run python --version
uv run coverage run --context={envname} -p -m pytest -vv {posargs}

[testenv:report]
allowlist_externals = poetry
allowlist_externals = uv
passenv = COVERAGE_REPORT_COMMAND
parallel_show_output = true
commands =
poetry install --only test
poetry run coverage combine
poetry run {env:COVERAGE_REPORT_COMMAND:coverage report}
uv run coverage combine
uv run {env:COVERAGE_REPORT_COMMAND:coverage report}

[testenv:clean]
allowlist_externals = poetry
allowlist_externals = uv
commands =
poetry install --only test
poetry run coverage erase
uv run coverage erase
9 changes: 4 additions & 5 deletions test/test_template_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ def test_generated_files(reps_new):
"docs/reference/index.rst",
"docs/tutorials/index.rst",
"pyproject.toml",
"poetry.lock",
f"src/{name}/__init__.py",
"taskcluster/ci/codecov/kind.yml",
"taskcluster/ci/config.yml",
Expand All @@ -38,17 +37,16 @@ def test_generated_files(reps_new):
"taskcluster/requirements.in",
"taskcluster/requirements.txt",
"taskcluster/scripts/codecov-upload.py",
"taskcluster/scripts/pyenv-setup",
"taskcluster/scripts/poetry-setup",
"test/conftest.py",
f"test/test_{name}.py",
"tox.ini",
"uv.lock",
]

project = reps_new(name, "python")

actual = []
ignore = ("__pycache__", ".git/", ".pyc")
ignore = ("__pycache__", ".git/", ".pyc", ".venv")
for path in project.rglob("*"):
if path.is_dir() or any(i in str(path) for i in ignore):
continue
Expand All @@ -66,7 +64,8 @@ def test_generated_files(reps_new):
"__project_slug": "my-package",
"__package_name": "my_package",
"short_description": "",
"author": "Mozilla Release Engineering <[email protected]>",
"author_name": "Mozilla Release Engineering",
"author_email": "[email protected]",
"github_slug": "mozilla-releng/my-package",
"min_python_version": "3.8",
"__min_tox_python_version": "38",
Expand Down

0 comments on commit 0e7e3a1

Please sign in to comment.