Просмотр исходного кода

fix(skills): python-env — lead with modern uv project workflow

The skill taught `uv pip install` as the default, contradicting the
modern-tools rule (uv add / uv sync for project deps; uv pip is last resort).
Realign skill + references:

- SKILL.md: lead with uv init/add/remove/sync/run + uv tool/uvx for CLIs;
  uv pip demoted to an explicit "last resort" compatibility section.
- pyproject-patterns: dev tooling moved from [project.optional-dependencies]
  to [dependency-groups] (PEP 735, what `uv add --dev` writes); docs extra
  stays as an opt-in optional-dependency.
- publishing: `uv build` instead of pip install build + python -m build;
  twine via uvx; setup-uv@v5 in CI.
- dependency-management: uv.lock project workflow added as primary, the
  requirements.txt/compile path reframed as secondary; workspace installs
  via `uv sync` (not editable pip); private indexes via [[tool.uv.index]];
  CI uses uv sync --frozen and uv lock --check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0xDarkMatter 1 неделя назад
Родитель
Сommit
dd1c78e510

+ 93 - 43
skills/python-env/SKILL.md

@@ -10,49 +10,92 @@ metadata:
 
 
 # Python Environment
 # Python Environment
 
 
-Fast Python environment management with uv.
+Fast Python environment management with uv. Prefer the uv **project** workflow
+(`uv add` / `uv sync` / `uv run`) over the `uv pip` compatibility layer — it
+manages `pyproject.toml` + a lockfile for you and is reproducible.
 
 
 ## Quick Commands
 ## Quick Commands
 
 
 | Task | Command |
 | Task | Command |
 |------|---------|
 |------|---------|
-| Create venv | `uv venv` |
-| Install package | `uv pip install requests` |
-| Install from requirements | `uv pip install -r requirements.txt` |
-| Run script | `uv run python script.py` |
-| Show installed | `uv pip list` |
+| Start a project | `uv init <name>` (app) · `uv init --package <name>` (installable, `src/` layout) |
+| Add dependency | `uv add httpx` |
+| Add dev dependency | `uv add --dev pytest ruff` |
+| Remove dependency | `uv remove httpx` |
+| Sync env from lockfile | `uv sync` |
+| Run in project env | `uv run pytest` |
+| Update lockfile | `uv lock` |
+| Install a CLI tool | `uv tool install ruff` · one-shot: `uvx ruff` |
+| Install a Python | `uv python install 3.12` |
+
+## Start a Project
 
 
-## Virtual Environment
+```bash
+# Application (flat layout, no package build)
+uv init myapp
+
+# Installable package (src/ layout — separate tests/ that import by name)
+uv init --package wordtools
+# → src/wordtools/__init__.py, pyproject.toml with build-system
+```
+
+`uv init` creates `pyproject.toml`, pins a Python version, and prepares the
+project for `uv add` / `uv sync`. The `--package` (src) layout is preferred for
+anything with a test suite or that you intend to ship.
+
+## Manage Dependencies
 
 
 ```bash
 ```bash
-# Create venv (instant)
-uv venv
+# Add runtime deps (writes to [project.dependencies] + updates the lockfile)
+uv add "httpx>=0.25" pydantic
+
+# Add dev-only deps (writes to the dev dependency-group)
+uv add --dev pytest ruff mypy
+
+# Add with extras
+uv add "fastapi[standard]"
+
+# Remove
+uv remove httpx
 
 
-# Create with specific Python
-uv venv --python 3.11
+# Install everything from pyproject + uv.lock into .venv (reproducible)
+uv sync
 
 
-# Activate (or use uv run)
-source .venv/bin/activate  # Unix
-.venv\Scripts\activate     # Windows
+# Refresh the lockfile (e.g. after manual pyproject edits)
+uv lock
 ```
 ```
 
 
-## Package Installation
+`uv` creates and manages `.venv` automatically — you rarely activate it; just
+prefix commands with `uv run`.
+
+## Run Code
 
 
 ```bash
 ```bash
-# Single package
-uv pip install requests
+uv run python script.py     # run a script in the project env
+uv run pytest               # run a tool from the dev group
+uv run -- ruff check .      # `--` ends uv flag parsing
+```
+
+Never call bare `python` / `pytest` / `ruff` in a uv project — they may resolve
+to a different interpreter. Always `uv run`.
+
+## CLI Tools (global, not project deps)
 
 
-# Multiple packages
-uv pip install flask sqlalchemy pytest
+```bash
+uv tool install ruff        # persistent, isolated, on PATH
+uv tool upgrade ruff
+uvx ruff check .            # ephemeral one-shot run, nothing installed
+```
 
 
-# With extras
-uv pip install "fastapi[all]"
+Use `uv tool` / `uvx` for developer CLIs (ruff, pre-commit, httpie). Use
+`uv add` only for things your code imports.
 
 
-# Version constraints
-uv pip install "django>=4.0,<5.0"
+## Python Versions
 
 
-# Uninstall
-uv pip uninstall requests
+```bash
+uv python install 3.12      # download a managed interpreter
+uv python list              # show available + installed
+uv init --python 3.12 app   # pin a project to a version
 ```
 ```
 
 
 ## Minimal pyproject.toml
 ## Minimal pyproject.toml
