Skip to content

Add env lock command to generate PEP compliant lock files.#2216

Open
cjames23 wants to merge 18 commits intopypa:masterfrom
cjames23:env-lock-feature
Open

Add env lock command to generate PEP compliant lock files.#2216
cjames23 wants to merge 18 commits intopypa:masterfrom
cjames23:env-lock-feature

Conversation

@cjames23
Copy link
Copy Markdown
Member

@cjames23 cjames23 commented Mar 26, 2026

This PR replaces #2174 which had been closed due to having to delete my fork to get back to a clean state for creating a release.

The latest changes made ensure dependency-groups and extras are passed through for lockfile generation as well.

Closes #716 #749 #2176

Includes AI generated content.

@flying-sheep
Copy link
Copy Markdown
Contributor

Awesome! I think the only thing missing is some tests of the actual lockfile, unless I’m missing something. E.g. that envs.some-env.dependencies end up in it and that that f14a893 works as expected.

@gabrielsimoes
Copy link
Copy Markdown

@flying-sheep @cjames23 firstly, thank you for your work here.

Are there plans to support generate_lockfile as a method in EnvironmentInterface so that plugins can implement custom behavior (e.g. populate [tool] with custom metadata)?

If this is not in the plans, would you be open to a contribution here? (once this PR merges of course) I can open another issue if this is best tracked separately.

@cjames23
Copy link
Copy Markdown
Member Author

Awesome! I think the only thing missing is some tests of the actual lockfile, unless I’m missing something. E.g. that envs.some-env.dependencies end up in it and that that f14a893 works as expected.

Nope not missing anything. I am working on those tests right now :) I was just getting the PR back open for us to continue discussing and iterating over it.

@ofek
Copy link
Copy Markdown
Contributor

ofek commented Mar 31, 2026

Are there plans to support generate_lockfile as a method in EnvironmentInterface so that plugins can implement custom behavior (e.g. populate [tool] with custom metadata)?

Perhaps useful for @cjames23 to know, there are other ways to go about implementing pluggable lock file support. I proposed a potential design in #355. The read/write methods would no longer be required though because I landed on a much better (imo) abstraction with the FileSystemContext as returned by an environment's EnvironmentInterface.fs_context method.

The idea was that if an environment interface exposed filesystem synchronization primitives then dependency lockers could merely use that and be its own type of plugin. I thought that it would be nice to have them decoupled, to a certain extent. Lockers would also call other environment methods that are part of the public API but I view that as way cleaner and easier to implement than a locker having to subclass an environment interface just to change a bit of logic.

This was years ago when I was in peak Hatch design mode so my gut instinct tells me that paradigm is directionally correct. However, I would prefer providing value to users sooner rather than later so it's totally your call! Whatever you do is fine by me; just let me know when to review 🙂

Copy link
Copy Markdown
Contributor

@ofek ofek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Feel free to merge after addressing the docs issues.

@@ -0,0 +1,150 @@
# How to use lockfiles
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a tutorial.


## Dependency lockers

**Dependency lockers** are implemented: register classes with `hatch_register_locker` (see [Dependency locker plugins](../locker.md)). They stay **decoupled** from environment plugins—no subclassing [`EnvironmentInterface`](#hatch.env.plugin.interface.EnvironmentInterface) just to change locking—and call the environment’s **public API** (for example [`command_context`](#hatch.env.plugin.interface.EnvironmentInterface.command_context) and [`platform`](../utilities.md)).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AI heavily over indexed on the comment I made and the associated discussion. Let's remove the second sentence so the docs overall make more sense here.

Comment on lines +24 to +33
## Interface

Implement [`LockerInterface`](https://github.com/pypa/hatch/blob/master/src/hatch/env/lockers/interface.py) with:

- `PLUGIN_NAME: str` — selector (`uv`, `pip`, or your plugin name).
- `supports(environment) -> bool`
- `generate(environment, dependencies: list[str], output_path, *, upgrade=..., layered=..., lock_extras=..., lock_groups=..., upgrade_packages=...)`
- `in_sync(environment, dependencies: list[str], output_path, *, same flags as generate) -> bool`
- `apply_lock(environment, lock_path)`
- Optional override: `install_matches_lock(environment, lock_path) -> bool` for `dependencies_in_sync` when `locked` (default `True`; UV uses `uv pip sync --dry-run`).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section I think is entirely out of place and doesn't match the flow of the other plugin pages. We should remove this I think.

Also, we shouldn't link to source code directly with URLs.

Comment on lines +35 to +43
## Conceptual mapping

| Idea | Hatch |
| ---- | ----- |
| `generate(dependencies: list[str])` → resolve → persist | `generate(environment, dependencies, output_path, *, …)` — orchestrator supplies merged PEP 508 lines plus layered flags (`lock_extras`, `lock_groups`, `upgrade`, …). |
| `in_sync(dependencies) -> bool` | `in_sync(environment, dependencies, output_path, *, …) -> bool` |
| `write_file` / `read_file` on the environment | Lockfiles normally live on the project root: read/write `output_path` ([`Path`](../plugins/utilities.md)). For remote or isolated storage, use [`fs_context`](../plugins/environment/reference.md#hatch.env.plugin.interface.EnvironmentInterface.fs_context) to stage paths and `sync_local` / `sync_env` before or after resolver commands. |
| `run_shell_command` under `command_context` | Run resolver/install commands inside `with environment.command_context():` using `environment.platform.check_command` / `run_command` (same pattern as built-in lockers). |
| Apply resolved graph to the environment | `apply_lock(environment, lock_path)` — e.g. `uv pip sync` for the UV locker. |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also doesn't really flow but I'm kinda at a loss because either AI assists and reduces the quality of the documentation or we just aren't able to land features due to time constraints we have in real life.

Note to self that we need to add AI instructions to the repo for better alignment.

Comment on lines +54 to +58
upgrade: bool = False,
upgrade_packages: tuple[str, ...] = (),
layered: bool = False,
lock_extras: tuple[str, ...] = (),
lock_groups: tuple[str, ...] = (),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with merging but do you think we should hold off on adding so many options until we get feedback on the workflow?

)


verify_lockfile = lockfile_in_sync # design-doc name for :func:`lockfile_in_sync`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the comment?


stub_pylock = "lock-version = 1\n"

def fake_subprocess_run(cmd, *_args, **_kwargs) -> subprocess.CompletedProcess:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there must be a less invasive approach for this subset of tests.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me think on this one a bit more. This was the first thing that came to mind.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Support dependency hashing

4 participants