Skip to main content

DuckTools: Env

ducktools-env intends to provide a few tools to aid in running and distributing applications and scripts written in Python that require additional dependencies.

Why wouldn't I just use uv/pipx/hatch/... to run scripts

You probably would, I wrote the core of this before those had support for inline metadata back when it was based on the since rejected PEP-722. It has a few additional features I like and had a few features before they were implemented elsewhere.

  1. There's a script registry so you can access scripts from anywhere without them needing to be on PATH
  2. lockfiles are basic requirements.txt format rather than the custom uv.lock format
    • Ideally this will eventually be replaced by the pylock.toml format once pip can install from it directly.
  3. It is possible to bundle ducktools-env into a zipapp with the script so the user does not need to have ducktools-env installed locally.
    • Additional data can also be bundled into the zipapp

What is this for?

Suppose you have a Python script that you wish to share with someone else, but it relies on a third party dependency such as requests. In order for someone else to run your code they need to both have an appropriate version of Python and to create a virtual environment in which to install requests and subsequently run your script.

PEP-723 introduced inline script metadata which allows users to declare dependencies for single python files in a standardized format. This is designed to make sharing scripts with PyPI dependencies easier as now the script can define its own requirements.

However, using this format requires the use of an extra package such as 'UV' or 'hatch' using a specific command such as uv run my_script.py or hatch run my_script.py.

ducktools-env is designed to bundle your script into a Python zipapp which can be run by any Python 3.12+ install and will handle creating the virtualenv and launching the script with the appropriate dependencies without needing the other user to have any specific script running tool installed.

To aid this, ducktools-env provides the bundle and run commands.

ducktools-env run my_script.py

Will run your script much like some of the other script runners.

ducktools-env bundle my_script.py

Will then generate a zipapp bundle of your script and the required tools to extract and execute it in the same way as it is executed via the run command.

The resulting bundle will include ducktools-env and the pip zipapp in order to bootstrap the unbundling process.

What if the user does not have Python installed

Running the bundle requires the user to have an install of Python 3.12 or later. This should be available via python.org with installers for Windows/Mac and either already included or available from any up to date Linux distribution. This is all that should be needed for your script to run.

The version of Python that will actually be used to build the environment will be the latest version that can be found via ducktools-pythonfinder that satisfies the requires-python specification.

Where is data stored?

Environment data and the application itself will be stored in the following locations:

  • Windows: %LOCALAPPDATA%\ducktools\env
  • Linux/Mac/Other:
    • Data: ~/.local/share/ducktools/env
    • Config: ~/.config/ducktools/env (Not yet used)

Usage

The tool can be used in multiple ways:

  • Installed via uv tool (or pipx)
    • uv tool install ducktools-env
    • ducktools-env <command>
    • This adds the dtrun shortcut for ducktools-env run
  • Executed from the zipapp
  • Installed in an environment
    • Download with pip or uv in a virtual environment: pip install ducktools-env
    • Run with: ducktools-env <command>
    • The dtrun shortcut is also available
  • Accessed directly via uvx with uv
    • uvx ducktools-env <command>
    • No access to the dtrun shortcut this way

These examples will use the ducktools-env command as the base as if installed via uv tool or a similar tool.

Run a script that uses inline script metadata:

ducktools-env run my_script.py

If installed via uv, pipx or pip there is an alias dtrun for this command. Unlike the full command it does not accept optional arguments and all arguments are passed on to the script.

dtrun my_script.py

Bundle the script into a zipapp:

ducktools-env bundle my_script.py

Clear the temporary environment cache:

ducktools-env clear_cache

Clear the full ducktools/env install directory:

ducktools-env clear_cache --full

Build the env folder from the installed package:

ducktools-env rebuild_env

Registering scripts

It is also now possible to register scripts with ducktools-env.

ducktools-env register path/to/my_script.py

which can then be run by using the script name without the extension:

ducktools-env run my_script or dtrun my_script

Locking environments

When generating zipapp bundles it may be desirable to also generate a lockfile to make sure that the versions of installed dependencies do not change between generation and execution without having to over specify in the original script.

This generation feature requires uv be installed in order to create a universal requirements.txt format lockfile. As such uv is not required to use the generated lockfile, only to create it.

Create a lockfile without running a script:

ducktools-env generate_lock my_script.py

Run a script and output the generated lockfile (output as my_script.py.dtenv.lock):

ducktools-env run --generate-lock my_script.py (--generate-lock does not work with dtrun)

Run a script using a pre-generated lockfile:

ducktools-env run --with-lock my_script.py.dtenv.lock my_script.py

If a my_script.py.dtenv.lock file is found for a script it will automatically be used without needing to be specified

Bundle a script and generate a lockfile (that will be bundled):

ducktools-env bundle --generate-lock my_script.py

Bundle a script with a pre-generated lockfile:

ducktools-env bundle --with-lock my_script.py.dtenv.lock my_script.py

If a my_script.py.dtenv.lock file exists it will automatically be used in the bundle also.

The lockfile extension is now .dtenv.lock as uv will try to use a .lock file if it exists and uses its own tool-specific lockfile format. To avoid a clash with uv this was renamed.

Including data files with script bundles

If you wish to include data files with your script you can do so using a tool table in the toml block.

# /// script
# requires-python = ">=3.10"
# dependencies = ["cowsay"]
#
# [tool.ducktools.env]
# include.data = ["path/to/folder", "path/to/file.txt"]
# ///

If this is made into a bundle these files and folders will be collected into a bundle_data folder included in the zipapp.

This data can be retrieved on demand using get_data_folder from ducktools.env.bundled_data which will create a temporary folder containing a copy of the data files and return the path to the folder.