@@ -61,45 +104,52 @@ uv pip uninstall requests
 [project]
 [project]
 name = "my-project"
 name = "my-project"
 version = "0.1.0"
 version = "0.1.0"
-requires-python = ">=3.10"
+requires-python = ">=3.11"
 dependencies = [
 dependencies = [
     "httpx>=0.25",
     "httpx>=0.25",
     "pydantic>=2.0",
     "pydantic>=2.0",
 ]
 ]
 
 
-[project.optional-dependencies]
+# Dev deps live here; `uv add --dev <pkg>` manages this group.
+[dependency-groups]
 dev = [
 dev = [
-    "pytest>=7.0",
-    "ruff>=0.1",
+    "pytest>=8.0",
+    "ruff>=0.4",
+    "mypy>=1.10",
 ]
 ]
 ```
 ```
 
 
-## Project Setup Checklist
+## Compatibility Layer (`uv pip`) — last resort
+
+`uv pip` mirrors pip's interface for environments uv doesn't manage (a hand-made
+venv, a legacy `requirements.txt`, CI that isn't uv-native). It does **not**
+update `pyproject.toml` or the lockfile — prefer `uv add` / `uv sync` whenever
+you control the project.
 
 
 ```bash
 ```bash
-mkdir my-project && cd my-project
-uv venv
-# Create pyproject.toml
-uv pip install -e ".[dev]"
-uv pip list
+uv venv                              # bare venv (no project)
+uv pip install -r requirements.txt   # legacy requirements file
+uv pip install -e .                  # editable install into an unmanaged venv
+uv pip compile requirements.in -o requirements.txt   # pin a requirements.txt
 ```
 ```
 
 
 ## Troubleshooting
 ## Troubleshooting
 
 
 | Issue | Solution |
 | Issue | Solution |
 |-------|----------|
 |-------|----------|
-| "No Python found" | `uv python install 3.11` |
-| Wrong Python version | `uv venv --python 3.11` |
-| Conflicting deps | `uv pip compile --resolver=backtracking` |
+| "No Python found" | `uv python install 3.12` |
+| Pin project Python | `uv init --python 3.12` or edit `requires-python` |
+| Lock/resolve conflict | `uv lock --resolution=lowest-direct` to probe, then loosen bounds |
+| Stale env after pull | `uv sync` |
 | Cache issues | `uv cache clean` |
 | Cache issues | `uv cache clean` |
 
 
 ## When to Use
 ## When to Use
 
 
-- **Always** use uv over pip for speed
-- Creating virtual environments
-- Installing packages
-- Managing dependencies
-- Running scripts in project context
+- **Always** use uv over pip — 10-100x faster
+- `uv add` / `uv remove` / `uv sync` for project dependencies (not `uv pip install`)
+- `uv run` to execute anything inside the project env
+- `uv tool install` / `uvx` for standalone developer CLIs
+- `uv pip` only for environments uv doesn't manage
 
 
 ## Additional Resources
 ## Additional Resources
 
 

+ 46 - 30
skills/python-env/references/dependency-management.md

@@ -2,7 +2,26 @@
 
 
 Advanced patterns for managing Python dependencies with uv.
 Advanced patterns for managing Python dependencies with uv.
 
 
-## Lock File Workflow
+## Project Lockfile (uv.lock) — preferred
+
+For any project with a `pyproject.toml`, uv manages a `uv.lock` automatically.
+This is the reproducible, uv-native workflow — prefer it over a hand-managed
+`requirements.txt`.
+
+```bash
+uv add "flask>=2.0" sqlalchemy     # add runtime deps; uv.lock updated
+uv add --dev pytest ruff mypy      # dev-only dependency group
+uv remove flask                    # drop a dep
+uv sync                            # install exactly from uv.lock
+uv sync --frozen                   # CI: fail if uv.lock is stale, don't re-resolve
+uv lock --upgrade                  # bump everything within constraints
+uv lock --upgrade-package flask    # bump just one
+```
+
+## requirements.txt Workflow (uv pip compile)
+
+Use this only when you specifically need a `requirements.txt` — Docker layer
+caching, non-uv CI, or legacy tooling. Otherwise prefer `uv.lock` above.
 
 
 ### Basic Lock Pattern
 ### Basic Lock Pattern
 
 
@@ -136,30 +155,40 @@ dependencies = ["my-core", "fastapi>=0.100"]
 ### Workspace Commands
 ### Workspace Commands
 
 
 ```bash
 ```bash
-# Install all workspace packages
-uv pip install -e packages/core -e packages/api -e packages/cli
-
-# Sync entire workspace
+# Install all workspace members into one env (native — no editable pip installs)
 uv sync
 uv sync
 
 
-# Run command in workspace context
+# Run a command in the workspace env
 uv run pytest
 uv run pytest
+
+# Operate on a specific member
+uv run --package my-api pytest
 ```
 ```
 
 
 ## Private Packages
 ## Private Packages
 
 
-### Configure Index
+### Configure Index (pyproject.toml)
+
+```toml
+[[tool.uv.index]]
+name = "private"
+url = "https://pypi.private.com/simple/"
+```
 
 
 ```bash
 ```bash
-# Extra index for private packages
-uv pip install my-private-package --extra-index-url https://pypi.private.com/simple/
+uv add my-private-package          # resolves against configured indexes
+```
+
+### Authentication via environment
 
 
-# With authentication
-uv pip install my-private-package \
-  --extra-index-url https://user:token@pypi.private.com/simple/
+```bash
+# Per-index credentials: UV_INDEX_<NAME>_USERNAME / _PASSWORD
+export UV_INDEX_PRIVATE_USERNAME=user
+export UV_INDEX_PRIVATE_PASSWORD=token
+uv add my-private-package
 ```
 ```
 
 
-### In requirements.in
+### requirements.txt workflow
 
 
 ```
 ```
 --extra-index-url https://pypi.private.com/simple/
 --extra-index-url https://pypi.private.com/simple/
@@ -167,13 +196,6 @@ my-public-package>=1.0
 my-private-package>=2.0
 my-private-package>=2.0
 ```
 ```
 
 
-### Environment Variable
-
-```bash
-export UV_EXTRA_INDEX_URL=https://user:token@pypi.private.com/simple/
-uv pip install my-private-package
-```
-
 ## Git Dependencies
 ## Git Dependencies
 
 
 ```toml
 ```toml
@@ -253,14 +275,10 @@ uv pip install --no-cache package-name
 
 
 ```yaml
 ```yaml
 - name: Install uv
 - name: Install uv
-  uses: astral-sh/setup-uv@v1
-  with:
-    version: "latest"
+  uses: astral-sh/setup-uv@v5
 
 
 - name: Install dependencies
 - name: Install dependencies
-  run: |
-    uv venv
-    uv pip sync requirements.txt
+  run: uv sync --frozen
 
 
 - name: Run tests
 - name: Run tests
   run: uv run pytest
   run: uv run pytest
@@ -273,7 +291,7 @@ uv pip install --no-cache package-name
   uses: actions/cache@v3
   uses: actions/cache@v3
   with:
   with:
     path: ~/.cache/uv
     path: ~/.cache/uv
-    key: uv-${{ hashFiles('requirements.txt') }}
+    key: uv-${{ hashFiles('uv.lock') }}
     restore-keys: uv-
     restore-keys: uv-
 ```
 ```
 
 
@@ -281,9 +299,7 @@ uv pip install --no-cache package-name
 
 
 ```yaml
 ```yaml
 - name: Verify lock file is up to date
 - name: Verify lock file is up to date
-  run: |
-    uv pip compile requirements.in -o requirements-check.txt
-    diff requirements.txt requirements-check.txt
+  run: uv lock --check
 ```
 ```
 
 
 ## Best Practices
 ## Best Practices

+ 20 - 26
skills/python-env/references/publishing.md

@@ -34,12 +34,13 @@ dependencies = [
     "pydantic>=2.0",
     "pydantic>=2.0",
 ]
 ]
 
 
