Skip to content

Commit f2bf690

Browse files
committed
docs: switch docs/templates to uv-first (replace pip with uv sync/pip)
1 parent c9d001f commit f2bf690

File tree

8 files changed

+261
-153
lines changed

8 files changed

+261
-153
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
2+
# Scaffold Python Package – Create & Launch New Project
3+
4+
## 🎯 Purpose
5+
Implement a scaffolding project that allows you to launch new Python packages with full setup: `uv` venv management, `pyproject.toml`, `pytest + coverage`, `Sphinx` docs, Git repo, CI config.
6+
7+
## ✅ Inputs
8+
- `package_name` — Package name (PEP 8 valid, e.g. `my_cool_pkg`)
9+
- `target_dir` — Absolute path to parent directory where the project folder will be created
10+
11+
## ✅ Sequence of Tasks
12+
13+
### 1. Validate inputs
14+
- [ ] Run shell script to check `package_name` matches `[a-zA-Z_][a-zA-Z0-9_]*`
15+
- [ ] Ensure `target_dir` is an absolute path
16+
```bash
17+
set -e
18+
PKG="${package_name}"
19+
DIR="${target_dir}"
20+
python3 - <<'PY'
21+
import re, sys, os
22+
pkg=sys.argv[1]; dir_=sys.argv[2]
23+
if not re.fullmatch(r'[a-zA-Z_][a-zA-Z0-9_]*', pkg):
24+
print(f"Invalid package_name: {pkg}"); sys.exit(1)
25+
if not os.path.isabs(dir_):
26+
print(f"target_dir must be absolute: {dir_}"); sys.exit(1)
27+
print("Inputs OK.")
28+
PY "$PKG" "$DIR"
29+
````
30+
31+
### 2. Prepare template workspace
32+
33+
* [ ] Create a temporary workspace `.cline_tmp_pkg_template`
34+
* [ ] Within it, create directory structure for TEMPLATE:
35+
36+
* `TEMPLATE/{{PKG}}/`
37+
* `TEMPLATE/tests/`
38+
* `TEMPLATE/docs/`
39+
* `TEMPLATE/.github/workflows/`
40+
* [ ] Add the following files in TEMPLATE (with placeholder `{{PKG}}` inside):
41+
42+
* `pyproject.toml`
43+
* `README.md`
44+
* `LICENSE`
45+
* `.gitignore`
46+
* `.github/workflows/ci.yaml`
47+
* `{{PKG}}/__init__.py`, `{{PKG}}/hello.py`
48+
* `tests/test_hello.py`
49+
* `docs/conf.py`, `docs/index.rst`
50+
* `requirements-dev.txt`
51+
* `Makefile`
52+
* Optional `setup.cfg` or patch file for dev-extras
53+
54+
### 3. Materialize project & substitute placeholders
55+
56+
* [ ] Compute `DEST="${target_dir}/${package_name}"`
57+
* [ ] Fail if `DEST` already exists
58+
* [ ] Copy all files from workspace TEMPLATE to `${DEST}`
59+
* [ ] Recursively replace placeholder `{{PKG}}` with actual `package_name` in all files
60+
* [ ] Print “Project created at: ${DEST}
61+
62+
### 4. Initialize git repository
63+
64+
* [ ] `cd` into `${DEST}`
65+
* [ ] `git init`
66+
* [ ] `git add .`
67+
* [ ] `git commit -m "chore: initial scaffold"`
68+
* [ ] Print “Git repo initialized.”
69+
70+
### 5. Setup `uv` environment & install dev extras
71+
72+
* [ ] `cd "${DEST}"`
73+
* [ ] `uv venv`
74+
* [ ] `uv sync --all-extras`
75+
* [ ] Print “Environment ready.”
76+
77+
### 6. Run tests & coverage
78+
79+
* [ ] `cd "${DEST}"`
80+
* [ ] `uv run pytest --cov="${package_name}" --cov-report=term-missing`
81+
82+
### 7. Build Sphinx documentation
83+
84+
* [ ] `cd "${DEST}"`
85+
* [ ] `uv run sphinx-build -b html docs docs/_build/html`
86+
* [ ] Print “Docs built at: docs/_build/html”
87+
88+
### 8. Output summary
89+
90+
* [ ] Print “Scaffold complete.”
91+
* [ ] Print project path: `${DEST}`
92+
* [ ] Print next steps:
93+
94+
```text
95+
cd "${DEST}"
96+
source .venv/bin/activate # (or use uv run)
97+
uv run python -c "import ${package_name}; print(${package_name}.hello())"
98+
```
99+
100+
## 📁 Generated Structure
101+
102+
```
103+
${package_name}/
104+
.github/workflows/ci.yaml
105+
docs/
106+
conf.py
107+
index.rst
108+
_build/ (after docs build)
109+
tests/
110+
test_hello.py
111+
${package_name}/
112+
__init__.py
113+
hello.py
114+
.gitignore
115+
LICENSE
116+
Makefile
117+
README.md
118+
pyproject.toml
119+
requirements-dev.txt
120+
setup.cfg
121+
```
122+
123+
## 📝 Notes
124+
125+
* Uses `uv` for virtualenv management.
126+
* Configured for `pytest + pytest-cov`.
127+
* Sphinx docs with theme `furo` (can edit in `docs/conf.py`).
128+
* Dev extras via `pyproject.toml` optional-deps.
129+
* CI workflow provided in `.github/workflows/ci.yaml`.
130+
131+
```

