Embed-friendly Python env/package scanner with zero deps.
This is primarily designed so that you can take src/picopip.py
and copy it into your own project to use it without having
to install dependencies.
You can use get_packages_from_env to list all installed packages in a Python virtual environment. Here's how to do it using Python's built-in venv module:
>>> from picopip import get_packages_from_env
>>>
>>> pkgs = get_packages_from_env(venvdir)
>>> print(pkgs)
[('certifi', '2025.4.26'), ('charset-normalizer', '3.4.2'), ('idna', '3.10'),
('pip', '21.2.4'), ('requests', '2.32.3'), ('setuptools', '58.0.4'),
('urllib3', '2.4.0')]get_packages_from_env(<venv_path>)returns a list of(name, version)tuples for all installed packages in the given virtual environment.- You can use any venv path, and the function will find all packages, including those installed via pip.
By default get_packages_from_env expects a full virtual environment layout
(lib/pythonX.Y/site-packages, .pth expansion, system and PYTHONPATH
discovery). If you instead installed packages into a flat directory with
pip install --target <dir>, pass path_as_target=True to scan that directory
directly.
pip install --target ./vendor requests>>> from picopip import get_packages_from_env
>>>
>>> pkgs = get_packages_from_env("./vendor", path_as_target=True)
>>> print(pkgs)
[('certifi', '2025.4.26'), ('charset-normalizer', '3.4.2'), ('idna', '3.10'),
('requests', '2.32.3'), ('urllib3', '2.4.0')]With path_as_target=True:
venv_pathis treated as the site-packages directory itself, so nolib/pythonX.Y/site-packagesstructure is required..pthfiles are not expanded andPYTHONPATHis not read.ignore_system_packagesis ignored (system packages are never scanned).
If you only need the version of a specific package, you can get it
using get_package_version_from_env
>>> from picopip import get_package_version_from_env
>>>
>>> version = get_package_version_from_env(venvdir, "pip")
>>> print(version)
'21.2.4'parse_version normalizes a version into a tuple that follows the same ordering
rules used by pip/packaging. The first element is the release components,
the second is an offset encoding pre/dev/post markers so you can compare the
tuples with standard operators.
>>> from picopip import parse_version
>>>
>>> parse_version("1.13.5")
((1, 13, 5), 0)
>>> parse_version("1.13.5a1") < parse_version("1.13.5")
True
>>> parse_version("1.13.5.post2") > parse_version("1.13.5")
Trueparse_constraints parses a requirement spec like "aiokafka >= 0.8, < 1.0"
into the package name and a list of (comparator, parsed_version) pairs.
Apply every pair to a parsed version to decide whether it satisfies the spec.
>>> from picopip import parse_constraints, parse_version
>>>
>>> name, constraints = parse_constraints("aiokafka >= 0.8, < 1.0")
>>> name
'aiokafka'
>>> v = parse_version("0.9.1")
>>> all(op(v, cv) for op, cv in constraints)
True
>>> v = parse_version("1.0.0")
>>> all(op(v, cv) for op, cv in constraints)
FalseThe name is None for bare specs (">= 0.8, < 1.0").
Releases are cut by pushing a N.N.N git tag. The Release workflow runs
lint and tests, then publishes a GitHub release with auto-generated notes.
-
Bump the
Version:line insrc/picopip.pyonmainand merge. -
Tag the release commit with the matching version and push the tag:
git tag 0.5.1 git push origin 0.5.1
The workflow fails if the tag does not match N.N.N or does not match the
Version: line in src/picopip.py, so both must be kept in sync.