flexdoc 0.1.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.
- flexdoc-0.1.0/.agents/skills/simple-modern-uv/SKILL.md +150 -0
- flexdoc-0.1.0/.agents/skills/simple-modern-uv/references/adopt-existing.md +135 -0
- flexdoc-0.1.0/.agents/skills/simple-modern-uv/references/customize.md +75 -0
- flexdoc-0.1.0/.agents/skills/simple-modern-uv/references/faq.md +89 -0
- flexdoc-0.1.0/.agents/skills/tbd/SKILL.md +293 -0
- flexdoc-0.1.0/.claude/.gitignore +2 -0
- flexdoc-0.1.0/.claude/hooks/tbd-closing-reminder.sh +15 -0
- flexdoc-0.1.0/.claude/scripts/ensure-gh-cli.sh +123 -0
- flexdoc-0.1.0/.claude/scripts/tbd-session.sh +27 -0
- flexdoc-0.1.0/.claude/settings.json +47 -0
- flexdoc-0.1.0/.claude/skills/tbd/SKILL.md +293 -0
- flexdoc-0.1.0/.codex/ensure-gh-cli.sh +123 -0
- flexdoc-0.1.0/.codex/hooks.json +47 -0
- flexdoc-0.1.0/.codex/tbd-closing-reminder.sh +15 -0
- flexdoc-0.1.0/.codex/tbd-session.sh +27 -0
- flexdoc-0.1.0/.copier-answers.yml +11 -0
- flexdoc-0.1.0/.github/workflows/ci.yml +132 -0
- flexdoc-0.1.0/.github/workflows/publish.yml +48 -0
- flexdoc-0.1.0/.gitignore +184 -0
- flexdoc-0.1.0/.tbd/.gitattributes +2 -0
- flexdoc-0.1.0/.tbd/.gitignore +21 -0
- flexdoc-0.1.0/.tbd/config.yml +94 -0
- flexdoc-0.1.0/AGENTS.md +385 -0
- flexdoc-0.1.0/CHANGELOG.md +66 -0
- flexdoc-0.1.0/CLAUDE.md +4 -0
- flexdoc-0.1.0/LICENSE +21 -0
- flexdoc-0.1.0/Makefile +37 -0
- flexdoc-0.1.0/PKG-INFO +101 -0
- flexdoc-0.1.0/README.md +69 -0
- flexdoc-0.1.0/SUPPLY-CHAIN-SECURITY.md +146 -0
- flexdoc-0.1.0/TODO.md +38 -0
- flexdoc-0.1.0/devtools/lint.py +73 -0
- flexdoc-0.1.0/docs/development.md +123 -0
- flexdoc-0.1.0/docs/flexdoc-spec.md +1003 -0
- flexdoc-0.1.0/docs/installation.md +31 -0
- flexdoc-0.1.0/docs/project/research/research-2026-05-29-document-model.md +1067 -0
- flexdoc-0.1.0/docs/project/research/research-2026-05-30-multilayer-parsing.md +658 -0
- flexdoc-0.1.0/docs/project/research/research-2026-05-30-span-references.md +169 -0
- flexdoc-0.1.0/docs/project/review/senior-engineering-review-flexdoc-standalone-2026-06.md +263 -0
- flexdoc-0.1.0/docs/project/specs/active/plan-2026-05-29-unified-document-model.md +1062 -0
- flexdoc-0.1.0/docs/project/specs/active/plan-2026-05-31-doc-model-refinements.md +285 -0
- flexdoc-0.1.0/docs/project/specs/active/plan-2026-05-31-golden-doc-testing.md +191 -0
- flexdoc-0.1.0/docs/project/specs/active/plan-2026-06-11-flexdoc-extraction.md +629 -0
- flexdoc-0.1.0/docs/project/specs/active/plan-2026-06-11-structural-metadata.md +408 -0
- flexdoc-0.1.0/docs/publishing.md +228 -0
- flexdoc-0.1.0/docs/usage.md +136 -0
- flexdoc-0.1.0/examples/backfill_timestamps.py +141 -0
- flexdoc-0.1.0/examples/doc_structure.py +106 -0
- flexdoc-0.1.0/examples/normalized_form.py +111 -0
- flexdoc-0.1.0/pyproject.toml +207 -0
- flexdoc-0.1.0/skills-lock.json +17 -0
- flexdoc-0.1.0/src/flexdoc/__init__.py +54 -0
- flexdoc-0.1.0/src/flexdoc/docs/__init__.py +155 -0
- flexdoc-0.1.0/src/flexdoc/docs/base_blocks.py +170 -0
- flexdoc-0.1.0/src/flexdoc/docs/block_info.py +167 -0
- flexdoc-0.1.0/src/flexdoc/docs/block_tree.py +142 -0
- flexdoc-0.1.0/src/flexdoc/docs/block_types.py +104 -0
- flexdoc-0.1.0/src/flexdoc/docs/collect.py +161 -0
- flexdoc-0.1.0/src/flexdoc/docs/debug.py +177 -0
- flexdoc-0.1.0/src/flexdoc/docs/doc_graph.py +288 -0
- flexdoc-0.1.0/src/flexdoc/docs/doc_graph_schema.json +219 -0
- flexdoc-0.1.0/src/flexdoc/docs/flex_doc.py +782 -0
- flexdoc-0.1.0/src/flexdoc/docs/frontmatter.py +77 -0
- flexdoc-0.1.0/src/flexdoc/docs/interval_index.py +73 -0
- flexdoc-0.1.0/src/flexdoc/docs/links.py +109 -0
- flexdoc-0.1.0/src/flexdoc/docs/node.py +165 -0
- flexdoc-0.1.0/src/flexdoc/docs/node_table.py +428 -0
- flexdoc-0.1.0/src/flexdoc/docs/paragraphs.py +395 -0
- flexdoc-0.1.0/src/flexdoc/docs/render.py +100 -0
- flexdoc-0.1.0/src/flexdoc/docs/search_tokens.py +83 -0
- flexdoc-0.1.0/src/flexdoc/docs/sections.py +130 -0
- flexdoc-0.1.0/src/flexdoc/docs/sizes.py +47 -0
- flexdoc-0.1.0/src/flexdoc/docs/span_ref.py +192 -0
- flexdoc-0.1.0/src/flexdoc/docs/token_diffs.py +367 -0
- flexdoc-0.1.0/src/flexdoc/docs/token_mapping.py +91 -0
- flexdoc-0.1.0/src/flexdoc/docs/wordtoks.py +284 -0
- flexdoc-0.1.0/src/flexdoc/html/__init__.py +74 -0
- flexdoc-0.1.0/src/flexdoc/html/extractor.py +30 -0
- flexdoc-0.1.0/src/flexdoc/html/html_in_md.py +510 -0
- flexdoc-0.1.0/src/flexdoc/html/html_plaintext.py +25 -0
- flexdoc-0.1.0/src/flexdoc/html/html_tags.py +423 -0
- flexdoc-0.1.0/src/flexdoc/html/timestamps.py +58 -0
- flexdoc-0.1.0/src/flexdoc/py.typed +0 -0
- flexdoc-0.1.0/src/flexdoc/util/__init__.py +11 -0
- flexdoc-0.1.0/src/flexdoc/util/read_time.py +59 -0
- flexdoc-0.1.0/src/flexdoc/util/token_estimate.py +34 -0
- flexdoc-0.1.0/tests/__init__.py +0 -0
- flexdoc-0.1.0/tests/docs/__init__.py +0 -0
- flexdoc-0.1.0/tests/docs/test_base_blocks.py +469 -0
- flexdoc-0.1.0/tests/docs/test_block_info.py +87 -0
- flexdoc-0.1.0/tests/docs/test_block_types.py +334 -0
- flexdoc-0.1.0/tests/docs/test_blocks.py +326 -0
- flexdoc-0.1.0/tests/docs/test_caching_threadsafe.py +123 -0
- flexdoc-0.1.0/tests/docs/test_collect.py +346 -0
- flexdoc-0.1.0/tests/docs/test_doc_graph.py +329 -0
- flexdoc-0.1.0/tests/docs/test_flex_doc.py +455 -0
- flexdoc-0.1.0/tests/docs/test_footnote_ref.py +37 -0
- flexdoc-0.1.0/tests/docs/test_frontmatter.py +122 -0
- flexdoc-0.1.0/tests/docs/test_interval_index.py +111 -0
- flexdoc-0.1.0/tests/docs/test_links.py +153 -0
- flexdoc-0.1.0/tests/docs/test_node.py +60 -0
- flexdoc-0.1.0/tests/docs/test_node_table.py +377 -0
- flexdoc-0.1.0/tests/docs/test_offsets.py +107 -0
- flexdoc-0.1.0/tests/docs/test_sections.py +159 -0
- flexdoc-0.1.0/tests/docs/test_span_ref.py +245 -0
- flexdoc-0.1.0/tests/docs/test_spans.py +116 -0
- flexdoc-0.1.0/tests/docs/test_subdoc_and_empty.py +31 -0
- flexdoc-0.1.0/tests/docs/test_token_diffs.py +128 -0
- flexdoc-0.1.0/tests/docs/test_token_mapping.py +119 -0
- flexdoc-0.1.0/tests/docs/test_token_validation.py +40 -0
- flexdoc-0.1.0/tests/docs/test_wordtoks.py +98 -0
- flexdoc-0.1.0/tests/golden/README.md +57 -0
- flexdoc-0.1.0/tests/golden/documents/footnotes_refs.md +15 -0
- flexdoc-0.1.0/tests/golden/documents/inline_html.md +16 -0
- flexdoc-0.1.0/tests/golden/documents/kitchen_sink.md +29 -0
- flexdoc-0.1.0/tests/golden/documents/malformed.md +20 -0
- flexdoc-0.1.0/tests/golden/documents/nested_list.md +17 -0
- flexdoc-0.1.0/tests/golden/documents/tight_vs_loose.md +18 -0
- flexdoc-0.1.0/tests/golden/documents/unicode.md +11 -0
- flexdoc-0.1.0/tests/golden/expected/footnotes_refs/docgraph.yaml +63 -0
- flexdoc-0.1.0/tests/golden/expected/footnotes_refs/reassembled.md +9 -0
- flexdoc-0.1.0/tests/golden/expected/footnotes_refs/report.yaml +216 -0
- flexdoc-0.1.0/tests/golden/expected/inline_html/docgraph.yaml +63 -0
- flexdoc-0.1.0/tests/golden/expected/inline_html/reassembled.md +11 -0
- flexdoc-0.1.0/tests/golden/expected/inline_html/report.yaml +173 -0
- flexdoc-0.1.0/tests/golden/expected/kitchen_sink/docgraph.yaml +264 -0
- flexdoc-0.1.0/tests/golden/expected/kitchen_sink/reassembled.md +17 -0
- flexdoc-0.1.0/tests/golden/expected/kitchen_sink/report.yaml +383 -0
- flexdoc-0.1.0/tests/golden/expected/malformed/docgraph.yaml +45 -0
- flexdoc-0.1.0/tests/golden/expected/malformed/reassembled.md +11 -0
- flexdoc-0.1.0/tests/golden/expected/malformed/report.yaml +127 -0
- flexdoc-0.1.0/tests/golden/expected/nested_list/docgraph.yaml +199 -0
- flexdoc-0.1.0/tests/golden/expected/nested_list/reassembled.md +7 -0
- flexdoc-0.1.0/tests/golden/expected/nested_list/report.yaml +195 -0
- flexdoc-0.1.0/tests/golden/expected/tight_vs_loose/docgraph.yaml +187 -0
- flexdoc-0.1.0/tests/golden/expected/tight_vs_loose/reassembled.md +11 -0
- flexdoc-0.1.0/tests/golden/expected/tight_vs_loose/report.yaml +220 -0
- flexdoc-0.1.0/tests/golden/expected/unicode/docgraph.yaml +86 -0
- flexdoc-0.1.0/tests/golden/expected/unicode/reassembled.md +5 -0
- flexdoc-0.1.0/tests/golden/expected/unicode/report.yaml +132 -0
- flexdoc-0.1.0/tests/golden/test_golden_docs.py +156 -0
- flexdoc-0.1.0/tests/html/__init__.py +0 -0
- flexdoc-0.1.0/tests/html/test_html_tag_hardening.py +24 -0
- flexdoc-0.1.0/tests/html/test_html_tags.py +843 -0
- flexdoc-0.1.0/tests/html/test_html_validation_and_classes.py +19 -0
- flexdoc-0.1.0/tests/html/test_timestamps.py +76 -0
- flexdoc-0.1.0/tests/test_examples.py +24 -0
- flexdoc-0.1.0/tests/test_root_api.py +53 -0
- flexdoc-0.1.0/tests/test_supply_chain.py +41 -0
- flexdoc-0.1.0/uv.lock +1149 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: simple-modern-uv
|
|
3
|
+
description: >-
|
|
4
|
+
Set up, modernize, or update Python projects using the simple-modern-uv template:
|
|
5
|
+
uv, ruff, BasedPyright, pytest, GitHub Actions CI, and tag-driven PyPI publishing.
|
|
6
|
+
Use when starting a new Python project or package; when modernizing, upgrading, or
|
|
7
|
+
migrating an existing Python package to uv (from Poetry, setuptools, pip,
|
|
8
|
+
requirements.txt, or PDM); or when updating a project already built from
|
|
9
|
+
simple-modern-uv to the latest template.
|
|
10
|
+
license: MIT
|
|
11
|
+
compatibility: Requires git and uv (https://docs.astral.sh/uv/); network access to GitHub
|
|
12
|
+
metadata:
|
|
13
|
+
author: jlevy (github.com/jlevy)
|
|
14
|
+
source: https://github.com/jlevy/simple-modern-uv
|
|
15
|
+
allowed-tools: Bash(uvx:*), Bash(uv:*), Bash(git:*), Bash(make:*), Bash(gh:*), Read, Write, Edit
|
|
16
|
+
---
|
|
17
|
+
# simple-modern-uv: Modern Python Project Setup
|
|
18
|
+
|
|
19
|
+
[simple-modern-uv](https://github.com/jlevy/simple-modern-uv) is a minimal, modern
|
|
20
|
+
Python project template: **uv** for everything package-related, **ruff** for
|
|
21
|
+
lint/format, **BasedPyright** for type checking, **pytest**, GitHub Actions CI, and
|
|
22
|
+
tag-driven publishing to PyPI with dynamic versioning (the git tag is the version).
|
|
23
|
+
It is a [Copier](https://copier.readthedocs.io) template, so projects can pull future
|
|
24
|
+
template improvements at any time with `copier update`.
|
|
25
|
+
|
|
26
|
+
There are three workflows.
|
|
27
|
+
Pick by situation:
|
|
28
|
+
|
|
29
|
+
1. **New project**: create a project from scratch.
|
|
30
|
+
2. **Upgrade existing project**: convert any existing Python package to this template’s
|
|
31
|
+
structure and tooling.
|
|
32
|
+
See [references/adopt-existing.md](references/adopt-existing.md).
|
|
33
|
+
3. **Update templated project**: the project has a `.copier-answers.yml` from this
|
|
34
|
+
template; pull the latest template changes.
|
|
35
|
+
|
|
36
|
+
For all workflows: when something fails, check [references/faq.md](references/faq.md)
|
|
37
|
+
before improvising. For post-setup customization (license change, private/unpublished
|
|
38
|
+
package, entry points), see [references/customize.md](references/customize.md).
|
|
39
|
+
|
|
40
|
+
If this file was fetched from a URL rather than installed as a folder, fetch the
|
|
41
|
+
reference files from the **same ref**: replace `SKILL.md` in the URL you used with
|
|
42
|
+
`references/<name>.md` (don’t mix a pinned `SKILL.md` with references from `main`).
|
|
43
|
+
|
|
44
|
+
## The Interview: What to Ask the User (and What Not to Ask)
|
|
45
|
+
|
|
46
|
+
Every workflow uses the same answer set (the template’s questions in
|
|
47
|
+
[copier.yml](https://github.com/jlevy/simple-modern-uv/blob/main/copier.yml)). **Infer
|
|
48
|
+
everything you can, then ask the user to confirm once, in a single batched message.**
|
|
49
|
+
Never ask one question at a time, and never ask about things listed under “conventions”
|
|
50
|
+
below.
|
|
51
|
+
|
|
52
|
+
**Essentials** (confirm these; they are the user’s decisions):
|
|
53
|
+
|
|
54
|
+
| Answer key | Meaning | Notes |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| `package_name` | PyPI package and GitHub repo name | Normalize to **kebab-case** (`acme-widgets`); show the user the name you derived |
|
|
57
|
+
| `package_module` | Python module name | Derived automatically as **snake_case** (`acme_widgets`); show it, don’t ask |
|
|
58
|
+
| `package_description` | One-line description | |
|
|
59
|
+
| `package_license` | `MIT` (default), `Apache-2.0`, `BSD-3-Clause`, `AGPL-3.0-or-later`, `Proprietary`, or `None` (decide later) | Always surface, naming the default |
|
|
60
|
+
| `publish_to_pypi` | `true` (default) or `false` | Always surface; `false` for private packages and apps |
|
|
61
|
+
|
|
62
|
+
For `package_author_name`, `package_author_email`, and `package_github_org`, **infer**
|
|
63
|
+
values from `git config user.name` and `user.email` and the repo’s remote or
|
|
64
|
+
`gh auth status`, and include them in the confirmation summary rather than asking.
|
|
65
|
+
|
|
66
|
+
**Conventions** (apply silently; mention in your final summary; deviate only if the user
|
|
67
|
+
explicitly asks): `src/` layout; first version is the git tag `v0.1.0` for a new project
|
|
68
|
+
(for upgrades of published packages, the next version above what’s on PyPI); Python
|
|
69
|
+
3.11+; line length 100; the template’s tool choices.
|
|
70
|
+
These are never questions.
|
|
71
|
+
|
|
72
|
+
## Workflow 1: New Project
|
|
73
|
+
|
|
74
|
+
1. Run the interview (above).
|
|
75
|
+
Then render, replacing the answer values:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
uvx copier@9.15.1 copy --defaults \
|
|
79
|
+
--data package_name=acme-widgets \
|
|
80
|
+
--data "package_description=One-line description" \
|
|
81
|
+
--data "package_author_name=Jane Doe" \
|
|
82
|
+
--data package_author_email=jane@example.com \
|
|
83
|
+
--data package_github_org=acme \
|
|
84
|
+
--data package_license=MIT \
|
|
85
|
+
--data publish_to_pypi=true \
|
|
86
|
+
gh:jlevy/simple-modern-uv acme-widgets
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
(Omit `--data` keys that should take defaults.
|
|
90
|
+
`package_module` derives automatically from `package_name`.)
|
|
91
|
+
|
|
92
|
+
2. Initialize git, commit, then install.
|
|
93
|
+
Sync only after the first commit: dynamic versioning reads git, and syncing first
|
|
94
|
+
leaves the editable install’s version at `0.0.0`.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
cd acme-widgets
|
|
98
|
+
git init --initial-branch=main
|
|
99
|
+
git add . && git commit -m "Initial commit from simple-modern-uv."
|
|
100
|
+
uv sync --all-extras
|
|
101
|
+
git add uv.lock && git commit -m "Add uv.lock."
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
3. **Verify**: `make lint` and `make test` must pass.
|
|
105
|
+
Fix anything that doesn’t before declaring success.
|
|
106
|
+
|
|
107
|
+
4. Offer next steps (don’t just stop): create the GitHub repo and push (an empty repo
|
|
108
|
+
with no README, license, or .gitignore; the template provides them); fill in
|
|
109
|
+
`README.md`; if publishing, `docs/publishing.md` covers the one-time PyPI Trusted
|
|
110
|
+
Publisher setup; tag `v0.1.0` when ready to release.
|
|
111
|
+
|
|
112
|
+
## Workflow 2: Upgrade an Existing Project
|
|
113
|
+
|
|
114
|
+
Follow [references/adopt-existing.md](references/adopt-existing.md).
|
|
115
|
+
In brief: run the interview with answers **inferred from the repo itself**
|
|
116
|
+
(pyproject/setup.py metadata, LICENSE file, git remote, PyPI presence), render the
|
|
117
|
+
template into a sibling temp directory with those answers, merge the template’s
|
|
118
|
+
structure into the project (preserving its dependencies, metadata, and code), translate
|
|
119
|
+
old tool configs to uv equivalents, write `.copier-answers.yml` so future updates work,
|
|
120
|
+
and verify with `uv sync`, lint, and tests.
|
|
121
|
+
Land everything on a branch as one reviewable change.
|
|
122
|
+
|
|
123
|
+
## Workflow 3: Update a Templated Project
|
|
124
|
+
|
|
125
|
+
Requires a clean working tree and the project’s `.copier-answers.yml`.
|
|
126
|
+
|
|
127
|
+
1. If the template has questions the project has never answered (keys present in the
|
|
128
|
+
template’s `copier.yml` but missing from the project’s `.copier-answers.yml`), first
|
|
129
|
+
check the project’s actual state and pass matching `--data` so defaults can’t revert
|
|
130
|
+
hand customizations (see “Reconciling New Questions on Update” in
|
|
131
|
+
[references/customize.md](references/customize.md)).
|
|
132
|
+
|
|
133
|
+
2. ```bash
|
|
134
|
+
uvx copier@9.15.1 update --defaults --skip-answered
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
3. Resolve any `*.rej` files or inline conflict markers, re-run `make lint` and
|
|
138
|
+
`make test`, then summarize what the template update changed (check the
|
|
139
|
+
[release notes](https://github.com/jlevy/simple-modern-uv/releases)).
|
|
140
|
+
|
|
141
|
+
## Verification Gate (All Workflows)
|
|
142
|
+
|
|
143
|
+
Before reporting success: `uv sync --all-extras`, `make lint`, and `make test` all pass,
|
|
144
|
+
and for publishable packages `uv build` produces a wheel with a sensible version
|
|
145
|
+
(requires at least one git commit; see the FAQ if the version looks wrong).
|
|
146
|
+
Report what you ran and the results.
|
|
147
|
+
|
|
148
|
+
<!-- This document follows common-doc-guidelines.md.
|
|
149
|
+
See github.com/jlevy/practical-prose and review guidelines before editing.
|
|
150
|
+
-->
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Upgrading an Existing Project to simple-modern-uv
|
|
2
|
+
|
|
3
|
+
Convert an existing Python package (setuptools/pip, Poetry, PDM, hatch, or plain
|
|
4
|
+
scripts) to the simple-modern-uv structure.
|
|
5
|
+
The approach: render the template next to the project, then merge it in deliberately;
|
|
6
|
+
never blindly overwrite.
|
|
7
|
+
Work on a branch and land the whole migration as one reviewable change.
|
|
8
|
+
|
|
9
|
+
Out of scope (stop and tell the user): C extensions or custom build steps, conda
|
|
10
|
+
environments, monorepos/workspaces with multiple packages.
|
|
11
|
+
These need decisions a checklist shouldn’t make.
|
|
12
|
+
|
|
13
|
+
## Step 1: Infer the Interview Answers from the Repo
|
|
14
|
+
|
|
15
|
+
| Answer | Where to look |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `package_name` | `[project] name`, `setup.py name=`, `[tool.poetry] name`, or the repo name |
|
|
18
|
+
| `package_description` | `[project] description` or `setup.py description=` |
|
|
19
|
+
| `package_author_name` / `email` | `[project] authors`, `[tool.poetry] authors`, or git log |
|
|
20
|
+
| `package_github_org` | `git remote get-url origin` |
|
|
21
|
+
| `package_license` | `LICENSE` file contents and `license` field/classifiers; map to MIT, Apache-2.0, BSD-3-Clause, or AGPL-3.0-or-later; no license found maps to None; else Proprietary |
|
|
22
|
+
| `publish_to_pypi` | `true` if the package is on PyPI (`uv pip index` or check pypi.org) or clearly intended for it; `false` for apps/private code (a `Private :: Do Not Upload` classifier is a definitive no) |
|
|
23
|
+
|
|
24
|
+
Confirm the essentials with the user in one batched message (per the interview contract
|
|
25
|
+
in SKILL.md), flagging anything that will change.
|
|
26
|
+
One decision that must be surfaced if it applies: if the project currently supports
|
|
27
|
+
Python older than 3.11 **and** is published, adopting the template raises
|
|
28
|
+
`requires-python` to `>=3.11`. Dropping versions for existing users is the user’s call,
|
|
29
|
+
not yours.
|
|
30
|
+
|
|
31
|
+
## Step 2: Render the Template Beside the Project
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cd ..
|
|
35
|
+
uvx copier@9.15.1 copy --defaults \
|
|
36
|
+
--data package_name=<name> ... \
|
|
37
|
+
gh:jlevy/simple-modern-uv <project>-template-render
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Use the render as the source of truth for structure; you will copy from it into the real
|
|
41
|
+
project.
|
|
42
|
+
|
|
43
|
+
## Step 3: Merge Structure into the Project
|
|
44
|
+
|
|
45
|
+
Copy from the render, adapting as you go:
|
|
46
|
+
|
|
47
|
+
- **`pyproject.toml`**: start from the rendered one and port the project’s reality into
|
|
48
|
+
it: `dependencies`, extras, entry points, tool configs that should survive (translate
|
|
49
|
+
as below). Keep the rendered `[build-system]` (hatchling with uv-dynamic-versioning),
|
|
50
|
+
`[tool.uv]`, `[tool.ruff]`, `[tool.basedpyright]`, `[tool.pytest.ini_options]`
|
|
51
|
+
sections.
|
|
52
|
+
- **Code layout**: move code to `src/<module>/` (the convention; if the user insists on
|
|
53
|
+
a flat layout, adjust `packages` and `testpaths` instead).
|
|
54
|
+
Ensure `py.typed` exists in the package.
|
|
55
|
+
Tests go in `tests/`.
|
|
56
|
+
- **Copy verbatim**: `devtools/lint.py`, `Makefile`, `.gitignore` (merge with existing
|
|
57
|
+
entries), `.github/workflows/ci.yml` (and `publish.yml` if publishing),
|
|
58
|
+
`docs/installation.md`, `docs/development.md`.
|
|
59
|
+
- **Agent instruction files**: copy `AGENTS.md` and `CLAUDE.md` from the render if the
|
|
60
|
+
project has neither.
|
|
61
|
+
If the project already has an `AGENTS.md`, keep its content and fold in the template’s
|
|
62
|
+
build/test commands.
|
|
63
|
+
If it already has a `CLAUDE.md`, don’t overwrite it; add the `@AGENTS.md` import line
|
|
64
|
+
at the top so Claude Code picks up the shared conventions.
|
|
65
|
+
- **`.copier-answers.yml`**: copy from the render.
|
|
66
|
+
This is what makes future `copier update` work; without it the project is orphaned
|
|
67
|
+
from the template.
|
|
68
|
+
- **Delete superseded files** (after their content is ported): `setup.py`, `setup.cfg`,
|
|
69
|
+
`requirements*.txt`, `poetry.lock`, `pdm.lock`, `Pipfile*`, old
|
|
70
|
+
`tox.ini`/`.flake8`/`mypy.ini`/`.pylintrc`, old CI workflows that the template’s CI
|
|
71
|
+
replaces.
|
|
72
|
+
|
|
73
|
+
## Per-Source Translations
|
|
74
|
+
|
|
75
|
+
**Poetry**:
|
|
76
|
+
|
|
77
|
+
- Caret/tilde specs → PEP 621 ranges: `^1.2.3` → `>=1.2.3,<2.0.0`; `~1.2.3` →
|
|
78
|
+
`>=1.2.3,<1.3.0`. Prefer simple `>=` floors where the project doesn’t actually need
|
|
79
|
+
upper bounds.
|
|
80
|
+
- `[tool.poetry.dependencies]` → `[project] dependencies` (drop the `python` entry;
|
|
81
|
+
that’s `requires-python`).
|
|
82
|
+
- `[tool.poetry.group.dev.dependencies]` (or `dev-dependencies`) →
|
|
83
|
+
`[dependency-groups] dev`.
|
|
84
|
+
- `[tool.poetry.scripts]` → `[project.scripts]`.
|
|
85
|
+
- `[tool.poetry] version` → delete; the version now comes from git tags
|
|
86
|
+
(`dynamic = ["version"]`).
|
|
87
|
+
- `packages = [{include = "pkg", from = "src"}]` → hatch’s
|
|
88
|
+
`[tool.hatch.build.targets.wheel] packages = ["src/pkg"]` (already in the rendered
|
|
89
|
+
file).
|
|
90
|
+
|
|
91
|
+
**setuptools/pip**:
|
|
92
|
+
|
|
93
|
+
- `setup.py`/`setup.cfg` metadata → `[project]` table.
|
|
94
|
+
`install_requires` → `dependencies`; `extras_require` →
|
|
95
|
+
`[project.optional-dependencies]`; `entry_points["console_scripts"]` →
|
|
96
|
+
`[project.scripts]`.
|
|
97
|
+
- `requirements.txt` → runtime deps to `dependencies`; `requirements-dev.txt` →
|
|
98
|
+
`[dependency-groups] dev`. Drop exact `==` pins unless genuinely required; `uv.lock`
|
|
99
|
+
provides reproducibility.
|
|
100
|
+
|
|
101
|
+
**mypy → BasedPyright**: drop `mypy.ini`/`[tool.mypy]`; the rendered
|
|
102
|
+
`[tool.basedpyright]` block is the starting point.
|
|
103
|
+
Existing `# type: ignore` comments still work.
|
|
104
|
+
If legacy code produces a wall of errors, see the FAQ: relax first, ratchet later.
|
|
105
|
+
|
|
106
|
+
**Other linters (flake8, isort, black, pylint)**: delete their configs; ruff covers them
|
|
107
|
+
(`I` rules replace isort, the formatter replaces black).
|
|
108
|
+
|
|
109
|
+
## Step 4: Lock, Verify, Iterate
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
uv sync --all-extras # creates uv.lock; commit it
|
|
113
|
+
make lint # codespell + ruff + basedpyright
|
|
114
|
+
make test
|
|
115
|
+
uv build # only if publishing
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Fix failures using [faq.md](faq.md).
|
|
119
|
+
Lint/type errors in legacy code are normal on first run: auto-fix what `make lint`
|
|
120
|
+
fixes, relax basedpyright rules where the noise is unhelpful (leave a comment), and
|
|
121
|
+
don’t rewrite working code just to satisfy a rule.
|
|
122
|
+
|
|
123
|
+
## Step 5: Versioning and Finish
|
|
124
|
+
|
|
125
|
+
- Dynamic versioning needs a tag: if the package is on PyPI at version X.Y.Z, tag the
|
|
126
|
+
merge commit `vX.Y.(Z+1)` (or the next minor) **after** merging; an unpublished
|
|
127
|
+
project starts at `v0.1.0`. Until a tag exists, builds get a dev version: fine for CI,
|
|
128
|
+
wrong for release.
|
|
129
|
+
- Commit everything (including `uv.lock` and `.copier-answers.yml`) on the branch and
|
|
130
|
+
summarize: what moved, what was translated, what was deleted, anything the user should
|
|
131
|
+
review by hand (license, classifiers, CI triggers).
|
|
132
|
+
|
|
133
|
+
<!-- This document follows common-doc-guidelines.md.
|
|
134
|
+
See github.com/jlevy/practical-prose and review guidelines before editing.
|
|
135
|
+
-->
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Customizing a simple-modern-uv Project
|
|
2
|
+
|
|
3
|
+
Common customizations after (or during) setup.
|
|
4
|
+
Where a customization maps to a template question, prefer answering the question over
|
|
5
|
+
hand-editing: hand edits to template-managed files can be reverted by a later
|
|
6
|
+
`copier update` (see “Reconciling New Questions on Update” below).
|
|
7
|
+
|
|
8
|
+
## Changing the License
|
|
9
|
+
|
|
10
|
+
Preferred: set the `package_license` answer (`MIT`, `Apache-2.0`, `BSD-3-Clause`,
|
|
11
|
+
`AGPL-3.0-or-later`, `Proprietary`, or `None` to decide later) when rendering, or
|
|
12
|
+
re-answer it later with:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
uvx copier@9.15.1 update --data package_license=Apache-2.0
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This updates the `LICENSE` file and the `license` field in `pyproject.toml` together,
|
|
19
|
+
and records the answer in `.copier-answers.yml`. `None` renders no `LICENSE` file and no
|
|
20
|
+
`license` field; re-answer the question when the project picks one.
|
|
21
|
+
For a license outside the template’s choices: pick `Proprietary` (so the template stops
|
|
22
|
+
managing it), then replace `LICENSE` and set `license` in `pyproject.toml` to the
|
|
23
|
+
correct SPDX identifier yourself.
|
|
24
|
+
|
|
25
|
+
## Private or Unpublished Packages
|
|
26
|
+
|
|
27
|
+
Set `publish_to_pypi=false` (at render time, or via
|
|
28
|
+
`copier update --data publish_to_pypi=false`). This removes
|
|
29
|
+
`.github/workflows/publish.yml` and `docs/publishing.md`, and adds the
|
|
30
|
+
`Private :: Do Not Upload` classifier so PyPI rejects an accidental upload.
|
|
31
|
+
Everything else (CI, lint, tests, versioning) keeps working.
|
|
32
|
+
To start publishing later, flip the answer back the same way, then do the one-time
|
|
33
|
+
Trusted Publisher setup in `docs/publishing.md`.
|
|
34
|
+
|
|
35
|
+
## Reconciling New Questions on Update
|
|
36
|
+
|
|
37
|
+
When the template adds questions over time, a project that predates them has no recorded
|
|
38
|
+
answer, and `copier update --defaults` fills in the default, which can contradict hand
|
|
39
|
+
edits made before the question existed.
|
|
40
|
+
Before updating an older project, check its actual state and pass explicit `--data`:
|
|
41
|
+
|
|
42
|
+
- Hand-replaced license?
|
|
43
|
+
Pass the matching `package_license` (or `Proprietary` for anything custom).
|
|
44
|
+
- Deleted `publish.yml` by hand, or has the `Private :: Do Not Upload` classifier?
|
|
45
|
+
Pass `publish_to_pypi=false`.
|
|
46
|
+
|
|
47
|
+
Rule of thumb: defaults describe a fresh project, not yours; anywhere the project
|
|
48
|
+
visibly deviates from a fresh render, make the answer explicit.
|
|
49
|
+
|
|
50
|
+
## Apps and CLIs (vs. Libraries)
|
|
51
|
+
|
|
52
|
+
- Entry points live in `[project.scripts]`: `mycli = "my_module.cli:main"`; then
|
|
53
|
+
`uv run mycli` works and installs expose the command.
|
|
54
|
+
- An app that’s not a library usually wants `publish_to_pypi=false`; it still gets the
|
|
55
|
+
full dev workflow.
|
|
56
|
+
- For a long-lived service, consider pinning the Python version with a `.python-version`
|
|
57
|
+
file (uv reads it automatically).
|
|
58
|
+
|
|
59
|
+
## Other Common Tweaks
|
|
60
|
+
|
|
61
|
+
- **Line length**: `[tool.ruff] line-length` in `pyproject.toml` (default 100; black
|
|
62
|
+
uses 88).
|
|
63
|
+
- **Stricter/looser type checking**: toggle the commented `report*` settings in
|
|
64
|
+
`[tool.basedpyright]`; the template ships a pragmatic middle ground.
|
|
65
|
+
- **More ruff rules**: uncomment entries in `[tool.ruff.lint] select` (e.g. `"D"` for
|
|
66
|
+
docstring rules, `"SIM"` for simplifications).
|
|
67
|
+
- **OS matrix in CI**: edit `os:` in `.github/workflows/ci.yml`
|
|
68
|
+
(`["ubuntu-latest", "macos-latest", "windows-latest"]`).
|
|
69
|
+
- **Spell-check exceptions**: `[tool.codespell] ignore-words-list`.
|
|
70
|
+
|
|
71
|
+
After any customization, re-run `make lint` and `make test`.
|
|
72
|
+
|
|
73
|
+
<!-- This document follows common-doc-guidelines.md.
|
|
74
|
+
See github.com/jlevy/practical-prose and review guidelines before editing.
|
|
75
|
+
-->
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# FAQ: Common Problems
|
|
2
|
+
|
|
3
|
+
Failure modes seen in real setups and migrations, with fixes.
|
|
4
|
+
Check here before improvising.
|
|
5
|
+
|
|
6
|
+
## Build Fails: “This does not appear to be a Git project”
|
|
7
|
+
|
|
8
|
+
`uv-dynamic-versioning` reads the version from git.
|
|
9
|
+
The project must be a git repo with at least one commit before `uv build` (or any
|
|
10
|
+
build-backend invocation, including the editable install during `uv sync`) can resolve a
|
|
11
|
+
version. Fix:
|
|
12
|
+
`git init --initial-branch=main && git add . && git commit -m "Initial commit"`.
|
|
13
|
+
|
|
14
|
+
## Version Is 0.0.0, 0.1.devN, or Otherwise Wrong
|
|
15
|
+
|
|
16
|
+
No git tag yet: dynamic versioning derives the version from the latest `v*` tag.
|
|
17
|
+
|
|
18
|
+
- New project: tag `v0.1.0` when ready to release; dev versions before that are normal
|
|
19
|
+
and harmless for CI.
|
|
20
|
+
- Migrated package already on PyPI at X.Y.Z: the first tag must be **greater** than
|
|
21
|
+
X.Y.Z (e.g. `vX.Y.(Z+1)`), or the publish will be rejected as a duplicate/downgrade.
|
|
22
|
+
- Tag exists but ignored: tags must look like `v1.2.3`; also check CI uses
|
|
23
|
+
`fetch-depth: 0` (the template’s workflows do) so tags are available.
|
|
24
|
+
- `importlib.metadata` still reports an old version after committing or tagging: the
|
|
25
|
+
editable install’s metadata is captured at sync time and uv won’t refresh it on its
|
|
26
|
+
own. Run `uv sync --reinstall-package <module>` (and sync only after the first commit
|
|
27
|
+
on new projects).
|
|
28
|
+
|
|
29
|
+
## `uv sync` Fails on Python Version
|
|
30
|
+
|
|
31
|
+
The template requires Python 3.11+. `uv python install` downloads a managed interpreter;
|
|
32
|
+
pin one for the project with `uv python pin 3.12` (writes `.python-version`). If uv
|
|
33
|
+
itself errors with “required-version”, upgrade uv: the template requires uv >= 0.9.
|
|
34
|
+
|
|
35
|
+
## BasedPyright Erupts with Hundreds of Errors on Legacy Code
|
|
36
|
+
|
|
37
|
+
Expected on first run over older code.
|
|
38
|
+
Don’t rewrite the codebase to satisfy it, and don’t turn it off:
|
|
39
|
+
|
|
40
|
+
1. Start from the template’s `[tool.basedpyright]` block, which already relaxes the
|
|
41
|
+
noisiest rules.
|
|
42
|
+
2. Temporarily disable the loudest remaining categories (uncomment the provided
|
|
43
|
+
`report*` lines, e.g. `reportUnknownVariableType = false`), leaving a comment to
|
|
44
|
+
ratchet later.
|
|
45
|
+
3. Existing mypy-style `# type: ignore` comments still work; prefer fixing real findings
|
|
46
|
+
over suppressing them.
|
|
47
|
+
|
|
48
|
+
## codespell Flags Names or Legacy Prose
|
|
49
|
+
|
|
50
|
+
Add exceptions in `pyproject.toml`: `[tool.codespell] ignore-words-list = "word1,word2"`
|
|
51
|
+
or `skip = "path1,path2"`.
|
|
52
|
+
|
|
53
|
+
## Conflicts During `copier update`
|
|
54
|
+
|
|
55
|
+
Copier writes `*.rej` files (or inline conflict markers) where the template and local
|
|
56
|
+
edits collide. Resolve each by hand, keeping the project’s intent; delete the `.rej`
|
|
57
|
+
files; re-run `make lint` and `make test`. A dirty working tree also blocks updates;
|
|
58
|
+
commit or stash first.
|
|
59
|
+
|
|
60
|
+
## `publish.yml` Came Back or License Reverted After an Update
|
|
61
|
+
|
|
62
|
+
The project predates the `publish_to_pypi` and `package_license` questions and the
|
|
63
|
+
update filled them with defaults.
|
|
64
|
+
Re-run the update passing the project’s reality, e.g.
|
|
65
|
+
`uvx copier@9.15.1 update --data publish_to_pypi=false`, and see “Reconciling New
|
|
66
|
+
Questions on Update” in [customize.md](customize.md).
|
|
67
|
+
|
|
68
|
+
## Publish Workflow Fails with OIDC or Permission Errors
|
|
69
|
+
|
|
70
|
+
The one-time PyPI Trusted Publisher setup hasn’t been done for this repo (or the
|
|
71
|
+
workflow filename doesn’t match what PyPI was told).
|
|
72
|
+
Follow `docs/publishing.md` in the project; no API tokens are needed.
|
|
73
|
+
|
|
74
|
+
## Lockfile Resolution Seems Stale or Refuses a Brand-New Release
|
|
75
|
+
|
|
76
|
+
`UV_EXCLUDE_NEWER` (set in the template’s CI) enforces a 14-day supply-chain cooling-off
|
|
77
|
+
window, so releases newer than that are deliberately invisible.
|
|
78
|
+
This is a feature; don’t remove it to get a day-old package.
|
|
79
|
+
Locally, leave the variable unset for normal work, or set it to match CI when debugging
|
|
80
|
+
resolution differences.
|
|
81
|
+
|
|
82
|
+
## Tests Pass Locally but CI Fails on a Python Version
|
|
83
|
+
|
|
84
|
+
The CI matrix runs 3.11–3.14. Most failures are version-specific syntax/stdlib use; run
|
|
85
|
+
the failing version locally with `uv run --python 3.11 pytest`.
|
|
86
|
+
|
|
87
|
+
<!-- This document follows common-doc-guidelines.md.
|
|
88
|
+
See github.com/jlevy/practical-prose and review guidelines before editing.
|
|
89
|
+
-->
|