.github/workflows/ci.yml

Lines changed: 67 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,35 @@ on:
66
pull_request:
77
branches: [ main ]
88

9+
# Cancel redundant runs per-branch/PR
10+
concurrency:
11+
group: ci-${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: true
13+
14+
# Least-privilege for the jobs below
15+
permissions:
16+
contents: read
17+
918
jobs:
1019
test:
1120
name: Test / Lint / Typecheck (uv)
1221
runs-on: ubuntu-latest
22+
# Write perms only where needed
23+
permissions:
24+
contents: read
1325
strategy:
26+
fail-fast: false
1427
matrix:
15-
python-version: ["3.11", "3.12", "3.13", "3.14"]
28+
include:
29+
- python-version: "3.11"
30+
experimental: false
31+
- python-version: "3.12"
32+
experimental: false
33+
- python-version: "3.13"
34+
experimental: false
35+
- python-version: "3.14" # treat 3.14 as experimental so CI doesn't block if it breaks
36+
experimental: true
37+
continue-on-error: ${{ matrix.experimental }}
1638

1739
steps:
1840
- name: Checkout
@@ -23,29 +45,31 @@ jobs:
2345
with:
2446
enable-cache: true
2547

26-
- name: Set up Python ${{ matrix.python-version }}
48+
- name: Set up Python
2749
run: uv python install ${{ matrix.python-version }}
2850

29-
- name: Sync dependencies with uv
30-
run: uv sync --all-extras
51+
# Ensure dev tools (ruff, mypy, pytest, bandit, safety, pytest-cov) are declared in pyproject dev deps.
52+
- name: Sync dependencies
53+
run: uv sync --all-extras --dev
3154

3255
- name: Lint (ruff)
33-
run: uv run ruff check python_project_deployment
56+
run: uv run ruff check .
3457

3558
- name: Typecheck (mypy)
36-
run: uv run mypy python_project_deployment
59+
run: uv run mypy src
3760

3861
- name: Tests (pytest)
3962
run: uv run pytest --cov --cov-report=xml --cov-report=html
4063

41-
- name: Security scan for dangerous APIs
64+
- name: Dangerous API scan (grep)
65+
continue-on-error: true
66+
shell: bash
4267
run: |
4368
set -euo pipefail
44-
if grep -R --line-number -E "\beval\(|\bexec\(|pickle\.loads|yaml\.load|subprocess\.(Popen|call)" python_project_deployment/ tests/ || true; then
45-
echo "⚠️ Potentially dangerous API usage detected. Please review before merging." >&2
69+
if grep -R --line-number -E "\beval\(|\bexec\(|pickle\.loads|yaml\.load(?!_safe)|subprocess\.(Popen|call)" src/ tests/ || true; then
70+
echo "⚠️ Potentially dangerous API usage detected. Please review." >&2
4671
exit 2
4772
fi
48-
continue-on-error: true
4973
5074
- name: Upload coverage.xml
5175
uses: actions/upload-artifact@v4
@@ -59,89 +83,65 @@ jobs:
5983
name: coverage-html-${{ matrix.python-version }}
6084
path: htmlcov
6185

86+
# Upload Codecov once to avoid noisy duplicate uploads
87+
- name: Upload to Codecov
88+
if: matrix.python-version == '3.11'
89+
uses: codecov/codecov-action@v4
90+
with:
91+
files: coverage.xml
92+
flags: unittests
93+
fail_ci_if_error: false
94+
env:
95+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
96+
6297
security:
63-
name: Security Scan
98+
name: Security Scan (Bandit + Safety)
6499
runs-on: ubuntu-latest
100+
needs: test
101+
# Grant code scanning upload only here
102+
permissions:
103+
contents: read
104+
security-events: write
105+
65106
env:
66107
SECURITY_FAIL_LEVEL: MEDIUM
108+
67109
steps:
68-
- uses: actions/checkout@v4
110+
- name: Checkout
111+
uses: actions/checkout@v4
69112

70113
- name: Install uv
71114
uses: astral-sh/setup-uv@v4
72115
with:
73116
enable-cache: true
74117

75-
- name: Set up Python 3.11
118+
- name: Set up Python
76119
run: uv python install 3.11
77120

78-
- name: Sync deps and install security tools
79-
run: |
80-
uv sync --all-extras
81-
uv pip install bandit safety
121+
- name: Sync dependencies
122+
run: uv sync --all-extras --dev
82123

83-
- name: Run Bandit and export SARIF
124+
- name: Run Bandit (JSON + SARIF)
84125
run: |
85-
uv run bandit -r python_project_deployment/ -f json -o bandit-report.json
86-
uv run bandit -r python_project_deployment/ -f sarif -o bandit-report.sarif
87-
continue-on-error: true
126+
uv run bandit -r src/ -f json -o bandit-report.json || true
127+
uv run bandit -r src/ -f sarif -o bandit-report.sarif || true
88128
89129
- name: Upload Bandit SARIF to GitHub Code Scanning
90130
uses: github/codeql-action/upload-sarif@v3
91131
with:
92132
sarif_file: bandit-report.sarif
93133
continue-on-error: true
94134

95-
- name: Run Safety (fail on any vulnerability)
96-
run: |
97-
uv run safety check --json > safety-report.json || true
98-
python - <<'PY'
99-
import json,sys,os
100-
try:
101-
data=json.load(open('safety-report.json'))
102-
if data.get('vulnerabilities'):
103-
print('Safety reported vulnerabilities')
104-
sys.exit(2)
105-
print('No safety vulnerabilities')
106-
except Exception as e:
107-
print(f'Error parsing safety output: {e}')
108-
sys.exit(0)
109-
PY
110-
continue-on-error: true
135+
- name: Run Safety (JSON)
136+
run: uv run safety check --json > safety-report.json || true
111137

112138
- name: Apply Bandit threshold
113-
run: |
114-
python - <<'PY'
115-
import json,sys,os
116-
level=os.environ.get('SECURITY_FAIL_LEVEL','MEDIUM').upper()
117-
try:
118-
data=json.load(open('bandit-report.json'))
119-
sevs=[issue.get('issue_severity','') for issue in data.get('results',[])]
120-
if level=='NONE':
121-
print('SECURITY_FAIL_LEVEL=NONE: not failing on bandit issues')
122-
sys.exit(0)
123-
if level=='HIGH':
124-
if 'HIGH' in sevs:
125-
print('Failing on HIGH bandit issues')
126-
sys.exit(2)
127-
else:
128-
print('No HIGH bandit issues')
129-
sys.exit(0)
130-
if level=='MEDIUM':
131-
if any(s in ('HIGH','MEDIUM') for s in sevs):
132-
print('Failing on MEDIUM or HIGH bandit issues')
133-
sys.exit(2)
134-
else:
135-
print('No MEDIUM/HIGH bandit issues')
136-
sys.exit(0)
137-
print('Unknown SECURITY_FAIL_LEVEL:', level)
138-
sys.exit(1)
139-
except Exception as e:
140-
print(f'Error parsing bandit report: {e}')
141-
sys.exit(0)
142-
PY
139+
run: uv run python scripts/security_bandit_check.py
143140
continue-on-error: true
144141

142+
- name: Fail on Safety vulnerabilities
143+
run: uv run python scripts/security_safety_check.py
144+
145145
- name: Upload security reports
146146
if: always()
147147
uses: actions/upload-artifact@v4
@@ -151,4 +151,3 @@ jobs:
151151
bandit-report.json
152152
bandit-report.sarif
153153
safety-report.json
154-
if data.get('vulnerabilities'):

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ help:
1515

1616
install:
1717
@command -v uv >/dev/null 2>&1 || \
18-
{ echo "uv not found. Please install uv in your Python environment. Example:"; \
19-
echo " python -m pip install --upgrade pip && python -m pip install uv"; exit 1; }
18+
{ echo "uv not found. Please install uv. Example:"; \
19+
echo " curl -LsSf https://astral.sh/uv/install.sh | sh"; exit 1; }
2020
uv sync --all-extras
2121

2222
sync:

README.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@ A modern scaffolding tool for creating new Python packages with best practices b
1414
## Installation
1515

1616
```bash
17-
# Install with uv
18-
uv pip install -e .
19-
20-
# Or with pip
21-
pip install -e .
17+
# Install (uv-first)
18+
uv venv
19+
uv sync --all-extras
2220
```
2321

2422
## Usage
@@ -112,7 +110,7 @@ Direct uv commands:
112110
```bash
113111
# Create a venv and install dev extras
114112
uv venv
115-
uv pip install -e ".[dev]"
113+
uv sync --all-extras
116114

117115
# Run tests
118116
uv run pytest

0 commit comments

Comments
 (0)