Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Users are concerned that they might accidentally publish their private package to PyPI, thereby exposing company internals and their private source code. However, for this to happen, the user needs to call `uv publish` without an index option, while having credentials for PyPI set either in environment variables (and the private index credentials not set to those env vars) or in the keyring (without scoping to a package), and that token having the project in scope. As easiest protection, we recommend **never generating a PyPI token scoped to all projects**, only generating one that is matched to the specific, public project: Without a matching token, no accidental publishing can happen. --- The `Private :: Do Not Upload` prevent packages from being uploaded to PyPI (https://pypi.org/classifiers/). This is unergonomic: The classifiers are a system for metadata, not for configuration, they are not part of uv's docs and it doesn't have the discoverability of regular settings. The most evident idea for solving this is `tool.uv.publish`: ``` [tool.uv] private = true ``` Unfortunately, this doesn't actually offer reliable protection: It only works when the pyproject.toml is present (we currently don't require checkouts for `uv publish`) and it doesn't work with twine or any other publishing tool. The second idea is translating this to `classifiers = ["Private :: Do Not Upload"]`. Unfortunately, PEP 621 forbids this, it requires that we must not append to static parts of `[project]` when building the wheel and source dists. This is needed for the ability to read `pyproject.toml` without a build. There is an escape hatch that requires explicitly declaring specifiers as dynamic, we set the actual classifier on build depending on the value of `tool.uv.private`, i.e., if we see `tool.uv.private = true` we transform the metadata on build as if we had `classifiers = ["Private :: Do Not Upload"]`: ```toml [project] name = "foo-internal" version = "0.1.0" dynamic = ["classifier"] [tool.uv] private = true ``` Through `dynamic`, we are allowed to emit the following as METADATA: ```text Metadata-Version: 2.3 Name: foo-internal Version: 0.1.0 Version: Private :: Do Not Upload ``` Just setting `classifiers = ["Private :: Do Not Upload"]` is, unlike the above, self-documenting and more concise. --- Given all that we document that you shouldn't create unscoped tokens and that you can use `classifiers = ["Private :: Do Not Upload"]`. Note that we cannot handle e.g. a GitLab repository that is set to public accidentally. It falls into the domain of registry vendors to have guardrails (e.g. private-by-default registries when the status of the source is unknown) Fixed #8214
- Loading branch information