uvault 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uvault-0.2.0/.github/workflows/ci.yml +34 -0
- uvault-0.2.0/.github/workflows/release.yml +30 -0
- uvault-0.2.0/.gitignore +13 -0
- uvault-0.2.0/.pre-commit-config.yaml +14 -0
- uvault-0.2.0/.python-version +1 -0
- uvault-0.2.0/LICENSE +21 -0
- uvault-0.2.0/PKG-INFO +23 -0
- uvault-0.2.0/README.md +13 -0
- uvault-0.2.0/docs/how-to.md +84 -0
- uvault-0.2.0/docs/index.md +42 -0
- uvault-0.2.0/docs/reference.md +100 -0
- uvault-0.2.0/mkdocs.yml +15 -0
- uvault-0.2.0/pyproject.toml +53 -0
- uvault-0.2.0/src/uvault/__init__.py +1 -0
- uvault-0.2.0/src/uvault/add.py +108 -0
- uvault-0.2.0/src/uvault/cli.py +87 -0
- uvault-0.2.0/src/uvault/develop.py +124 -0
- uvault-0.2.0/src/uvault/sync.py +228 -0
- uvault-0.2.0/src/uvault/vcs.py +281 -0
- uvault-0.2.0/tests/test_add.py +167 -0
- uvault-0.2.0/tests/test_cli.py +102 -0
- uvault-0.2.0/tests/test_develop.py +364 -0
- uvault-0.2.0/tests/test_sync.py +594 -0
- uvault-0.2.0/uv.lock +1398 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v6
|
|
17
|
+
|
|
18
|
+
- name: Install uv
|
|
19
|
+
uses: astral-sh/setup-uv@v8.2.0
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
enable-cache: true
|
|
23
|
+
|
|
24
|
+
- name: Run tests and coverage
|
|
25
|
+
run: uv run --frozen pytest
|
|
26
|
+
|
|
27
|
+
pre-commit:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v6
|
|
31
|
+
- uses: actions/setup-python@v6
|
|
32
|
+
with:
|
|
33
|
+
python-version: "3.13"
|
|
34
|
+
- uses: pre-commit/action@v3.0.1
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*.*.*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
name: Upload release to PyPI
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
environment:
|
|
13
|
+
name: pypi
|
|
14
|
+
url: https://pypi.org/p/uvault
|
|
15
|
+
permissions:
|
|
16
|
+
id-token: write
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v6
|
|
19
|
+
|
|
20
|
+
- name: Install uv
|
|
21
|
+
uses: astral-sh/setup-uv@v8.2.0
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.13"
|
|
24
|
+
enable-cache: true
|
|
25
|
+
|
|
26
|
+
- name: Build package
|
|
27
|
+
run: uv build
|
|
28
|
+
|
|
29
|
+
- name: Publish to PyPI
|
|
30
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
uvault-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
+
rev: v4.6.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: trailing-whitespace
|
|
6
|
+
- id: end-of-file-fixer
|
|
7
|
+
- id: check-yaml
|
|
8
|
+
- id: check-toml
|
|
9
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
10
|
+
rev: v0.4.8 # Matches compatible pre-commit version hook, will auto-update
|
|
11
|
+
hooks:
|
|
12
|
+
- id: ruff
|
|
13
|
+
args: [ --fix ]
|
|
14
|
+
- id: ruff-format
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
uvault-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pierre Verkest
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
uvault-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: uvault
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Development and vaulting workflow for uv VCS dependencies.
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: diskcache>=5.6.3
|
|
8
|
+
Requires-Dist: tomlkit>=0.15.0
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# uvault
|
|
12
|
+
|
|
13
|
+
Development and vaulting workflow for [uv](https://github.com/astral-sh/uv) VCS dependencies.
|
|
14
|
+
|
|
15
|
+
Secure your Python projects against deleted or force-pushed upstream commits. `uvault` automatically vaults transient VCS references (like GitHub PRs) into your organization's own repositories, while allowing developers to instantly switch dependencies into local editable mode—fully integrated with `pyproject.toml` and `uv`.
|
|
16
|
+
|
|
17
|
+
## Documentation
|
|
18
|
+
|
|
19
|
+
The complete documentation is available in the `docs/` folder:
|
|
20
|
+
|
|
21
|
+
* [Quickstart & Key Features](docs/index.md) - Learn what `uvault` is and how to get started quickly.
|
|
22
|
+
* [How-To Guides](docs/how-to.md) - Step-by-step guides for installing and using `uvault` in your day-to-day workflow.
|
|
23
|
+
* [CLI & Configuration Reference](docs/reference.md) - Detailed information on `pyproject.toml` configuration (`[tool.uvault]`) and all CLI commands (`sync`, `add`, `develop`).
|
uvault-0.2.0/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# uvault
|
|
2
|
+
|
|
3
|
+
Development and vaulting workflow for [uv](https://github.com/astral-sh/uv) VCS dependencies.
|
|
4
|
+
|
|
5
|
+
Secure your Python projects against deleted or force-pushed upstream commits. `uvault` automatically vaults transient VCS references (like GitHub PRs) into your organization's own repositories, while allowing developers to instantly switch dependencies into local editable mode—fully integrated with `pyproject.toml` and `uv`.
|
|
6
|
+
|
|
7
|
+
## Documentation
|
|
8
|
+
|
|
9
|
+
The complete documentation is available in the `docs/` folder:
|
|
10
|
+
|
|
11
|
+
* [Quickstart & Key Features](docs/index.md) - Learn what `uvault` is and how to get started quickly.
|
|
12
|
+
* [How-To Guides](docs/how-to.md) - Step-by-step guides for installing and using `uvault` in your day-to-day workflow.
|
|
13
|
+
* [CLI & Configuration Reference](docs/reference.md) - Detailed information on `pyproject.toml` configuration (`[tool.uvault]`) and all CLI commands (`sync`, `add`, `develop`).
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Tutorials / How-To
|
|
2
|
+
|
|
3
|
+
## How to manage and vault dependencies
|
|
4
|
+
|
|
5
|
+
`uvault` is built around a typical workflow: declaring a dependency intention, synchronizing it to your vault, and (optionally) switching to local development mode.
|
|
6
|
+
|
|
7
|
+
### Installation (Recommended)
|
|
8
|
+
|
|
9
|
+
To get the most out of `uvault`, especially its ability to automatically discover repository URLs via package metadata when using the `uvault add` command, we highly recommend installing it as a development dependency in your project:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uv add --dev uvault
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
By installing it within your project's environment, `uvault` gains access to the local site-packages, allowing it to easily read the metadata of your project's existing dependencies.
|
|
16
|
+
|
|
17
|
+
### Step 1: Add a new dependency (`uvault add`)
|
|
18
|
+
|
|
19
|
+
Instead of manually editing the `pyproject.toml` file, use the `uvault add` command to declare an intention in `[tool.uvault.sources]`.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
uvault add my-addon https://github.com/OCA/my-addon --pr 123 --subdirectory my_addon
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
If the repository URL is already known (published package), you can omit it and `uvault` will try to guess it from the package metadata:
|
|
26
|
+
```bash
|
|
27
|
+
uvault add my-addon --branch 16.0
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
*Note: This command only configures your intention. It does not update your `uv` lockfile.*
|
|
31
|
+
|
|
32
|
+
### Step 2: Synchronize and Vault (`uvault sync`)
|
|
33
|
+
|
|
34
|
+
Once you have declared your dependencies, run the synchronization command to fetch them and push them to your organization's vault repository.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
uvault sync
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The command will:
|
|
41
|
+
1. Fetch the exact commit of the targeted branch or PR.
|
|
42
|
+
2. Clone this repository into the local cache (if necessary).
|
|
43
|
+
3. Push this commit to your vault repository as a frozen tag (e.g. `ppr-<sha>`).
|
|
44
|
+
4. Automatically update the `[tool.uv.sources]` block in `pyproject.toml` to point to this immutable reference.
|
|
45
|
+
|
|
46
|
+
**Crucial Step:** After syncing, you must instruct `uv` to resolve the new dependencies:
|
|
47
|
+
```bash
|
|
48
|
+
uv lock
|
|
49
|
+
# or
|
|
50
|
+
uv sync
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
#### Update an existing reference
|
|
54
|
+
If the original PR has been updated (new commits):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
uvault sync --update
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This will force the retrieval of new changes and update the vault reference. You can also target specific packages (which automatically implies an update):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uvault sync --package my-addon
|
|
64
|
+
uvault sync -P my-addon -P another-addon
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Step 3: Develop Locally (`uvault develop`)
|
|
68
|
+
|
|
69
|
+
When you need to make modifications to a vaulted dependency, you can switch it into local editable mode in seconds.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
uvault develop my-addon my-feature-branch
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This command will:
|
|
76
|
+
1. Clone the repository into your local workspace (e.g., `./.src/my-addon`).
|
|
77
|
+
2. Automatically configure your custom vault remotes (defined in `~/.config/uvault/config.toml`) so you can push your work easily.
|
|
78
|
+
3. Replace the remote reference in `[tool.uv.sources]` with a local, editable path (`editable = true`).
|
|
79
|
+
4. Creates and checks out the new branch (`git checkout -b <branch>`), or simply switches to it if it already exists.
|
|
80
|
+
|
|
81
|
+
**Crucial Step:** Once the local path is set, make sure to synchronize your environment:
|
|
82
|
+
```bash
|
|
83
|
+
uv sync
|
|
84
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# uvault
|
|
2
|
+
|
|
3
|
+
Development and vaulting workflow for [uv](https://github.com/astral-sh/uv) VCS dependencies.
|
|
4
|
+
|
|
5
|
+
Secure your Python projects against deleted or force-pushed upstream commits.
|
|
6
|
+
|
|
7
|
+
`uvault` automatically vaults transient VCS references (like GitHub PRs)
|
|
8
|
+
into your organization's own repositories, while allowing developers to instantly
|
|
9
|
+
switch dependencies into local editable mode—fully integrated with `pyproject.toml` and `uv`.
|
|
10
|
+
|
|
11
|
+
## Key Features
|
|
12
|
+
|
|
13
|
+
1. **Vaulting of Commits**: Never lose code again! Upstream pull requests and branches can be force-pushed or deleted. `uvault` fetches the exact commits your project depends on and pushes them as immutable tags to your own organization's vault repository.
|
|
14
|
+
2. **Easy Local Development**: Switch any VCS dependency to local "editable" mode in seconds. `uvault develop` clones the package locally and seamlessly configures `uv` to use your local copy so you can test changes and contribute back.
|
|
15
|
+
|
|
16
|
+
## Quickstart
|
|
17
|
+
|
|
18
|
+
### 1. Add a Dependency
|
|
19
|
+
|
|
20
|
+
Use `uvault add` to register an intention for a dependency. You can pass a PEP 508 URL or rely on automatic URL guessing if the package is already published.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uvault add odoo-addon-my-package https://github.com/OCA/my-repo --pr 123 --subdirectory my_package
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. Vault and Lock
|
|
27
|
+
|
|
28
|
+
Synchronize the dependency with your vault and lock it in `uv`.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uvault sync
|
|
32
|
+
uv lock
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 3. Develop Locally
|
|
36
|
+
|
|
37
|
+
Need to edit the dependency? Clone it instantly and set it as an editable dependency in `[tool.uv.sources]`.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
uvault develop my-package feat-branch
|
|
41
|
+
uv sync
|
|
42
|
+
```
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Reference
|
|
2
|
+
|
|
3
|
+
## Installation & Usage Modes
|
|
4
|
+
|
|
5
|
+
`uvault` can be executed in two different ways depending on your needs:
|
|
6
|
+
|
|
7
|
+
### 1. As a Project Development Dependency (Recommended)
|
|
8
|
+
|
|
9
|
+
Installing `uvault` as a dev dependency gives it access to your project's environment. This is crucial if you want `uvault add` to automatically guess repository URLs based on the metadata of your installed packages.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
uv add --dev uvault
|
|
13
|
+
uv run uvault --help
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### 2. As an Independent Tool via `uvx`
|
|
17
|
+
|
|
18
|
+
If you prefer keeping your dependencies minimal and don't need the automatic URL resolution feature, you can run `uvault` purely as an isolated, ephemeral tool using `uvx`.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
uvx uvault sync
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## `pyproject.toml` Configuration
|
|
25
|
+
|
|
26
|
+
The intention configuration is located in the `[tool.uvault]` section:
|
|
27
|
+
|
|
28
|
+
```toml
|
|
29
|
+
[tool.uvault]
|
|
30
|
+
tag_prefix = "apycod-" # (Optional) Prefix for generated tags. Defaults to "".
|
|
31
|
+
include_project_version = true # (Optional) Includes the current project's version in the vault tag. Defaults to true.
|
|
32
|
+
dev_directory = ".src/" # (Optional) Directory for developed sources. Defaults to ".src/".
|
|
33
|
+
|
|
34
|
+
# VCS Vault Configuration
|
|
35
|
+
[[tool.uvault.vcs_vaults]]
|
|
36
|
+
provider = "github.com" # (Required) The git hosting provider (e.g., "github.com" or "gitlab.com").
|
|
37
|
+
owner = "petrus-v" # (Optional) The user or organization owning the vault repository.
|
|
38
|
+
fetch_ssh = true # (Optional) If true, generates ssh:// URLs in [tool.uv.sources] instead of https://. Defaults to false.
|
|
39
|
+
push_ssh = true # (Optional) If true, pushes tags to the vault using ssh://. Defaults to true.
|
|
40
|
+
default = true # (Optional) Marks this vault as the default for synchronizing packages.
|
|
41
|
+
|
|
42
|
+
# Declaration of dependencies to synchronize
|
|
43
|
+
[tool.uvault.sources]
|
|
44
|
+
my-package = { git = "https://github.com/OCA/repository", rev = "refs/pull/100/head", subdirectory = "my_package" }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## User Configuration
|
|
48
|
+
|
|
49
|
+
You can define local machine-specific configurations, such as custom git remotes, in `~/.config/uvault/config.toml`. These remotes will be automatically added to the repository when running `uvault develop`.
|
|
50
|
+
|
|
51
|
+
```toml
|
|
52
|
+
[remotes]
|
|
53
|
+
myorg = "https://gitlab.com/myorg/"
|
|
54
|
+
perso = "ssh://git@github.com/personal/"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## CLI Commands
|
|
58
|
+
|
|
59
|
+
### `uvault sync`
|
|
60
|
+
|
|
61
|
+
Synchronizes remote references to the vault repository and updates `[tool.uv.sources]`.
|
|
62
|
+
|
|
63
|
+
**Note:** By default, `uvault sync` will skip any package that is already declared in `[tool.uv.sources]`.
|
|
64
|
+
|
|
65
|
+
**Flags:**
|
|
66
|
+
* `-P <package>, --package <package>` : Only sync the specified package. Can be used multiple times.
|
|
67
|
+
* `--update` : Forces an update of the package(s) even if they are already present in `[tool.uv.sources]`.
|
|
68
|
+
* `--delete-extra` : Removes any package found in `[tool.uv.sources]` that is not declared in `[tool.uvault.sources]`.
|
|
69
|
+
* `--keep-develop` : By default, packages in develop mode (`editable = true` or using `path`) are restored to their vaulted state during sync. Use this flag to keep them in develop mode.
|
|
70
|
+
|
|
71
|
+
### `uvault add`
|
|
72
|
+
|
|
73
|
+
Adds a new dependency intention directly into `[tool.uvault.sources]` without manual file editing.
|
|
74
|
+
|
|
75
|
+
**Arguments:**
|
|
76
|
+
* `<package>` : The name of the package to add.
|
|
77
|
+
* `[url]` : (Optional) The VCS URL (e.g. `https://github.com/OCA/my-addon`). If omitted, `uvault` will attempt to guess it from PyPI metadata.
|
|
78
|
+
|
|
79
|
+
**Options:**
|
|
80
|
+
* `--branch <name>` : Target a specific branch.
|
|
81
|
+
* `--tag <name>` : Target a specific tag.
|
|
82
|
+
* `--pr <number>` : Target a pull/merge request number.
|
|
83
|
+
* `--rev <hash>` : Target a specific commit hash or exact reference.
|
|
84
|
+
* `--subdirectory <path>` : The subdirectory within the repository where the Python package is located.
|
|
85
|
+
|
|
86
|
+
### `uvault develop`
|
|
87
|
+
|
|
88
|
+
Switches a vaulted dependency into local editable mode for active development.
|
|
89
|
+
|
|
90
|
+
**Arguments:**
|
|
91
|
+
* `<package>` : The name of the package to develop locally (must be declared in `[tool.uvault.sources]`).
|
|
92
|
+
* `<branch>` : The name of the branch to checkout or create.
|
|
93
|
+
|
|
94
|
+
**Workflow Details:**
|
|
95
|
+
1. Clones (using a fast partial blobless clone) or fetches the repository into your `.src/` directory.
|
|
96
|
+
2. Creates and checks out the new branch (`git checkout -b <branch>`), or switches to it if it exists (`git checkout <branch>`).
|
|
97
|
+
3. Sets up remotes, including `origin`, `vault`, and any other custom remotes defined in `~/.config/uvault/config.toml`.
|
|
98
|
+
4. Modifies `[tool.uv.sources]` to use `{ path = "./.src/<package>", editable = true }`.
|
|
99
|
+
|
|
100
|
+
::: uvault.cli
|
uvault-0.2.0/mkdocs.yml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
site_name: uvault documentation
|
|
2
|
+
theme:
|
|
3
|
+
name: material
|
|
4
|
+
features:
|
|
5
|
+
- navigation.tabs
|
|
6
|
+
- navigation.sections
|
|
7
|
+
|
|
8
|
+
plugins:
|
|
9
|
+
- search
|
|
10
|
+
- mkdocstrings
|
|
11
|
+
|
|
12
|
+
nav:
|
|
13
|
+
- Quickstart: index.md
|
|
14
|
+
- Tutorials / How-To: how-to.md
|
|
15
|
+
- Reference: reference.md
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "uvault"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Development and vaulting workflow for uv VCS dependencies."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"diskcache>=5.6.3",
|
|
9
|
+
"tomlkit>=0.15.0",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[dependency-groups]
|
|
13
|
+
dev = [
|
|
14
|
+
"bump-my-version>=1.3.0",
|
|
15
|
+
"mkdocs-material>=9.7.6",
|
|
16
|
+
"mkdocstrings[python]>=1.0.4",
|
|
17
|
+
"pre-commit>=4.6.0",
|
|
18
|
+
"pytest>=9.0.3",
|
|
19
|
+
"pytest-cov>=7.1.0",
|
|
20
|
+
"ruff>=0.15.16",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["hatchling"]
|
|
26
|
+
build-backend = "hatchling.build"
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
uvault = "uvault.cli:main"
|
|
30
|
+
|
|
31
|
+
[tool.pytest.ini_options]
|
|
32
|
+
addopts = ["--cov=src/uvault", "--cov-report=term-missing", "--cov-fail-under=100"]
|
|
33
|
+
testpaths = ["tests"]
|
|
34
|
+
|
|
35
|
+
[tool.ruff]
|
|
36
|
+
line-length = 88
|
|
37
|
+
target-version = "py310"
|
|
38
|
+
|
|
39
|
+
[tool.bumpversion]
|
|
40
|
+
current_version = "0.2.0"
|
|
41
|
+
commit = true
|
|
42
|
+
tag = true
|
|
43
|
+
tag_name = "v{new_version}"
|
|
44
|
+
|
|
45
|
+
[[tool.bumpversion.files]]
|
|
46
|
+
filename = "pyproject.toml"
|
|
47
|
+
search = 'version = "{current_version}"'
|
|
48
|
+
replace = 'version = "{new_version}"'
|
|
49
|
+
|
|
50
|
+
[[tool.bumpversion.files]]
|
|
51
|
+
filename = "src/uvault/__init__.py"
|
|
52
|
+
search = '__version__ = "{current_version}"'
|
|
53
|
+
replace = '__version__ = "{new_version}"'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import urllib.parse
|
|
3
|
+
import tomlkit
|
|
4
|
+
|
|
5
|
+
from uvault.vcs import guess_repository_url
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AddCommand:
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
package: str,
|
|
12
|
+
url: str | None = None,
|
|
13
|
+
pr: str | None = None,
|
|
14
|
+
branch: str | None = None,
|
|
15
|
+
tag: str | None = None,
|
|
16
|
+
rev: str | None = None,
|
|
17
|
+
subdirectory: str | None = None,
|
|
18
|
+
pyproject_path: str = "pyproject.toml",
|
|
19
|
+
):
|
|
20
|
+
self.package = package
|
|
21
|
+
self.url = url
|
|
22
|
+
self.pr = pr
|
|
23
|
+
self.branch = branch
|
|
24
|
+
self.tag = tag
|
|
25
|
+
self.rev = rev
|
|
26
|
+
self.subdirectory = subdirectory
|
|
27
|
+
self.pyproject_path = Path(pyproject_path)
|
|
28
|
+
|
|
29
|
+
def _parse_url(self):
|
|
30
|
+
if not self.url:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
# Handle git+https://... format
|
|
34
|
+
if self.url.startswith("git+"):
|
|
35
|
+
url_no_git = self.url[4:]
|
|
36
|
+
parsed = urllib.parse.urlparse(url_no_git)
|
|
37
|
+
|
|
38
|
+
# Extract rev/branch/tag from @...
|
|
39
|
+
if "@" in parsed.path:
|
|
40
|
+
path, ref = parsed.path.split("@", 1)
|
|
41
|
+
self.url = f"{parsed.scheme}://{parsed.netloc}{path}"
|
|
42
|
+
self.rev = (
|
|
43
|
+
ref # by default it's a rev, could be branch/tag but uv accepts rev
|
|
44
|
+
)
|
|
45
|
+
else:
|
|
46
|
+
self.url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
|
|
47
|
+
|
|
48
|
+
# Extract subdirectory from #subdirectory=...
|
|
49
|
+
if parsed.fragment:
|
|
50
|
+
fragments = urllib.parse.parse_qs(parsed.fragment)
|
|
51
|
+
if "subdirectory" in fragments:
|
|
52
|
+
self.subdirectory = fragments["subdirectory"][0]
|
|
53
|
+
|
|
54
|
+
def run(self):
|
|
55
|
+
if not self.pyproject_path.exists():
|
|
56
|
+
print("pyproject.toml not found")
|
|
57
|
+
return 1
|
|
58
|
+
|
|
59
|
+
self._parse_url()
|
|
60
|
+
|
|
61
|
+
if not self.url:
|
|
62
|
+
guessed_url = guess_repository_url(self.package)
|
|
63
|
+
if not guessed_url:
|
|
64
|
+
print(f"Could not find or guess repository URL for {self.package}")
|
|
65
|
+
return 1
|
|
66
|
+
self.url = guessed_url
|
|
67
|
+
print(f"Guessed repository URL: {self.url}")
|
|
68
|
+
|
|
69
|
+
with open(self.pyproject_path, "r", encoding="utf-8") as f:
|
|
70
|
+
doc = tomlkit.parse(f.read())
|
|
71
|
+
|
|
72
|
+
if "tool" not in doc:
|
|
73
|
+
doc.add("tool", tomlkit.table())
|
|
74
|
+
if "uvault" not in doc["tool"]:
|
|
75
|
+
doc["tool"].add("uvault", tomlkit.table())
|
|
76
|
+
if "sources" not in doc["tool"]["uvault"]:
|
|
77
|
+
doc["tool"]["uvault"].add("sources", tomlkit.table())
|
|
78
|
+
|
|
79
|
+
uvault_sources = doc["tool"]["uvault"]["sources"]
|
|
80
|
+
|
|
81
|
+
new_source = tomlkit.inline_table()
|
|
82
|
+
new_source["git"] = self.url
|
|
83
|
+
|
|
84
|
+
# Priority: pr, rev, branch, tag
|
|
85
|
+
if self.pr:
|
|
86
|
+
if "github.com" in self.url:
|
|
87
|
+
new_source["rev"] = f"refs/pull/{self.pr}/head"
|
|
88
|
+
else:
|
|
89
|
+
new_source["rev"] = f"refs/merge-requests/{self.pr}/head"
|
|
90
|
+
elif self.rev:
|
|
91
|
+
new_source["rev"] = self.rev
|
|
92
|
+
elif self.branch:
|
|
93
|
+
new_source["branch"] = self.branch
|
|
94
|
+
elif self.tag:
|
|
95
|
+
new_source["tag"] = self.tag
|
|
96
|
+
|
|
97
|
+
if self.subdirectory:
|
|
98
|
+
new_source["subdirectory"] = self.subdirectory
|
|
99
|
+
|
|
100
|
+
uvault_sources[self.package] = new_source
|
|
101
|
+
|
|
102
|
+
with open(self.pyproject_path, "w", encoding="utf-8") as f:
|
|
103
|
+
f.write(tomlkit.dumps(doc))
|
|
104
|
+
|
|
105
|
+
print(f"Added {self.package} to [tool.uvault.sources]")
|
|
106
|
+
print("Run `uvault sync` to lock this dependency in [tool.uv.sources]")
|
|
107
|
+
|
|
108
|
+
return 0
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
from uvault.sync import SyncCommand
|
|
4
|
+
from uvault.develop import DevelopCommand
|
|
5
|
+
from uvault.add import AddCommand
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main(argv=None):
|
|
9
|
+
if argv is None:
|
|
10
|
+
argv = sys.argv[1:] # pragma: no cover
|
|
11
|
+
|
|
12
|
+
parser = argparse.ArgumentParser(
|
|
13
|
+
description="Development and vaulting workflow for uv VCS dependencies."
|
|
14
|
+
)
|
|
15
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
16
|
+
|
|
17
|
+
# Sync command
|
|
18
|
+
sync_parser = subparsers.add_parser("sync", help="Sync and vault VCS dependencies.")
|
|
19
|
+
sync_parser.add_argument(
|
|
20
|
+
"-U",
|
|
21
|
+
"--update",
|
|
22
|
+
action="store_true",
|
|
23
|
+
help="Force update the vaulted reference.",
|
|
24
|
+
)
|
|
25
|
+
sync_parser.add_argument(
|
|
26
|
+
"-P",
|
|
27
|
+
"--package",
|
|
28
|
+
action="append",
|
|
29
|
+
help="Specific package to sync. Can be used multiple times.",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
sync_parser.add_argument(
|
|
33
|
+
"--delete-extra",
|
|
34
|
+
action="store_true",
|
|
35
|
+
help="Delete references in uv.sources that are not in uvault.sources.",
|
|
36
|
+
)
|
|
37
|
+
sync_parser.add_argument(
|
|
38
|
+
"--keep-develop",
|
|
39
|
+
action="store_true",
|
|
40
|
+
help="Keep packages that are in local develop mode instead of restoring them.",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Add command
|
|
44
|
+
add_parser = subparsers.add_parser("add", help="Add a new vaulting intention.")
|
|
45
|
+
add_parser.add_argument("package", help="The package to add")
|
|
46
|
+
add_parser.add_argument("url", nargs="?", help="The VCS URL or PEP 508 string")
|
|
47
|
+
add_parser.add_argument("--pr", help="Pull request number")
|
|
48
|
+
add_parser.add_argument("--branch", help="Branch name")
|
|
49
|
+
add_parser.add_argument("--tag", help="Tag name")
|
|
50
|
+
add_parser.add_argument("--rev", help="Revision SHA")
|
|
51
|
+
add_parser.add_argument("--subdirectory", help="Subdirectory in the repository")
|
|
52
|
+
|
|
53
|
+
# Develop command
|
|
54
|
+
dev_parser = subparsers.add_parser("develop", help="Develop a package locally.")
|
|
55
|
+
dev_parser.add_argument("package", help="The package to develop")
|
|
56
|
+
dev_parser.add_argument("branch", help="Branch name to checkout or create")
|
|
57
|
+
|
|
58
|
+
args = parser.parse_args(argv)
|
|
59
|
+
|
|
60
|
+
if args.command == "sync":
|
|
61
|
+
cmd = SyncCommand(
|
|
62
|
+
packages=args.package,
|
|
63
|
+
update=args.update,
|
|
64
|
+
delete_extra=args.delete_extra,
|
|
65
|
+
keep_develop=args.keep_develop,
|
|
66
|
+
)
|
|
67
|
+
return cmd.run()
|
|
68
|
+
elif args.command == "develop":
|
|
69
|
+
cmd = DevelopCommand(package=args.package, branch=args.branch)
|
|
70
|
+
return cmd.run()
|
|
71
|
+
elif args.command == "add":
|
|
72
|
+
cmd = AddCommand(
|
|
73
|
+
package=args.package,
|
|
74
|
+
url=args.url,
|
|
75
|
+
pr=args.pr,
|
|
76
|
+
branch=args.branch,
|
|
77
|
+
tag=args.tag,
|
|
78
|
+
rev=args.rev,
|
|
79
|
+
subdirectory=args.subdirectory,
|
|
80
|
+
)
|
|
81
|
+
return cmd.run()
|
|
82
|
+
|
|
83
|
+
return 0 # pragma: no cover
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__": # pragma: no cover
|
|
87
|
+
sys.exit(main())
|