From 9151013c6e46a3d80c4dd5e79832e013cd334334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Rzepecki?= Date: Wed, 28 Jan 2026 18:31:50 +0100 Subject: [PATCH] chore: Migrate from Poetry to uv and Hatchling This commit migrates the project's dependency management and build system from Poetry to uv and Hatchling. This change significantly speeds up dependency installation and testing, providing a more efficient development workflow. Key changes include: - `pyproject.toml` is now configured for Hatchling and uv. - GitHub Actions workflows have been updated to use `astral-sh/setup-uv`. - `tox.ini` is reconfigured to use `tox-uv` for test environments. - `README.md` has been updated with new setup and usage instructions. - Obsolete Poetry and pyenv files (`tool-versions.example`, etc.) have been removed. --- .github/actions/setup/action.yml | 40 +--- .github/workflows/release.yml | 5 +- .gitignore | 2 + .releaserc.yml | 2 +- README.md | 84 ++++----- ...{build_with_poetry.sh => build_with_uv.sh} | 6 +- pylintrc | 1 - pyproject.toml | 171 ++++++++++-------- requirements-dev.txt | 10 - requirements-test.txt | 3 - tool-versions.example | 1 - tox.ini | 35 ++-- 12 files changed, 173 insertions(+), 187 deletions(-) rename ci/scripts/{build_with_poetry.sh => build_with_uv.sh} (81%) delete mode 100644 requirements-dev.txt delete mode 100644 requirements-test.txt delete mode 100644 tool-versions.example diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index daba8580..f00a0fcc 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -1,4 +1,4 @@ -name: Setup base (python, pip cache, tox) +name: Setup base (python, uv, tox) inputs: python: description: "Python version to use" @@ -11,41 +11,17 @@ outputs: runs: using: "composite" steps: - - name: pip cache - uses: actions/cache@v4 - with: - path: | - ~/.cache/pip - key: ${{ runner.os }}-pip-${{ inputs.python }} - - - name: Cargo cache - uses: actions/cache/@v4 - with: - path: "~/.cargo" - key: ${{ runner.os }}-cargo - - - name: Poetry cache - uses: actions/cache/@v4 - with: - path: "~/.cache/pypoetry" - key: ${{ runner.os }}-poetry-${{ inputs.python }} - restore-keys: | - ${{ runner.os }}-poetry- - - uses: actions/setup-python@v6 id: python with: python-version: ${{ inputs.python }} - - name: upgrade pip and install tox + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + + - name: Install tox and tox-uv shell: bash run: | - python -m pip -q install --upgrade pip "setuptools==65.6.2" - pip -q install "tox<4" tox-gh-actions - - - name: install Rust and Poetry - shell: bash - run : | - curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable --profile minimal - source "$HOME/.cargo/env" - pip -q install poetry>=1.2.0 + uv tool install tox --with tox-uv --with tox-gh-actions diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2928b1af..2330164d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -70,7 +70,10 @@ jobs: steps: - uses: actions/checkout@v5 - uses: ./.github/actions/setup-semantic-release # node+semantic-release - - uses: ./.github/actions/setup # poetry + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true - id: semantic-release # branch policies defined in .releaserc env: GIT_AUTHOR_NAME: appland-release diff --git a/.gitignore b/.gitignore index 4473e002..fac985bc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .tool-versions poetry.lock +uv.lock __pycache__/ *.py[cod] @@ -22,3 +23,4 @@ htmlcov/ /ruff.toml appmap.log +*.sqlite3 diff --git a/.releaserc.yml b/.releaserc.yml index b5efcfc6..47b2e100 100644 --- a/.releaserc.yml +++ b/.releaserc.yml @@ -42,7 +42,7 @@ plugins: - pyproject.toml - - '@semantic-release/exec' - prepareCmd: | - /bin/bash ./ci/scripts/build_with_poetry.sh + /bin/bash ./ci/scripts/build_with_uv.sh - - '@semantic-release/github': - assets: - dist/*.whl diff --git a/README.md b/README.md index a73aa470..bf981d9a 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,15 @@ oldest version currently supported (see the ## Dependency management -[poetry](https://https://python-poetry.org/) for dependency management: +[uv](https://docs.astral.sh/uv/) is used for dependency management and provides fast package installation: -``` -% brew install poetry -% cd appmap-python -% poetry install +```bash +# Install uv (macOS/Linux) +curl -LsSf https://astral.sh/uv/install.sh | sh + +# Install dependencies +cd appmap-python +uv sync --all-extras ``` ### wrapt @@ -69,64 +72,63 @@ To update `wrapt`, use `tox` (described below) to run the `vendoring` environmen ## Linting [pylint](https://www.pylint.org/) for linting: -``` -% cd appmap-python -% poetry run pylint appmap +```bash +cd appmap-python +uv run tox -e lint + +# Or run pylint directly +uv run pylint appmap -------------------------------------------------------------------- Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00) - ``` -[Note that the current configuration has a threshold set which must be met for the Travis build to -pass. To make this easier to achieve, a number of checks have both been disabled. They should be -reenabled as soon as possible.] - ## Testing ### pytest -Note that you must install the dependencies contained in -[requirements-dev.txt](requirements-dev.txt) before running tests. See the explanation in -[pyproject.toml](pyproject.toml) for details. +[pytest](https://docs.pytest.org/en/stable/) for testing: -Additionally, the tests currently require that you set `APPMAP=true` and -`APPMAP_DISPLAY_PARAMS=true`. +```bash +cd appmap-python -[pytest](https://docs.pytest.org/en/stable/) for testing: +# Run all tests +APPMAP_DISPLAY_PARAMS=true uv run appmap-python pytest -``` -% cd appmap-python -% pip install -r requirements-test.txt -% APPMAP=true APPMAP_DISPLAY_PARAMS=true poetry run pytest +# Run tests with a specific Python version +APPMAP_DISPLAY_PARAMS=true uv run --python 3.9 appmap-python pytest + +# Run tests in parallel +APPMAP_DISPLAY_PARAMS=true uv run appmap-python pytest -n auto ``` ### tox -Additionally, the `tox` configuration provides the ability to run the tests for all -supported versions of Python and Django. +The `tox` configuration provides the ability to run the tests for all supported versions of Python and web frameworks (Django, Flask, SQLAlchemy). -`tox` requires that all the correct versions of Python to be available to create -the test environments. [pyenv](https://github.com/pyenv/pyenv) is an easy way to manage -multiple versions of Python, and the [xxenv-latest -plugin](https://github.com/momo-lab/xxenv-latest) can help get all the latest versions. +With `uv`, you don't need to pre-install Python versions - `uv` will automatically download and manage them: +```bash +cd appmap-python +# Run full test matrix (all Python versions and frameworks) +uv run tox -```sh -% brew install pyenv -% git clone https://github.com/momo-lab/xxenv-latest.git "$(pyenv root)"/plugins/xxenv-latest -% cd appmap-python -% pyenv latest local 3.{9,6,7,8} -% for v in 3.{9,6,7,8}; do pyenv latest install $v; done -% poetry run tox +# Run tests for a specific Python version +uv run tox -e py312-web + +# Run tests for specific framework +uv run tox -e py312-django5 + +# Update vendored wrapt dependency +uv run tox -e vendoring sync ``` ## Code Coverage [coverage](https://coverage.readthedocs.io/) for coverage: -``` -% cd appmap-python -% poetry run coverage run -m pytest -% poetry run coverage html -% open htmlcov/index.html +```bash +cd appmap-python +uv run coverage run -m pytest +uv run coverage html +open htmlcov/index.html ``` diff --git a/ci/scripts/build_with_poetry.sh b/ci/scripts/build_with_uv.sh similarity index 81% rename from ci/scripts/build_with_poetry.sh rename to ci/scripts/build_with_uv.sh index 53c89791..aa85b485 100755 --- a/ci/scripts/build_with_poetry.sh +++ b/ci/scripts/build_with_uv.sh @@ -4,16 +4,16 @@ set -e set -o pipefail if [ -z "$DISTRIBUTION_NAME" ] || [ "$DISTRIBUTION_NAME" = "appmap" ] ; then - exec poetry build $* + exec uv build $* fi -echo "Altering distribution name to $DISTRIBUTION_NAME" +echo "Altering distribution name to $DISTRIBUTION_NAME" cp -v pyproject.toml /tmp/pyproject.bak sed -i -e "s/^name = \".*\"/name = \"${DISTRIBUTION_NAME}\"/" pyproject.toml grep -n 'name = "' pyproject.toml -poetry build $* +uv build $* echo "Not patching artifacts with Provides-Dist, they won't work anyway (this flow is solely for publishing test)" cp -v /tmp/pyproject.bak pyproject.toml diff --git a/pylintrc b/pylintrc index 808bf36e..e281a970 100644 --- a/pylintrc +++ b/pylintrc @@ -95,7 +95,6 @@ recursive=no # When enabled, pylint would attempt to guess common misconfiguration and emit # user-friendly hints instead of false-positive error messages. -suggestion-mode=yes # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. diff --git a/pyproject.toml b/pyproject.toml index e0b54baf..e19a8cef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,101 +1,114 @@ -[tool.poetry] +[project] name = "appmap" version = "2.1.8" description = "Create AppMap files by recording a Python application." readme = "README.md" +requires-python = ">=3.8" +license = { text = "MIT" } authors = [ - "Alan Potter ", - "Viraj Kanwade ", - "Rafał Rzepecki " + { name = "Alan Potter", email = "alan@app.land" }, + { name = "Viraj Kanwade", email = "viraj.kanwade@forgeahead.io" }, + { name = "Rafał Rzepecki", email = "rafal@app.land" } ] -homepage = "https://github.com/applandinc/appmap-python" -license = "MIT" classifiers = [ - 'Development Status :: 4 - Beta', - 'Framework :: Django', - 'Framework :: Django :: 3.2', - 'Framework :: Flask', - 'Framework :: Pytest', - 'Intended Audience :: Developers', - 'Topic :: Software Development', - 'Topic :: Software Development :: Debuggers', - 'Topic :: Software Development :: Documentation' + "Development Status :: 4 - Beta", + "Framework :: Django", + "Framework :: Django :: 3.2", + "Framework :: Flask", + "Framework :: Pytest", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Topic :: Software Development", + "Topic :: Software Development :: Debuggers", + "Topic :: Software Development :: Documentation", ] -include = [ - { path = 'appmap.pth', format = ['sdist','wheel'] }, - { path = '_appmap/test/**/*', format = 'sdist' } -] - -exclude = ['_appmap/wrapt'] - -packages = [ - { include = "appmap" }, - { include = "_appmap/*.py" }, - { include = "_appmap/wrapt/**/*", from = "vendor" } -] - -[tool.poetry.dependencies] # Please update the documentation if changing the supported python version # https://github.com/applandinc/applandinc.github.io/blob/master/_docs/reference/appmap-python.md#supported-versions -python = "^3.8" -PyYAML = ">=5.3.0" -inflection = ">=0.3.0" -importlib-resources = "^5.4.0" -packaging = ">=19.0" -# If you include "Django" as an optional dependency here, you'll be able to use poetry to install it -# in your dev environment. However, doing so causes poetry v1.2.0 to remove it from the virtualenv -# *created and managed by tox*, i.e. not your dev environment. -# -# So, if you'd like to run the tests outside of tox, run `pip install -r requirements-dev.txt` to -# install it and the rest of the dev dependencies. +dependencies = [ + "PyYAML>=5.3.0", + "inflection>=0.3.0", + "importlib-resources>=5.4.0", + "packaging>=19.0", +] -[tool.poetry.group.dev.dependencies] -Twisted = "^22.4.0" -incremental = "<24.7.0" -asgiref = "^3.7.2" -black = "^24.2.0" -coverage = "^5.3" -flake8 = "^3.8.4" -httpretty = "^1.0.5" -isort = "^5.10.1" -pprintpp = ">=0.4.0" -pyfakefs = "^5.3.5" -pylint = "^3.0" -pytest = "^7.3.2" -pytest-django = "~4.7" -pytest-mock = "^3.5.1" -pytest-randomly = "^3.5.0" -pytest-shell-utilities = "^1.8.0" -pytest-xprocess = "^0.23.0" -python-decouple = "^3.5" -requests = "^2.25.1" -tox = "^3.22.0" -# v2.30.0 of "requests" depends on urllib3 v2, which breaks the tests for http_client_requests. Pin -# to v1 until this gets fixed. -urllib3 = "^1" -uvicorn = "^0.27.1" -fastapi = "^0.110.0" -httpx = "^0.27.0" -pytest-env = "^1.1.3" -pytest-console-scripts = "^1.4.1" -pytest-xdist = "^3.6.1" -psutil = "^6.0.0" -ruff = "^0.5.3" +[project.optional-dependencies] +test = [ + "pytest>=7.3.2,<8.0", + "pytest-mock>=3.5.1,<4.0", + "pytest-randomly>=3.5.0,<4.0", + "pytest-shell-utilities>=1.8.0,<2.0", + "pytest-xprocess>=0.23.0,<1.0", + "pytest-env>=1.1.3,<2.0", + "pytest-console-scripts>=1.4.1,<2.0", + "pytest-xdist>=3.6.1,<4.0", + "httpretty>=1.0.5,<2.0", + "pyfakefs>=5.3.5,<6.0", + "requests>=2.25.1,<3.0", + "python-decouple>=3.5,<4.0", + "Twisted>=22.4.0,<23.0", + "incremental<24.7.0", + "asgiref>=3.7.2,<4.0", + "psutil>=6.0.0,<7.0", + "uvicorn>=0.27.1,<1.0", + "fastapi>=0.110.0,<1.0", + "httpx>=0.27.0,<1.0", + # v2.30.0 of "requests" depends on urllib3 v2, which breaks the tests for http_client_requests. Pin + # to v1 until this gets fixed. + "urllib3>=1,<2", + "Django>=4.0,<5.0", + "Flask>=3.0", + "sqlalchemy>=2.0,<3.0", + "pytest-django>=4.7,<5.0", + "numpy>=1.24.4,<2.0; python_version < '3.9'", + "numpy>=2.0; python_version >= '3.9'", +] +dev = [ + "appmap[test]", + "black>=24.2.0,<25.0", + "coverage>=5.3,<6.0", + "flake8>=3.8.4,<4.0", + "isort>=5.10.1,<6.0", + "pprintpp>=0.4.0,<1.0", + "pylint>=3.0,<4.0", + "tox>=4.0,<5.0", + "tox-uv>=1.0,<2.0", + "tox-gh-actions>=3.0,<4.0", + "ruff>=0.5.3,<1.0", +] -[build-system] -requires = ["poetry-core>=1.1.0"] -build-backend = "poetry.core.masonry.api" +[project.urls] +Homepage = "https://github.com/applandinc/appmap-python" -[tool.poetry.plugins."pytest11"] -appmap = "appmap.pytest" - -[tool.poetry.scripts] +[project.scripts] appmap-agent-init = "appmap.command.appmap_agent_init:run" appmap-agent-status = "appmap.command.appmap_agent_status:run" appmap-agent-validate = "appmap.command.appmap_agent_validate:run" appmap-python = "appmap.command.runner:run" +[project.entry-points.pytest11] +appmap = "appmap.pytest" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["appmap", "_appmap"] +exclude = ["_appmap/test", "_appmap/wrapt"] +force-include = { "appmap.pth" = "appmap.pth", "vendor/_appmap/wrapt" = "_appmap/wrapt" } + +[tool.hatch.build.targets.sdist] +only-include = [ + "appmap", + "_appmap", + "appmap.pth", + "vendor", + "pyproject.toml", + "README.md", + "LICENSE", +] + [tool.black] line-length = 102 extend-exclude = ''' diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index f70988fa..00000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,10 +0,0 @@ -#requirements-dev.txt -tox -django -flask >=2, <= 3 -pytest-django<4.8 -fastapi -httpx -sqlalchemy -debugpy -numpy \ No newline at end of file diff --git a/requirements-test.txt b/requirements-test.txt deleted file mode 100644 index 16b853d6..00000000 --- a/requirements-test.txt +++ /dev/null @@ -1,3 +0,0 @@ -django ~= 3.2 -pytest-django < 4.8 -sqlalchemy < 2.0 diff --git a/tool-versions.example b/tool-versions.example deleted file mode 100644 index 4a4d044f..00000000 --- a/tool-versions.example +++ /dev/null @@ -1 +0,0 @@ -python 3.8.18 3.9.18 3.10.13 3.11.7 3.12.1 diff --git a/tox.ini b/tox.ini index be81c9d2..2b8ed82f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,8 @@ [tox] +requires = + tox>=4 + tox-uv + tox-gh-actions isolated_build = true # The *-web environments test the latest versions of Django and Flask with the full test suite. For @@ -22,13 +26,15 @@ deps= sqlalchemy >=2.0, <3.0 [testenv] -passenv = +usedevelop = true +extras = test +passenv = PYTEST_XDIST_AUTO_NUM_WORKERS -setenv = +setenv = APPMAP_DISPLAY_PARAMS=true deps= - poetry web: {[web-deps]deps} + web,django3,django4,django5: pytest-django >=4.7, <5.0 py38: numpy==1.24.4 py3{9,10,11,12}: numpy >=2 flask2: Flask >= 2.0, <3.0 @@ -38,31 +44,30 @@ deps= sqlalchemy1: sqlalchemy >=1.4.11, <2.0 commands = - poetry install -v - web: poetry run appmap-python {posargs:pytest -n logical} - django3: poetry run appmap-python pytest -n logical _appmap/test/test_django.py - django4: poetry run appmap-python pytest -n logical _appmap/test/test_django.py - django5: poetry run appmap-python pytest -n logical _appmap/test/test_django.py - flask2: poetry run appmap-python pytest -n logical _appmap/test/test_flask.py - sqlalchemy1: poetry run appmap-python pytest -n logical _appmap/test/test_sqlalchemy.py + web: appmap-python {posargs:pytest -n logical} + django3: appmap-python pytest -n logical _appmap/test/test_django.py + django4: appmap-python pytest -n logical _appmap/test/test_django.py + django5: appmap-python pytest -n logical _appmap/test/test_django.py + flask2: appmap-python pytest -n logical _appmap/test/test_flask.py + sqlalchemy1: appmap-python pytest -n logical _appmap/test/test_sqlalchemy.py [testenv:lint] -skip_install = True +skip_install = False +extras = test deps = - poetry {[web-deps]deps} numpy >=2 + pylint >=3.0 commands = - poetry install # It doesn't seem great to disable cyclic-import checking, but the imports # aren't currently causing any problems. They should probably get fixed # sometime soon. - {posargs:poetry run pylint --disable=cyclic-import -j 0 appmap _appmap} + {posargs:pylint --disable=cyclic-import -j 0 appmap _appmap} [testenv:vendoring] skip_install = True deps = vendoring commands = - poetry run vendoring {posargs:sync} + vendoring {posargs:sync} # We don't need the .pyi files vendoring generates python -c 'from pathlib import Path; all(map(Path.unlink, Path("vendor").rglob("*.pyi")))'