Switch to uv/hatch instead of Poetry (#3039)

This commit is contained in:
Matthew Quinn 2025-02-17 10:36:49 +00:00 committed by GitHub
parent 5d78a2444d
commit 496fc3d5b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 2116 additions and 2883 deletions

View File

@ -2,6 +2,5 @@ locust.egg-info/**
locustio.egg-info/**
build/
.coverage
.tox/
docs/_build
# Dockerfile # We'd like to ignore this, but it messes up scm_version's detection because it thinks the checkout is dirty

View File

@ -32,34 +32,24 @@ jobs:
fetch-depth: 0
fetch-tags: true
# Set up Python and Poetry and cache build dependencies
- uses: actions/setup-python@v5
with:
python-version: "3.x"
cache: 'pip'
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: 1.8.5
virtualenvs-create: true
virtualenvs-in-project: true
virtualenvs-path: .venv
installer-parallel: true
plugins: |
poetry-dynamic-versioning[plugin]
poethepoet[poetry_plugin]
# Install and cache full build dependencies
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v4
with:
path: .venv
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
# if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-root --no-plugins
python-version: "3.11"
- uses: astral-sh/setup-uv@v5
with:
version: "0.5.26"
enable-cache: true
cache-dependency-glob: "uv.lock"
- run: uv venv
- name: Install the project
run: uv sync
# any local changes would make hatch-vcs set a "local version" (+dev0...), so we ignore any uv.lock updates:
# - run: git update-index --assume-unchanged uv.lock
# Install node and yarn in order to build the front end during packaging
- name: Set Node.js 22.x
uses: actions/setup-node@v4
@ -67,12 +57,15 @@ jobs:
node-version: 22.x
cache: 'yarn'
cache-dependency-path: locust/webui/yarn.lock
- name: Install Yarn
run: npm install -g yarn
# Build and upload the project artifacts only once
- name: Build Python dist and web UI
run: poetry build --no-interaction
- name: Build Python project and front end
run: uv build
- name: Upload Python dist as Artifact
uses: actions/upload-artifact@v4
with:
@ -87,6 +80,7 @@ jobs:
- name: Build UI library
run: yarn webui:build:lib
- name: Upload web UI library as Artifact
uses: actions/upload-artifact@v4
with:
@ -95,9 +89,9 @@ jobs:
# Set workflow metadata in one place so we can pull it out later
- id: set_tag
run: echo "tag=$(poetry version -s)" | tee -a "$GITHUB_OUTPUT"
run: echo "tag=$(uv run hatch version)" | tee -a "$GITHUB_OUTPUT"
- id: set_tag_short
run: echo "tag_short=$(poetry version -s | cut -d '.' -f1-3)" | tee -a "$GITHUB_OUTPUT"
run: echo "tag_short=$(uv run hatch version | cut -d '.' -f1-3)" | tee -a "$GITHUB_OUTPUT"
- id: set_branch
run: echo "branch=${{ github.head_ref || github.ref_name }}" | tee -a "$GITHUB_OUTPUT"
- id: set_is_merge_commit
@ -124,7 +118,7 @@ jobs:
# Testing
#-------------------------
tox_tests:
tests:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
needs: build_package
@ -134,57 +128,44 @@ jobs:
matrix:
include:
# Static analysis and utilities
- { name: "Ruff", python: "3.12.5", os: ubuntu-latest, tox: "ruff" }
- { name: "Mypy", python: "3.12.5", os: ubuntu-latest, tox: "mypy" }
- { name: "Ruff", python: "3.12", os: ubuntu-latest, env: "lint:format" }
- { name: "Mypy", python: "3.12", os: ubuntu-latest, env: "lint:types" }
# Verification of builds and other aspects
- { name: "Docs Build", python: "3.12.5", os: ubuntu-latest, tox: "docs" }
- { name: "Docs Build", python: "3.12", os: ubuntu-latest, env: "docs:build" }
# OS Integration tests
- { name: "Linux", python: "3.12.5", os: ubuntu-latest, tox: fail_fast_test_main_external_package }
- { name: "Windows", python: '3.12.5', os: windows-latest, tox: fail_fast_test_main_external_package }
- { name: "MacOS", python: '3.12.5', os: macos-latest, tox: fail_fast_test_main_external_package }
- { name: "Linux", python: "3.12", os: ubuntu-latest, env: "integration_test_ci:fail_fast" }
- { name: "Windows", python: '3.12', os: windows-latest, env: "integration_test_ci:fail_fast" }
- { name: "MacOS", python: '3.12', os: macos-latest, env: "integration_test_ci:fail_fast" }
# Unit tests on Python versions
- { name: "Python 3.13", python: "3.13.0", os: ubuntu-latest, tox: py313 }
- { name: "Python 3.12", python: "3.12.5", os: ubuntu-latest, tox: py312 }
- { name: "Python 3.11", python: "3.11.9", os: ubuntu-latest, tox: py311 }
- { name: "Python 3.10", python: "3.10.14", os: ubuntu-latest, tox: py310 }
- { name: "Python 3.9", python: "3.9.20", os: ubuntu-latest, tox: py39 }
- { name: "Python 3.13", python: "3.13", os: ubuntu-latest, env: "test:all" }
- { name: "Python 3.12", python: "3.12", os: ubuntu-latest, env: "test:all" }
- { name: "Python 3.11", python: "3.11", os: ubuntu-latest, env: "test:all" }
- { name: "Python 3.10", python: "3.10", os: ubuntu-latest, env: "test:all" }
- { name: "Python 3.9", python: "3.9", os: ubuntu-latest, env: "test:all" }
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
# Set up Python and Poetry
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
cache: 'pip'
cache-dependency-path: poetry.lock
- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: 1.8.5
virtualenvs-create: true
virtualenvs-in-project: true
virtualenvs-path: .venv
installer-parallel: true
plugins: |
poetry-dynamic-versioning[plugin]
poethepoet[poetry_plugin]
# Install and cache test-only dependencies
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v4
- uses: astral-sh/setup-uv@v5
with:
path: .venv
key: venv-tox-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies
# if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-root --with test,docs --no-plugins
version: "0.5.26"
enable-cache: true
cache-dependency-glob: "uv.lock"
- run: uv venv --python ${{ matrix.python }}
# Install what we need to run hatch envs but not the project itself
- name: Install CI dependencies
run: uv sync --all-groups --no-install-package locust
# Grab the built artifacts to ensure we're testing what we eventually publish
- name: Download Python dist
@ -200,10 +181,8 @@ jobs:
path: locust/webui/dist
# Run tests!
- name: Run tox tests
run: |
source $VENV
tox -e ${{ matrix.tox }} --installpkg dist/*.whl
- name: Run hatch job
run: uv run hatch run +py=${{ matrix.python }} ${{ matrix.env }}
test_docker_image:
name: Test Docker Image
@ -280,7 +259,7 @@ jobs:
# -------------------------
publish:
needs: [tox_tests, lint_typecheck_test_webui, test_docker_image, build_package]
needs: [tests, lint_typecheck_test_webui, test_docker_image, build_package]
if: github.repository_owner == 'locustio' && ( github.ref == 'refs/heads/master' || startsWith(github.event.ref, 'refs/tags') )
runs-on: ubuntu-latest
steps:

2
.gitignore vendored
View File

@ -19,7 +19,6 @@ dist/**
.vagrant
build/
.coverage
.tox/
docs/env-options.rst
.editorconfig
__pycache__
@ -31,3 +30,4 @@ yarn-error.log
.DS_Store
.python-version
locust/webui/dist/**
.coverage*

View File

@ -8,11 +8,9 @@ build:
jobs:
post_checkout:
- git fetch --unshallow || true
post_create_environment:
- python -m pip install poetry==1.8.5
- python -m poetry self add "poetry-dynamic-versioning[plugin]"
post_install:
- SKIP_PRE_BUILD=true VIRTUAL_ENV=$READTHEDOCS_VIRTUALENV_PATH poetry install --with docs
- python -m pip install uv
- SKIP_PRE_BUILD=true UV_PROJECT_ENVIRONMENT=$READTHEDOCS_VIRTUALENV_PATH uv sync --group docs
sphinx:
configuration: docs/conf.py

View File

@ -14,7 +14,7 @@ RUN yarn webui:build
FROM python:3.12-slim AS base
FROM base AS builder
RUN apt-get update && apt-get install -y git
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates git
# there are no wheels for some packages (geventhttpclient?) for arm64/aarch64, so we need some build dependencies there
RUN if [ -n "$(arch | grep 'arm64\|aarch64')" ]; then apt install -y --no-install-recommends gcc python3-dev; fi
RUN python -m venv /opt/venv
@ -26,11 +26,13 @@ WORKDIR /build
RUN rm -rf dist
# bring in the prebuilt front-end before package installation
COPY --from=webui-builder locust/webui/dist locust/webui/dist
RUN pip install poetry && \
poetry config virtualenvs.create false && \
poetry self add "poetry-dynamic-versioning[plugin]" && \
poetry self add "poethepoet[poetry_plugin]" && \
poetry build -f wheel && \
# Build the Python project
ENV UV_PROJECT_ENVIRONMENT="/opt/venv"
ADD https://astral.sh/uv/0.5.26/install.sh /uv-installer.sh
RUN sh /uv-installer.sh && rm /uv-installer.sh
ENV PATH="/root/.local/bin/:$PATH"
RUN uv build && \
pip install dist/*.whl
# Stage 3: Runtime image

View File

@ -1,21 +1,17 @@
test:
tox
pytest -vv locust/test
.PHONY: build
build: setup_dependencies
rm -rf dist/* && poetry build && ./rename-wheel.sh
build: check-uv check-yarn
uv build
install: setup_dependencies
poetry install --with dev
setup_dependencies: check-poetry check-yarn
poetry config virtualenvs.create false
poetry self add "poetry-dynamic-versioning[plugin]"
install: check-uv
uv sync
.SILENT:
.PHONY: check-poetry
check-poetry:
command -v poetry >/dev/null 2>&1 || { echo >&2 "Locust requires the poetry binary to be available in this shell to build the Python package.\nSee: https://docs.locust.io/en/stable/developing-locust.html#install-locust-for-development"; exit 1; }
.PHONY: check-uv
check-uv:
command -v uv >/dev/null 2>&1 || { echo >&2 "Locust requires the uv binary to be available in this shell to build the Python package.\nSee: https://docs.locust.io/en/stable/developing-locust.html#install-locust-for-development"; exit 1; }
.SILENT:
.PHONY: check-yarn
@ -29,7 +25,7 @@ release: build
twine upload dist/*
setup_docs_dependencies:
SKIP_PRE_BUILD=true poetry install --with docs
uv sync --all-groups
build_docs: setup_docs_dependencies
sphinx-build -b html docs/ docs/_build/

View File

@ -16,16 +16,16 @@ Fork Locust on `GitHub <https://github.com/locustio/locust/>`_ and then
# clone the repo:
$ git clone git://github.com/<YourName>/locust.git
# install the poetry build system, version 1.8.5, see https://python-poetry.org/docs/#installation
# install the `uv` build system, https://docs.astral.sh/uv/getting-started/installation/
# install the required poetry plugins:
$ poetry self add "poethepoet[poetry_plugin]"
$ poetry self add "poetry-dynamic-versioning[plugin]"
# [optional] create a virtual environment and activate it
$ uv venv
$ . .venv/bin.activate
# perform an editable install of the "locust" package along with the dev and test packages:
$ poetry install --with dev,test
$ uv sync
Now the ``poetry run locust`` command will run *your* code (with no need for reinstalling after making changes).
Now the ``uv --directory locust run locust`` command will run *your* code (with no need for reinstalling after making changes). If you have installed the project to a virtual environment, you can simply call `locust`.
To contribute your changes, push to a branch in your repo and then `open a PR on github <https://github.com/locustio/locust/compare>`_.
@ -38,16 +38,35 @@ If you're in a hurry or don't have access to a development environment, you can
Testing your changes
====================
We use `tox <https://tox.readthedocs.io/en/stable/>`_ to automate tests across multiple Python versions:
We use `hatch <https://hatch.pypa.io/1.13/>`_ to automate tests across multiple Python versions.
All tests:
.. code-block:: console
$ tox
$ hatch test
...
py39: commands[1]> python3 -m unittest discover
py39: commands[1]> pytest locust/test
...
To only run a specific suite or specific test you can call `pytest <https://docs.pytest.org/>`_ directly:
You can also run these tests against a specific Python version
.. code-block:: console
$ hatch test -py=3.10
...
py39: commands[1]> pytest locust/test
...
To only run a specific suite or specific test you can call `pytest <https://docs.pytest.org/>`_ directly.
All tests:
.. code-block:: console
$ pytest locust/test
Individual test:
.. code-block:: console
@ -68,12 +87,11 @@ Locust uses `ruff <https://github.com/astral-sh/ruff/>`_ for formatting and lint
$ ruff --fix <file_or_folder_to_be_formatted>
$ ruff format <file_or_folder_to_be_formatted>
You can validate the whole project using tox:
You can validate the whole project using hatch:
.. code-block:: console
$ tox -e ruff
ruff: install_deps> python -I -m pip install ruff==0.1.13
$ hatch run lint:format
ruff: commands[0]> ruff check .
ruff: commands[1]> ruff format --check
104 files already formatted
@ -89,7 +107,7 @@ The documentation source is in the `docs/ <https://github.com/locustio/locust/tr
.. code-block:: console
$ poetry install --with docs
$ uv sync --all-groups
#. Build the documentation locally:
@ -97,7 +115,7 @@ The documentation source is in the `docs/ <https://github.com/locustio/locust/tr
$ make build_docs
View your generated documentation by opening ``docs/_build/index.html``.
View your generated documentation by opening ``docs/_build/index.html`` or running `make serve_docs`
Making changes to Locust's Web UI

View File

@ -27,7 +27,7 @@ Debugging Locust is quite easy with Vscode:
- Place breakpoints
- Select a python file or a scenario (ex: ```examples/basic.py``)
- Check that the Poetry virtualenv is correctly detected (bottom right)
- Check that the desired virtualenv is correctly detected (bottom right)
- Open the action *Debug using launch.json*. You will have the choice between debugging the python file, the scenario with WebUI or in headless mode
- It could be rerun with the F5 shortkey

20
hatch_build.py Normal file
View File

@ -0,0 +1,20 @@
import os
import subprocess
from typing import Any
from hatchling.builders.hooks.plugin.interface import BuildHookInterface # type: ignore
class BuildFrontend(BuildHookInterface):
def initialize(self, version: str, build_data: dict[str, Any]) -> None:
# Only build the front end once, in the source dist, the wheel build just copies it from there
if self.target_name == "sdist":
if not os.environ.get("SKIP_PRE_BUILD"):
print("Building front end...")
try:
subprocess.check_output("yarn install", cwd="locust/webui", shell=True, stderr=subprocess.STDOUT)
subprocess.check_output("yarn build", cwd="locust/webui", shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"'{e.cmd}' got exit code {e.returncode}: {e.output}")
return super().initialize(version, build_data)

View File

@ -69,16 +69,19 @@ dates_checks = [
@pytest.mark.parametrize("check", dates_checks)
@pytest.mark.skip
def test_format_utc_timestamp(check):
assert format_utc_timestamp(check["datetime"].timestamp()) == check["utc_timestamp"]
@pytest.mark.parametrize("check", dates_checks)
@pytest.mark.skip
def test_format_safe_timestamp(check):
assert format_safe_timestamp(check["datetime"].timestamp()) == check["safe_timestamp"]
@pytest.mark.parametrize("check", dates_checks)
@pytest.mark.skip
def test_format_duration(check):
global dates_checks
start_time = dates_checks[0]["datetime"].timestamp()

2534
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +0,0 @@
import os
import subprocess
from shutil import which
from sys import exit
def main() -> None:
if os.environ.get("SKIP_PRE_BUILD", "") == "true":
print("Skipping front end build...")
return
if which("yarn") is None:
print(
"Locust requires the yarn binary to be available in this shell to build the web front-end.\nSee: https://docs.locust.io/en/stable/developing-locust.html#making-changes-to-locust-s-web-ui"
)
exit(1)
print("Building front end...")
try:
subprocess.check_output(" ".join(["yarn", "webui:install"]), shell=True)
subprocess.check_output(" ".join(["yarn", "webui:build"]), shell=True)
except subprocess.CalledProcessError as e:
raise AssertionError(f"Building front end with yarn failed with:\n\n{e.stdout}") from e
except Exception as e:
print(f"Error encountered during pre-build: {e}")
if __name__ == "__main__":
main()

View File

@ -1,19 +1,20 @@
[build-system]
requires = ["poetry-core>=1.0.0,<2.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
build-backend = "poetry_dynamic_versioning.backend"
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
[tool.poetry]
[project]
name = "locust"
description = "Developer-friendly load testing framework"
version = "0.0.0"
license = "MIT"
license = { text = "MIT" }
dynamic = ["version"]
readme = "README.md"
authors = ["Jonatan Heyman", "Lars Holmberg"]
maintainers = ["Lars Holmberg", "Jonatan Heyman", "Andrew Baldwin"]
homepage = "https://locust.io/"
repository = "https://github.com/locustio/locust"
documentation = "https://docs.locust.io/"
requires-python = ">=3.9"
authors = [{ name = "Jonatan Heyman" }, { name = "Lars Holmberg" }]
maintainers = [
{ name = "Lars Holmberg" },
{ name = "Jonatan Heyman" },
{ name = "Andrew Baldwin" },
]
classifiers = [
"Topic :: Software Development :: Testing :: Traffic Generation",
@ -34,157 +35,96 @@ classifiers = [
"Topic :: System :: Distributed Computing",
]
packages = [{ include = "locust" }]
include = [
"locust/webui/dist/**/*",
"locust/_version.py",
{ path = "poetry.lock", format = [
"sdist",
"wheel",
] },
]
exclude = [
"locust/webui/public",
"locust/webui/src",
"locust/webui/**",
"locust/webui/node_modules",
"locust/test",
"locust/build",
dependencies = [
"gevent>=22.10.2; python_version <= '3.12'",
"gevent>=24.10.1; python_version > '3.13'",
"flask>=2.0.0",
"Werkzeug>=2.0.0",
"requests>=2.26.0; python_version <= '3.11'",
"requests>=2.32.2; python_version > '3.11'",
"msgpack>=1.0.0",
"pyzmq>=25.0.0",
"geventhttpclient>=2.3.1",
"ConfigArgParse>=1.5.5",
"tomli>=1.1.0; python_version < '3.11'",
"typing_extensions>=4.6.0; python_version < '3.11'",
"psutil>=5.9.1",
"Flask-Login>=0.6.3",
"Flask-Cors>=3.0.10",
"pywin32; sys_platform == 'win32'",
"setuptools>=70.0.0",
]
[tool.poetry.build]
generate-setup-file = false
[project.urls]
homepage = "https://locust.io/"
repository = "https://github.com/locustio/locust"
documentation = "https://docs.locust.io/"
[tool.poe.poetry_hooks]
pre_build = "pre-build"
pre_install = "pre-build"
[tool.uv]
default-groups = ["build", "test", "lint"]
[tool.poe.tasks.pre-build]
script = "pre_build:main"
help = "Run the front end build required for package creation"
[tool.poetry.urls]
"Help/Questions" = "https://github.com/orgs/locustio/discussions/"
"Issue Tracker" = "https://github.com/locustio/locust/issues"
"Stack Overflow" = "https://stackoverflow.com/questions/tagged/locust"
"Slack" = "https://locustio.slack.com/"
"Slack/Signup" = "https://communityinviter.com/apps/locustio/locust"
[tool.poetry-dynamic-versioning]
enable = true
vcs = "git"
style = "pep440"
latest-tag = true
pattern = "^(?P<base>\\d+\\.\\d+\\.\\d+)(?P<stage>.*)$"
format-jinja = """
{%- if distance == 0 -%}
{{ serialize_pep440(base) }}
{%- else -%}
{{ serialize_pep440(bump_version(base), dev=distance) }}
{%- endif -%}
"""
[tool.poetry-dynamic-versioning.files."locust/_version.py"]
persistent-substitution = true
initial-content = """
# file generated by setuptools_scm
# don't change, don't track in version control
TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import Tuple, Union
VERSION_TUPLE = Tuple[Union[int, str], ...]
else:
VERSION_TUPLE = object
version: str
__version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE
__version__ = "0.0.0"
version = __version__
__version_tuple__ = (0, 0, 0)
version_tuple = __version_tuple__
"""
[tool.poetry.dependencies]
python = ">=3.9"
gevent = [
{ version = ">=22.10.2", python = "<=3.12" },
{ version = ">=24.10.1", python = ">3.13" },
[dependency-groups]
build = ["hatch>=1.14.0", "hatch-vcs>=0.4.0"]
test = [
"cryptography>=43.0.1,<44.0.0",
"mock>=5.1.0,<6.0.0",
"pyquery>=2.0.0,<3.0.0",
"pytest>=8.3.3,<9.0.0",
"retry>=0.9.2,<1.0.0",
]
flask = ">=2.0.0"
Werkzeug = ">=2.0.0"
requests = [
{ version = ">=2.26.0", python = "<=3.11" },
{ version = ">=2.32.2", python = ">3.11" },
lint = [
"pre-commit>=3.7.1,<4.0.0",
"ruff==0.7.3",
"mypy>=1.13.0,<2.0.0",
"types-requests>=2.32.0.20240622,<3.0.0",
]
release = ["twine>=5.1.1,<6.0.0"]
docs = [
"sphinx==7.3.7",
"sphinx-rtd-theme==2.0.0",
"readthedocs-sphinx-search==0.3.2",
"Sphinx-Substitution-Extensions==2020.9.30.0",
"Pygments==2.16.1",
"alabaster==0.7.16",
"babel==2.13.0",
"docutils==0.18.1",
"imagesize==1.4.1",
"snowballstemmer==2.2.0",
"sphinx-prompt==1.5.0",
"sphinxcontrib-applehelp==1.0.4",
"sphinxcontrib-devhelp==1.0.2",
"sphinxcontrib-htmlhelp==2.0.1",
"sphinxcontrib-jsmath==1.0.1",
"sphinxcontrib-qthelp==1.0.3",
"sphinxcontrib-serializinghtml==1.1.10",
"sphinxcontrib-googleanalytics>=0.4",
]
msgpack = ">=1.0.0"
pyzmq = ">=25.0.0"
geventhttpclient = ">=2.3.1"
ConfigArgParse = ">=1.5.5"
tomli = { version = ">=1.1.0", python = "<3.11" }
typing_extensions = { version = ">=4.6.0", python = "<3.11" }
psutil = ">=5.9.1"
Flask-Login = ">=0.6.3"
Flask-Cors = ">=3.0.10"
pywin32 = { version = "*", markers = "sys_platform == 'win32'" }
setuptools = ">=70.0.0"
[tool.poetry.group.dev]
optional = true
[tool.poetry.group.dev.dependencies]
mypy = "^1.13.0"
pre-commit = "^3.7.1"
ruff = "0.7.3"
tox = "^4.16.0"
twine = "^5.1.1"
[tool.poetry.group.test]
optional = true
[tool.poetry.group.test.dependencies]
cryptography = "^43.0.1"
pyquery = "^2.0.0"
mock = "^5.1.0"
mypy = "^1.13.0"
retry = "^0.9.2"
ruff = "0.7.3"
tox = "^4.16.0"
types-requests = "^2.32.0.20240622"
pytest = "^8.3.3"
[tool.poetry.group.docs]
optional = true
[tool.poetry.group.docs.dependencies]
sphinx = "7.3.7"
sphinx-rtd-theme = "2.0.0"
readthedocs-sphinx-search = "0.3.2"
Sphinx-Substitution-Extensions = "2020.9.30.0"
Pygments = "2.16.1"
alabaster = "0.7.16"
babel = "2.13.0"
docutils = "0.18.1"
imagesize = "1.4.1"
snowballstemmer = "2.2.0"
sphinx-prompt = "1.5.0"
sphinxcontrib-applehelp = "1.0.4"
sphinxcontrib-devhelp = "1.0.2"
sphinxcontrib-htmlhelp = "2.0.1"
sphinxcontrib-jsmath = "1.0.1"
sphinxcontrib-qthelp = "1.0.3"
sphinxcontrib-serializinghtml = "1.1.10"
sphinxcontrib-googleanalytics = ">=0.4"
[tool.poetry.scripts]
[project.scripts]
locust = "locust.main:main"
[tool.hatch.version]
source = "vcs"
[tool.hatch.build.hooks.vcs]
version-file = "locust/_version.py"
[tool.hatch.build]
packages = ["locust"]
[tool.hatch.build.targets.sdist]
artifacts = ["locust/webui/dist"]
exclude = ["locust/webui/*", "locust/test", "locust/build"]
[tool.hatch.build.targets.wheel]
artifacts = ["locust/webui/dist"]
[tool.hatch.version.raw-options]
local_scheme = "no-local-version"
[tool.hatch.build.hooks.custom]
[tool.hatch.build.targets.BuildFrontend.hooks.custom]
[tool.ruff]
target-version = "py39"
line-length = 120
@ -222,3 +162,117 @@ python_version = "3.9"
exclude = ["locust/test"]
reportOptionalMemberAccess = "none"
analyzeUnannotatedFunctions = false
# Hatch environments
# List environments using `hatch env show`
# https://hatch.pypa.io/1.13/tutorials/environment/basic-usage/
## Local Testing
# Default hatch environment, used during local development
# List tests using `hatch test -s`
# https://hatch.pypa.io/1.13/tutorials/testing/overview/
[tool.hatch.envs.hatch-test]
default-args = ["locust/test"]
dependencies = [
"cryptography>=43.0.1,<44.0.0",
"mock>=5.1.0,<6.0.0",
"pyquery>=2.0.0,<3.0.0",
"pytest>=8.3.3,<9.0.0",
"retry>=0.9.2,<1.0.0",
]
[tool.hatch.envs.hatch-test.scripts]
run = [
"pytest{env:HATCH_TEST_ARGS:} {args}",
"bash -ec 'PYTHONUNBUFFERED=1 python3 examples/debugging_advanced.py | grep done'",
]
## CI testing
# Run all combinations: `hatch run test:all`
# Run single env: `hatch run +py=3.10 test:all`
[tool.hatch.envs.test]
template = "hatch-test"
[[tool.hatch.envs.test.matrix]]
python = ["3.9", "3.10", "3.11", "3.12", "3.13"]
## Run all unit tests and advanced debugging
[tool.hatch.envs.test.scripts]
# Disable these tests for now, because they are breaking. When someone has time they should be converted into regular unit tests
# bash -ec "PYTHONUNBUFFERED=1 timeout 20s python3 examples/rest.py >{temp_dir}/out.txt 2>/{temp_dir}/err.txt || true"
# grep -qm 1 'my custom error message with response text, response was {"args"' {temp_dir}/out.txt
# grep -qm 1 'ZeroDivisionError: division by zero at.*Response was {"ar' {temp_dir}/out.txt
# bash -ec '! grep . {temp_dir}/err.txt' # should be empty
all = [
"pytest {args:locust/test}",
"bash -ec 'PYTHONUNBUFFERED=1 python3 examples/debugging_advanced.py | grep done'",
]
# Run main test and bail on first error
fail_fast = "pytest -x {args:locust/test/test_main.py}"
## CI integration testing
## Integration testing of build packages on CI, using a pre-built package
[tool.hatch.envs.integration_test_ci]
template = "test"
skip-install = true
path = ".venv"
[[tool.hatch.envs.integration_test_ci.matrix]]
python = ["3.12"]
## Linting
[tool.hatch.envs.lint]
detached = true
dependencies = [
"ruff==0.7.3",
"mypy>=1.13.0,<2.0.0",
"types-requests>=2.32.0.20240622,<3.0.0",
]
[[tool.hatch.envs.lint.matrix]]
python = ["3.12"]
[tool.hatch.envs.lint.scripts]
all = [""]
types = ["mypy locust/"]
format = ["ruff check .", "ruff format --check"]
## Hatch docs building environment
[tool.hatch.envs.docs]
dependencies = [
"sphinx==7.3.7",
"sphinx-rtd-theme==2.0.0",
"readthedocs-sphinx-search==0.3.2",
"Sphinx-Substitution-Extensions==2020.9.30.0",
"Pygments==2.16.1",
"alabaster==0.7.16",
"babel==2.13.0",
"docutils==0.18.1",
"imagesize==1.4.1",
"snowballstemmer==2.2.0",
"sphinx-prompt==1.5.0",
"sphinxcontrib-applehelp==1.0.4",
"sphinxcontrib-devhelp==1.0.2",
"sphinxcontrib-htmlhelp==2.0.1",
"sphinxcontrib-jsmath==1.0.1",
"sphinxcontrib-qthelp==1.0.3",
"sphinxcontrib-serializinghtml==1.1.10",
"sphinxcontrib-googleanalytics>=0.4",
]
[[tool.hatch.envs.docs.matrix]]
python = ["3.12"]
[tool.hatch.envs.docs.scripts]
build = ["sphinx-build -b html docs/ docs/_build/"]

59
tox.ini
View File

@ -1,59 +0,0 @@
[tox]
envlist =
ruff
mypy
py{39,310,311,312,313}
fail_fast_test_main
[testenv]
package = skip
allowlist_externals =
bash
grep
poetry
timeout
ruff
mypy
sphinx-build
commands_pre =
# Ensures test dependencies are available to derivative envs
poetry install --no-interaction --no-root --with test --no-plugins
commands =
python -m unittest discover []
; Disable these tests for now, because they are breaking. When someone has time they should be converted into regular unit tests
; bash -ec "PYTHONUNBUFFERED=1 timeout 20s python3 examples/rest.py >{temp_dir}/out.txt 2>/{temp_dir}/err.txt || true"
; grep -qm 1 'my custom error message with response text, response was {"args"' {temp_dir}/out.txt
; grep -qm 1 'ZeroDivisionError: division by zero at.*Response was {"ar' {temp_dir}/out.txt
; bash -ec '! grep . {temp_dir}/err.txt' # should be empty
bash -ec 'PYTHONUNBUFFERED=1 python3 examples/debugging_advanced.py | grep done'
; Used for running integration tests on an externally built dist (CI)
[testenv:fail_fast_test_main_external_package]
package = external
package_glob = {toxinidir}{/}dist{/}*.whl
commands =
python -m unittest -f locust.test.test_main
; Used for running integration tests on a locally installed editable package on any OS
[testenv:fail_fast_test_main]
commands_pre =
poetry install --no-interaction --no-root --with test --no-plugins
python pre_build.py
pip install .
commands =
python -m unittest -f locust.test.test_main
[testenv:ruff]
commands =
ruff check .
ruff format --check
[testenv:mypy]
commands =
mypy locust/
[testenv:docs]
commands_pre =
poetry install --no-interaction --no-root --only docs --no-plugins
commands =
sphinx-build -b html docs/ docs/_build/

1784
uv.lock Normal file

File diff suppressed because it is too large Load Diff