Lock Files
==========

.. currentmodule:: packaging.pylock

Parse and validate `pylock.toml files <https://packaging.python.org/en/latest/specifications/pylock-toml/>`_.

.. versionadded:: 26.0

Usage
-----

.. code-block:: python

    import tomllib
    from pathlib import Path

    from packaging.pylock import Package, PackageWheel, Pylock, is_valid_pylock_path
    from packaging.utils import NormalizedName
    from packaging.version import Version

    # validate a pylock file name
    assert is_valid_pylock_path(Path("pylock.example.toml"))

    # parse and validate pylock file
    toml_dict = tomllib.loads(Path("pylock.toml").read_text(encoding="utf-8"))
    pylock = Pylock.from_dict(toml_dict)
    # the resulting pylock object is validated against the specification,
    # else a PylockValidationError is raised

    # generate a pylock file
    pylock = Pylock(
        lock_version=Version("1.0"),
        created_by="some_tool",
        packages=[
            Package(
                name=NormalizedName("example-package"),
                version=Version("1.0.0"),
                wheels=[
                    PackageWheel(
                        url="https://example.com/example_package-1.0.0-py3-none-any.whl",
                        hashes={"sha256": "0fd.."},
                    )
                ],
            )
        ],
    )
    toml_dict = pylock.to_dict()
    # use a third-party library to serialize to TOML

    # you can validate a manually constructed Pylock class
    pylock.validate()

    # select packages to install for the current environment
    for package, artifact in pylock.select():
        print(f"Install {package.name} from {artifact}")

Validation
----------

:meth:`Pylock.from_dict` and :meth:`Pylock.validate` check the structure of
the lock file against the specification. Required fields must be present,
fields must have the expected types, package and extra names must be
normalized, and versions, specifiers and markers must parse. Each package
must have exactly one source: distribution files (``sdist`` or ``wheels``),
or one of ``vcs``, ``directory`` or ``archive``. Wheel and sdist filenames
must be parseable (see :func:`~packaging.utils.parse_wheel_filename` and
:func:`~packaging.utils.parse_sdist_filename`) and match the package name
and version.

Validation stops at the structure. URL and path fields, including
``packages.index``, are stored as-is. They are not checked to be valid URLs
or paths, and relative paths are not resolved against the location of the
lock file. A wheel or sdist entry with no ``name`` takes its filename from
the last component of its ``path`` or ``url``; nothing else is read from
these fields. ``hashes`` tables must be non-empty and their values must be
strings, but algorithm names and digest formats are not checked. Nothing is
downloaded, so checking hashes and sizes against the actual artifacts is up
to the caller.

Reference
---------

.. autofunction:: is_valid_pylock_path

The following frozen keyword-only dataclasses are used to represent the
structure of a pylock file. The attributes correspond to the fields in the
pylock file specification.

.. autoclass:: Pylock
    :members: from_dict, to_dict, validate, select
    :exclude-members: __init__, __new__

.. class:: Package

.. class:: PackageWheel

.. class:: PackageSdist

.. class:: PackageArchive

.. class:: PackageVcs

.. class:: PackageDirectory

The following exceptions may be raised by this module:

.. autoexception:: PylockValidationError
    :exclude-members: __init__, __new__

.. autoexception:: PylockUnsupportedVersionError
    :exclude-members: __init__, __new__

.. autoexception:: PylockSelectError
    :exclude-members: __init__, __new__