-[project.optional-dependencies]
+# Dev tooling → dependency-groups (PEP 735); not shipped with the package.
+[dependency-groups]
 dev = [
 dev = [
-    "pytest>=7.0",
-    "pytest-cov>=4.0",
-    "ruff>=0.1",
-    "mypy>=1.0",
+    "pytest>=8.0",
+    "pytest-cov>=5.0",
+    "ruff>=0.4",
+    "mypy>=1.10",
 ]
 ]
 
 
 [project.urls]
 [project.urls]
@@ -58,28 +59,25 @@ plugin1 = "my_package.plugins:Plugin1"
 ## Build and Upload
 ## Build and Upload
 
 
 ```bash
 ```bash
-# Install build tools
-uv pip install build twine
-
-# Build package
-python -m build
+# Build sdist + wheel — uv has a native builder, no separate `build` install
+uv build
 
 
 # Check build artifacts
 # Check build artifacts
 ls dist/
 ls dist/
 # my_package-0.1.0-py3-none-any.whl
 # my_package-0.1.0-py3-none-any.whl
 # my_package-0.1.0.tar.gz
 # my_package-0.1.0.tar.gz
 
 
-# Check distribution
-twine check dist/*
+# Verify + upload with twine via uvx (ephemeral, nothing installed globally)
+uvx twine check dist/*
 
 
 # Upload to TestPyPI first
 # Upload to TestPyPI first
-twine upload --repository testpypi dist/*
+uvx twine upload --repository testpypi dist/*
 
 
-# Test installation from TestPyPI
+# Test installation from TestPyPI into a throwaway env
 uv pip install --index-url https://test.pypi.org/simple/ my-package
 uv pip install --index-url https://test.pypi.org/simple/ my-package
 
 
 # Upload to PyPI (production)
 # Upload to PyPI (production)
-twine upload dist/*
+uvx twine upload dist/*
 ```
 ```
 
 
 ## Version Management
 ## Version Management
@@ -187,15 +185,11 @@ jobs:
     steps:
     steps:
       - uses: actions/checkout@v4
       - uses: actions/checkout@v4
 
 
-      - uses: actions/setup-python@v5
-        with:
-          python-version: "3.11"
-
-      - name: Install build tools
-        run: pip install build
+      - name: Install uv
+        uses: astral-sh/setup-uv@v5
 
 
       - name: Build package
       - name: Build package
-        run: python -m build
+        run: uv build
 
 
       - name: Publish to PyPI
       - name: Publish to PyPI
         uses: pypa/gh-action-pypi-publish@release/v1
         uses: pypa/gh-action-pypi-publish@release/v1
@@ -244,10 +238,10 @@ my-package/
 
 
 | Command | Purpose |
 | Command | Purpose |
 |---------|---------|
 |---------|---------|
-| `python -m build` | Build wheel and sdist |
-| `twine check dist/*` | Verify package |
-| `twine upload dist/*` | Upload to PyPI |
-| `twine upload --repository testpypi dist/*` | Upload to TestPyPI |
+| `uv build` | Build wheel and sdist (native, no `build` install) |
+| `uvx twine check dist/*` | Verify package |
+| `uvx twine upload dist/*` | Upload to PyPI |
+| `uvx twine upload --repository testpypi dist/*` | Upload to TestPyPI |
 
 
 | Version | When |
 | Version | When |
 |---------|------|
 |---------|------|

+ 17 - 12
skills/python-env/references/pyproject-patterns.md

@@ -49,14 +49,19 @@ dependencies = [
     "rich>=13.0",
     "rich>=13.0",
 ]
 ]
 
 
-[project.optional-dependencies]
+# Dev tooling → dependency-groups (PEP 735). `uv add --dev <pkg>` writes here;
+# `uv sync` installs it by default. Not shipped with the published package.
+[dependency-groups]
 dev = [
 dev = [
-    "pytest>=7.0",
-    "pytest-asyncio>=0.21",
-    "pytest-cov>=4.0",
-    "ruff>=0.1",
-    "mypy>=1.0",
+    "pytest>=8.0",
+    "pytest-asyncio>=0.23",
+    "pytest-cov>=5.0",
+    "ruff>=0.4",
+    "mypy>=1.10",
 ]
 ]
+
+# Opt-in extras users install explicitly: `uv add "my-package[docs]"`
+[project.optional-dependencies]
 docs = [
 docs = [
     "mkdocs>=1.5",
     "mkdocs>=1.5",
     "mkdocs-material>=9.0",
     "mkdocs-material>=9.0",
@@ -91,7 +96,7 @@ dependencies = [
 [project.scripts]
 [project.scripts]
 mycli = "my_cli.main:app"
 mycli = "my_cli.main:app"
 
 
-[project.optional-dependencies]
+[dependency-groups]
 dev = ["pytest", "ruff"]
 dev = ["pytest", "ruff"]
 ```
 ```
 
 
@@ -115,13 +120,13 @@ dependencies = [
     "python-dotenv>=1.0",
     "python-dotenv>=1.0",
 ]
 ]
 
 
-[project.optional-dependencies]
+[dependency-groups]
 dev = [
 dev = [
-    "pytest>=7.0",
-    "pytest-asyncio>=0.21",
+    "pytest>=8.0",
+    "pytest-asyncio>=0.23",
     "httpx>=0.25",  # for testing
     "httpx>=0.25",  # for testing
-    "ruff>=0.1",
-    "mypy>=1.0",
+    "ruff>=0.4",
+    "mypy>=1.10",
 ]
 ]
 ```
 ```