Skip to content

PEP 751 experimental pylock.toml support #6391

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ test = "pytest -vvs"

[pipenv]
allow_prereleases = true
use_pylock = true
258 changes: 135 additions & 123 deletions Pipfile.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ credentials
shell
docker
scripts
pylock
advanced
diagnose
changelog
Expand Down
89 changes: 89 additions & 0 deletions docs/pylock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# PEP 751 pylock.toml Support

Pipenv supports [PEP 751](https://peps.python.org/pep-0751/) pylock.toml files, which provide a standardized format for recording Python dependencies to enable installation reproducibility.

## What is pylock.toml?

The pylock.toml file is a standardized lock file format introduced in PEP 751. It is designed to be:

- Human-readable and machine-generated
- Secure by default (includes file hashes)
- Able to support both single-use and multi-use lock files
- Compatible across different Python packaging tools

## Using pylock.toml with Pipenv

Pipenv can automatically detect and use pylock.toml files in your project. When both a Pipfile.lock and a pylock.toml file exist, Pipenv will prioritize the pylock.toml file.

### Reading pylock.toml Files

When you run commands like `pipenv install` or `pipenv sync`, Pipenv will check for a pylock.toml file in your project directory. If found, it will use the dependencies specified in the pylock.toml file instead of Pipfile.lock.

Pipenv looks for pylock.toml files in the following order:
1. A file named `pylock.toml` in the project directory
2. A file matching the pattern `pylock.*.toml` in the project directory

### Example pylock.toml File

Here's a simplified example of a pylock.toml file:

```toml
lock-version = '1.0'
environments = ["sys_platform == 'win32'", "sys_platform == 'linux'", "sys_platform == 'darwin'"]
requires-python = '>=3.8'
extras = []
dependency-groups = []
default-groups = []
created-by = 'pipenv'

[[packages]]
name = 'requests'
version = '2.28.1'
requires-python = '>=3.7'

[[packages.wheels]]
name = 'requests-2.28.1-py3-none-any.whl'
upload-time = '2022-07-13T14:00:00Z'
url = 'https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c61441f4AE7b7338a82051330d70/requests-2.28.1-py3-none-any.whl'
size = 61805
hashes = {sha256 = 'b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7'}
```

## Benefits of Using pylock.toml

- **Standardization**: pylock.toml is a standardized format that can be used by multiple Python packaging tools.
- **Security**: pylock.toml includes file hashes by default, making it more secure against supply chain attacks.
- **Flexibility**: pylock.toml can support both single-use and multi-use lock files, allowing for more complex dependency scenarios.
- **Interoperability**: pylock.toml can be used by different tools, reducing vendor lock-in.

## Writing pylock.toml Files

Pipenv can generate pylock.toml files alongside Pipfile.lock files. To enable this feature, add the following to your Pipfile:

```toml
[pipenv]
use_pylock = true
```

With this setting, whenever Pipenv updates the Pipfile.lock file (e.g., when running `pipenv lock`), it will also generate a pylock.toml file in the same directory.

You can also specify a custom name for the pylock.toml file:

```toml
[pipenv]
use_pylock = true
pylock_name = "dev" # This will generate pylock.dev.toml
```

## Limitations

- Some advanced features of pylock.toml, such as environment markers for extras and dependency groups, are not fully supported yet.
- The generated pylock.toml files are simplified versions of what a full PEP 751 implementation might produce.

## Future Plans

In future releases, Pipenv plans to add support for:

- Full support for environment markers for extras and dependency groups
- More comprehensive conversion between Pipfile.lock and pylock.toml formats
- Command-line options for generating pylock.toml files
21 changes: 21 additions & 0 deletions examples/Pipfile.with_pylock
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
requests = "*"

[dev-packages]
pytest = "*"

[requires]
python_version = "3.8"

[pipenv]
# Enable pylock.toml generation
use_pylock = true

# Optional: Specify a custom name for the pylock file
# This will generate pylock.dev.toml instead of pylock.toml
# pylock_name = "dev"
69 changes: 69 additions & 0 deletions examples/pylock.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
lock-version = '1.0'
environments = ["sys_platform == 'win32'", "sys_platform == 'linux'", "sys_platform == 'darwin'"]
requires-python = '>=3.8'
extras = []
dependency-groups = []
default-groups = []
created-by = 'pipenv'

[[packages]]
name = 'requests'
version = '2.28.1'
requires-python = '>=3.7'

[[packages.wheels]]
name = 'requests-2.28.1-py3-none-any.whl'
upload-time = '2022-07-13T14:00:00Z'
url = 'https://files.pythonhosted.org/packages/ca/91/6d9b8ccacd0412c08820f72cebaa4f0c61441f4AE7b7338a82051330d70/requests-2.28.1-py3-none-any.whl'
size = 61805
hashes = {sha256 = 'b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7'}

[[packages]]
name = 'urllib3'
version = '1.26.12'
requires-python = '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4'

[[packages.wheels]]
name = 'urllib3-1.26.12-py2.py3-none-any.whl'
upload-time = '2022-09-08T14:30:00Z'
url = 'https://files.pythonhosted.org/packages/6f/de/5be2e3eed8426f871b170663333a0f627fc2924cc386cd41a2d2d3f1699/urllib3-1.26.12-py2.py3-none-any.whl'
size = 141862
hashes = {sha256 = 'b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997'}

[[packages]]
name = 'certifi'
version = '2022.9.24'

[[packages.wheels]]
name = 'certifi-2022.9.24-py3-none-any.whl'
upload-time = '2022-09-24T18:00:00Z'
url = 'https://files.pythonhosted.org/packages/1d/38/fa96a426e0c0e68aabc68e896584b83ad1eec779265a028e156ce509630/certifi-2022.9.24-py3-none-any.whl'
size = 161915
hashes = {sha256 = '0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14'}

[[packages]]
name = 'charset-normalizer'
version = '2.1.1'
requires-python = '>=3.6.0'

[[packages.wheels]]
name = 'charset_normalizer-2.1.1-py3-none-any.whl'
upload-time = '2022-08-05T12:00:00Z'
url = 'https://files.pythonhosted.org/packages/a1/34/44964211e5410b051e4b8d2869c470ae8a68ae274953b1c7de6d98bbcf94/charset_normalizer-2.1.1-py3-none-any.whl'
size = 39673
hashes = {sha256 = '83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f'}

[[packages]]
name = 'idna'
version = '3.4'

[[packages.wheels]]
name = 'idna-3.4-py3-none-any.whl'
upload-time = '2022-08-12T15:00:00Z'
url = 'https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl'
size = 61545
hashes = {sha256 = '90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2'}

[tool.pipenv]
generated_from = "Pipfile.lock"
generation_date = "2025-04-25T04:20:00Z"
5 changes: 5 additions & 0 deletions news/7751.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Added support for PEP 751 pylock.toml files:

- Reading: When both a Pipfile.lock and a pylock.toml file exist, Pipenv will prioritize the pylock.toml file.
- Writing: Add ``use_pylock = true`` to the ``[pipenv]`` section of your Pipfile to generate pylock.toml files alongside Pipfile.lock.
- Customization: Use ``pylock_name = "name"`` in the ``[pipenv]`` section to generate named pylock files (e.g., pylock.name.toml).
53 changes: 53 additions & 0 deletions pipenv/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
proper_case,
)
from pipenv.utils.locking import atomic_open_for_write
from pipenv.utils.pylock import PylockFile, find_pylock_file
from pipenv.utils.project import get_default_pyproject_backend
from pipenv.utils.requirements import normalize_name
from pipenv.utils.shell import (
Expand Down Expand Up @@ -793,6 +794,19 @@ def _pipfile(self):
pf = ReqLibPipfile.load(self.pipfile_location)
return pf

@property
def pylock_location(self):
"""Returns the location of the pylock.toml file, if it exists."""
pylock_path = find_pylock_file(self.project_directory)
if pylock_path:
return str(pylock_path)
return None

@property
def pylock_exists(self):
"""Returns True if a pylock.toml file exists."""
return self.pylock_location is not None

@property
def lockfile_location(self):
return f"{self.pipfile_location}.lock"
Expand All @@ -803,6 +817,15 @@ def lockfile_exists(self):

@property
def lockfile_content(self):
"""Returns the content of the lockfile, checking for pylock.toml first."""
if self.pylock_exists or self.use_pylock:
try:
if self.pylock_exists:
pylock = PylockFile.from_path(self.pylock_location)
lockfile_data = pylock.convert_to_pipenv_lockfile()
return lockfile_data
except Exception as e:
err.print(f"[bold yellow]Error loading pylock.toml: {e}[/bold yellow]")
return self.load_lockfile()

def get_editable_packages(self, category):
Expand Down Expand Up @@ -986,8 +1009,22 @@ def write_toml(self, data, path=None):
with open(path, "w", newline=newlines) as f:
f.write(formatted_data)

@property
def use_pylock(self) -> bool:
"""Returns True if pylock.toml should be generated."""
return self.settings.get("use_pylock", False)

@property
def pylock_output_path(self) -> str:
"""Returns the path where pylock.toml should be written."""
pylock_name = self.settings.get("pylock_name")
if pylock_name:
return str(Path(self.project_directory) / f"pylock.{pylock_name}.toml")
return str(Path(self.project_directory) / "pylock.toml")

def write_lockfile(self, content):
"""Write out the lockfile."""
# Always write the Pipfile.lock
s = self._lockfile_encoder.encode(content)
open_kwargs = {"newline": self._lockfile_newlines, "encoding": "utf-8"}
with atomic_open_for_write(self.lockfile_location, **open_kwargs) as f:
Expand All @@ -997,6 +1034,22 @@ def write_lockfile(self, content):
if not s.endswith("\n"):
f.write("\n")

# If use_pylock is enabled, also write a pylock.toml file
if self.use_pylock:
try:
from pipenv.utils.pylock import PylockFile

pylock = PylockFile.from_lockfile(
lockfile_path=self.lockfile_location,
pylock_path=self.pylock_output_path,
)
pylock.write()
err.print(
f"[bold green]Generated pylock.toml at {self.pylock_output_path}[/bold green]"
)
except Exception as e:
err.print(f"[bold red]Error generating pylock.toml: {e}[/bold red]")

def pipfile_sources(self, expand_vars=True):
if self.pipfile_is_empty or "source" not in self.parsed_pipfile:
sources = [self.default_source]
Expand Down
7 changes: 6 additions & 1 deletion pipenv/routines/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,8 +478,13 @@ def do_install_dependencies(
lockfile_category = get_lockfile_section_using_pipfile_category(
pipfile_category
)
lockfile_type = (
"pylock.toml"
if project.use_pylock and project.pylock_location
else "Pipfile.lock"
)
console.print(
f"Installing dependencies from Pipfile.lock [{lockfile_category}]"
f"Installing dependencies from {lockfile_type} [{lockfile_category}]"
f"({lockfile['_meta'].get('hash', {}).get('sha256')[-6:]})...",
style="bold",
)
Expand Down
Loading
Loading