Development

Getting Started

(With thanks to pip’s Getting Started guide for the general structure here!)

This document aims to get you setup to work on venvstacks and to act as a guide and reference to the development setup. If you face any issues during this process, please open an issue about it on the issue tracker.

Get the source code

To work on venvstacks, you first need to get the source code. The source code is available on GitHub.

$ git clone https://github.com/lmstudio-ai/venvstacks
$ cd venvstacks

Development Environment

In order to work on venvstacks, you need to install pdm, tox, tox-pdm, and scriv (everything else can be executed via tox environments).

Given these tools, the default development environment can be set up and other commands executed as described below.

Running from the source tree

To run venvstacks from your source tree during development, use pdm to set up an editable install in the default venv:

$ pdm sync --dev

venvstacks can then be executed with pdm run:

$ pdm run venvstacks --help

 Usage: venvstacks [OPTIONS] COMMAND [ARGS]...

 Lock, build, and publish Python virtual environment stacks.

╭─ Options ───────────────────────────────────────────────────────────────────────╮
│ --help          Show this message and exit.                                     │
╰─────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ──────────────────────────────────────────────────────────────────────╮
│ build          Build (/lock/publish) Python virtual environment stacks.         │
│ local-export   Export layer environments for Python virtual environment stacks. │
│ lock           Lock layer requirements for Python virtual environment stacks.   │
│ publish        Publish layer archives for Python virtual environment stacks.    │
╰─────────────────────────────────────────────────────────────────────────────────╯

Building Documentation

pip’s documentation is built using Sphinx. The documentation is written in reStructuredText.

To build it locally, run:

$ tox -e docs

The built documentation can be found in the docs/_build folder.

Changelog Entries

The venvstacks changelog is managed with scriv.

All changes which may affect venvstacks users should be given a user facing changelog entry with scriv create.

Entries are written in .rst format by default, so they can use semantic references to the rest of the documentation. However, .md fragments are entirely fine if internal semantic links aren’t needed.

Refer to the “per-user” settings in the scriv documentation for details on how to customise the local behaviour of scriv create.

The project level scriv settings are stored in pyproject.toml (but the project largely relies on the default settings)

Automated Testing

Code consistency checks

The project source code is autoformatted and linted using ruff. It also uses mypy in strict mode to statically check that Python APIs are being accessed as expected.

All of these commands can be invoked via tox:

$ tox -e format
$ tox -e lint
$ tox -e typecheck

Note

Avoid using # noqa comments to suppress these warnings - wherever possible, warnings should be fixed instead. # noqa comments are reserved for rare cases where the recommended style causes severe readability problems, and there isn’t a more explicit mechanism (such as typing.cast) to indicate which check is being skipped.

# fmt: off/on and # fmt: skip comments may be used as needed when the autoformatter makes readability worse instead of better (for example, collapsing lists to a single line when they intentionally cover multiple lines, or breaking alignment of end-of-line comments).

Running tests locally

The project’s tests are written using the pytest test framework and the standard library’s unittest module. tox is used to automate the setup and execution of these tests across multiple Python versions.

Some of the tests build and deploy full environment stacks, which makes them take a long time to run (5+ minutes for the sample project build and export, even with fully cached dependencies).

Local test runs skip these slow tests by default, but they can be specifically requested by overriding the default positional arguments in the tox command.

For example, this will run just the slow tests using the default testing environment:

$ tox -m test -- -m "slow"

The example above runs tests against the default Python version configured in tox.ini. You can also use other defined versions by specifying the target environment directly:

$ tox -e py3.11

There are additional labels defined for running the oldest test environment, the latest test environment, and all test environments:

$ tox -m test_oldest
$ tox -m test_latest
$ tox -m test_all

tox has been configured to forward any additional arguments it is given to pytest (as shown in the slow test example). This enables the use of pytest’s rich CLI. In particular, you can select tests using all the options that pytest provides:

$ # Using file name
$ tox -m test -- tests/test_basics.py
$ # Using markers
$ tox -m test -- -m "slow"
$ # Using keyword text search
$ tox -m test -- -k "lock and not publish"

Keep in mind when doing this that the arguments given will replace the default -m "not slow" test marker filtering, so remember to include that explicitly when it is still desired.

Additional notes on running and updating the tests can be found in the testing README file.

Tests with committed expected output

The “sample project” test cases primarily work by checking that relocking and rebuilding the sample project produces the same locked requirements files and the same publication metadata.

This means those test cases will fail when the expected output is changed intentionally, such as choosing a new baseline date for the sample project lockfiles, adding new fields to the expected metadata, or changing the expected contents of the defined environment layers.

PRs that modify the tests/expected-output-config.yml file will trigger a GitHub workflows that checks all other tests pass, and then generates a new PR targeting the triggering PR branch. The changes to the expected output files can then be reviewed to confirm they match the expected impact of the changes that were (for example, launch module changes should only affect the hashes and sizes of the application layer archives that include those launch modules).

If the original PR is not correct, then it can be retriggered by closing and reopening the PR once the relevant fixes have been implemented.

Release Management

Version Numbering

Until the Python API has stabilised, venvstacks is using ZeroVer (starting from 0.1.0).

The versioning scheme to be used after the leading zero is dropped has not yet been decided (see Versioning for some of the options being considered).

Except for when a release is being prepared, the nominal version on main will have .dev0 appended to indicate it is not a release build.

Most releases are expected to be published directly without a prior release candidate build, but one may be used if it is deemed necessary (for example, 0.1.0rc1 was published in order to test the release pipeline prior to publishing 0.1.0).

Preparing New Releases

Prior to release:

  • Update the version in pyproject.toml to remove the pre-release suffix

  • Run scriv collect to update CHANGELOG.rst

  • Commit and push the updated version number and collected change log updates

  • Check the updated docs after the PR has been merged

Release (requires pandoc and a GitHub access token with release permissions):

  • Use misc/tag-release.sh to create an annotated tag for the current version

  • Push the tag to the remote repo

  • Run scriv github-release --dry-run to check what would be published

  • Run scriv github-release to make the release from the annotated tag

After release:

  • Check the release GitHub Action has published to PyPI correctly

  • Bump the version in pyproject.toml and add a .dev0 suffix