Note: Paths are relative to the script folder. If you include a folder, the folder itself will be included, not just its contents. This means that if you include ./ you will get the name of the folder the script is in (along with all of its contents).

This can be used to include additional code by inserting the relevant folder into sys.path before executing the body of a script.

# /// script
# requires-python = ">=3.12"
# dependencies = ["ducktools-env>=0.1.0"]
#
# [tool.ducktools.env]
# include.data = ["./"]
# include.license = ["license.md"]
# ///
from pathlib import Path

from ducktools.env.bundled_data import get_data_folder

with get_data_folder() as fld_name:
    for f in Path(fld_name).rglob("*"):
        print(f)

Application Environments

If you wish your script to persist as an "application" you can define 'owner', 'name' and 'version' fields.

These environments require generation of a lockfile.

A new version of the application will update the environment to depend on that version. The environment will be rebuilt if the lockfile is updated on updating to a new version. If the lockfile has changed but the version has not, running the application will fail (unless the version is a pre-release). Old versions will also fail to run if the environment has been created for a new version.

# /// script
# requires-python = ">=3.8.0"
# dependencies = ["cowsay"]
# [tool.ducktools.env]
# app.owner = "ducktools_testing"
# app.name = "cowsay_example"
# app.version = "0.1.0"
# ///

from cowsay.__main__ import cli

if __name__ == "__main__":
    cli()

Listing and deleting environments

Existing environments can be listed with the command

ducktools-env list

and deleted with

ducktools-env delete_env <envname>

where <envname> is the name of a temporary environment or the combination owner/name of an application environment as shown in the list.

Goals

Future goals for this tool:

  • Optionally bundle requirements inside the zipapp for use as offline bundles.

Dependencies

Currently ducktools.env relies on the following tools.

Subprocesses:

  • venv via subprocess on python installs
  • pip as a zipapp via subprocess as the dependency installer (the zipapp will be downloaded automatically)
    • This is done rather than including pip as a dependency to avoid a clash with pip installed in an environment
  • uv to generate lockfiles
    • This requires that the user has uv installed and on PATH

PyPI:

  • ducktools-classbuilder (A lazy, faster implementation of the building blocks behind things like dataclasses)
    • reannotate (for correctly handling Python 3.14 annotations)
  • ducktools-lazyimporter (A simple class based tool to handle deferred imports)
  • ducktools-scriptmetadata (The parser for inline script metadata blocks)
  • ducktools-pythonfinder (A tool to discover python installs available for environment creation)
  • packaging (for comparing dependency lists to cached environments)

Other tools in this space

zipapp

The standard library zipapp is at the core of how ducktools-env works. However it doesn't support running with C extensions and it has no inbuilt way to control which Python it will run under.

By contrast ducktools-env will respect a specified python version and required extensions, these can be bundled or downloaded on first launch via pip.

Shiv

shiv allows you to bundle zipapps with C extensions, but doesn't provide for any online installs and will extract everything into one ~/.shiv directory unless otherwise specified. At the time of writing support for using inline script metadata has not yet been merged but there is a PR to add support.

ducktools-env creates and manages virtual environments for each unique set of script requirements. These are kept in more platform specific directories documented earlier in the readme.

Pex

Pex provides an assortment of related tools for developers alongside a .pex bundler. It has (undocumented) support for inline script metadata for building its archives and will bundle dependencies including C extensions inside the archive, with the option to also include a Python runtime. It does not support online installs, so archives may be platform dependent or large.

PyInstaller

Pyinstaller will generate an executable from your script but will also bundle all of your dependencies in a platform specific way. It also bundles Python itself, which while convenient if python is not installed, is unnecessary if we can treat Python as a shared library.

Hatch

Hatch allows you to run scripts with inline dependencies, but requires the user on the other end already have hatch installed. The goal of ducktools-env is to make it so you can quickly bundle the script into a zipapp that will work on the other end with only Python as the requirement.

pipx

pipx is another tool that allows you to install packages from PyPI and run them as applications based on their [project.scripts] and [project.gui-scripts]. It also allows you to run inline scripts with more recent versions.

uv

uv can run PEP-723 scripts directly. ducktools-env mostly still exists for the extra zipapp bundling and script registry tools.

Metadata

Release files for ducktools-env 0.5.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ducktools-env 0.5.2
File Size Uploaded Provenance Actions
ducktools_env-0.5.2.tar.gz 3.6 MB With provenance Details for ducktools_env-0.5.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for ducktools-env 0.5.2
File Interpreter ABI Platform Actions
ducktools_env-0.5.2-py3-none-any.whl Python 3 none any Details for ducktools_env-0.5.2-py3-none-any.whl

Total release size: 3.6 MB

Release files / ducktools_env-0.5.2.tar.gz

Download URL ducktools_env-0.5.2.tar.gz
Size 3.6 MB
Tags Source
SHA-256 checksum
How to use checksums
3da4722572bcf9dde36b5a5c510191b766b0c3df01edd08413fce5452adbf9a4
BLAKE2b-256 checksum
How to use checksums
a6e981ee927628d2832db53577dc7cc6c556e0bb2244f6a2dc23dae5f91aed95
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / ducktools_env-0.5.2-py3-none-any.whl

Download URL ducktools_env-0.5.2-py3-none-any.whl
Size 61.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ad8a6bf1b1885c6c443edf50948baaedde225ae4974771113251fcaefc2bc482
BLAKE2b-256 checksum
How to use checksums
ce6e562b8e07076851af1fbf545f19111ca353105aa93855062324b7d38203da
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Security for ducktools-env 0.5.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for ducktools_env-0.5.2.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.5.2 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page