Skip to content

Add distilled package standards page and review skill - #41

Open
ecomodeller wants to merge 2 commits into
mainfrom
package-standards
Open

Add distilled package standards page and review skill#41
ecomodeller wants to merge 2 commits into
mainfrom
package-standards

Conversation

@ecomodeller

Copy link
Copy Markdown
Member

The course is seven slide decks, which makes it awkward to point someone at a specific rule mid-conversation. standards.md distils it into ~45 normative rules, one linkable heading each, published at standards.html:

…/standards.html#mutable-default-arguments
…/standards.html#composition-over-inheritance
…/standards.html#every-dependency-is-a-decision

Only rules you'd assert at someone — the exposition stays in the slides. That includes rules no tool can check (keep PRs small, composition over inheritance), since the point is lecturing, not just linting.

Issues folded in

Seven that never made it into the slides:

Left open deliberately: #39 (great-docs is still "worth evaluating"), #31 and #32 (pedagogy, belongs in the slides), #25 and #24 (questions you haven't settled).

Review skill

.claude/skills/review-python-package/ audits a package repo against the page and links every finding to the rule it breaks, so the report is something you can paste into a PR and the reader gets the explanation without you there. It runs ruff for ground truth but deliberately won't run pytest/mypy/uv sync unprompted on an unfamiliar repo.

The finding URLs 404 until this merges and Pages rebuilds.

The course content is spread across seven slide decks, which makes it hard
to point someone at a specific rule. standards.md distils it into ~45
normative rules, one linkable heading each, published at standards.html.

Exposition is deliberately left in the slides: the page holds only rules
you would assert at someone, including ones no tool can check.

Also folds in seven issues that never made it into the slides: #40 (the
underscore convention, with the public-API-only amendment to breaking
changes), #37 (cost of dependencies), #36 (long signatures), #35
(inappropriate intimacy), #34 (comments say why), #33/#26 (deprecation),
#22 (changelog).

The review-python-package skill audits a repository against the page and
links each finding to the rule it breaks.
Comment thread standards.md
`FutureWarning` is for end users (always visible). `mypy --enable-error-code=deprecated`
catches uses of `@deprecated` at type-check time.

### Changelog

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nice to have, but is tedious to curate manually. Think twice before spending time on this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — demoted to Nice in 0055f53, with the cost stated plainly ("a stale changelog is worse than none; if nobody reads it, skip it"). Rather than drop the rule, it now points at towncrier / git-cliff so the work lands on the author of each PR instead of on you at release time.

Comment thread standards.md Outdated
success — a non-empty string is truthy.

```python
if is_operable(height=12.0, period=5.0): # returns "No way!" — and this runs

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function returns None. Fix it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0055f53 — the example only had the call site, so nothing showed the mixed return. It now defines is_operable, with the str / None / bool paths visible, plus a line making the implicit-None case explicit as the same bug.

I read this as being about the One return type example. If you meant the clip above it, that one returns None on purpose — it is the bad variant showing mutation — so say the word and I will handle it differently.

Comment thread standards.md Outdated
```

### Keyword-only arguments
*Nice.* `def f(*, x, y)` forces callers to be explicit and lets you reorder parameters later

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be more nuanced. One or two positional arguments are fine, that is usually the data the function is operating upon. More than two positional arguments are a strong code smell.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adopted your wording in 0055f53 and promoted the rule to Recommended. It now says one or two positional parameters is fine (the data being operated on), everything after belongs behind the *, and three or more positionals is a strong smell — resample(df, 3, 0, True) as the counter-example.

This collided with When a long signature is a smell, which said "the smell is not the count". Scoped that one to the total count and cross-linked the two so the page does not argue with itself.

Comment thread standards.md Outdated
```

### Examples that are tested
*Nice.* `doctest` runs the examples in your docstrings. Documentation that is wrong is worse

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Quarto for documentation, where the snippets are run as part of the build is another option.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 0055f53doctest for docstring examples, Quarto for prose pages where the build executes every snippet and fails first.

Comment thread standards.md
```

### Published API documentation
*Recommended.* `mkdocs` + `mkdocstrings` + GitHub Pages, at

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quarto or the new framework Great Docs (wrapping Quarto), or zensical are all viable options.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 0055f53mkdocs + mkdocstrings stays the default, with Quarto, Great Docs and zensical named as viable alternatives (all linked).

Worth deciding whether #39 stays open now that the page names Great Docs.

Comment thread standards.md Outdated
### Published API documentation
*Recommended.* `mkdocs` + `mkdocstrings` + GitHub Pages, at
`https://dhi.github.io/<repository>/`. Note that a private repository can still have a public
website — `robots.txt` hides it from search engines but is not security.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer relevant. With GitHub Enterprise, we can have private pages.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in 0055f53. The robots.txt sentence is gone; the rule now says a private repository can have access-controlled Pages on GitHub Enterprise, and to use that rather than relying on the URL not being found.

Comment thread standards.md Outdated
```

### Lint and format with ruff
*Recommended.* `ruff check` finds unused imports, undefined names and dead variables — usually

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no reason not use ruff for linting and formatting.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your sentence is now the opening of the rule in 0055f53, with the reason spelled out: one binary, no configuration required, replaces flake8 + black + isort.

I left the severity at Recommended rather than promoting it. Blocker is defined at the top of the page as "not fit to share", and an unlinted package is still shareable — it would be the first rule to stretch that definition. Happy to promote it if you would rather the legend bend.

Comment thread standards.md
```

### Makefile
*Nice.* One source of truth for how to run the project's tools, and the fastest onboarding

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider just instead of make, more modern and better cross platform support.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 0055f53 — the heading is now A task runner: Makefile if everyone is on Linux or macOS, just if anyone is on Windows, where make is absent by default and just is a single binary (uv tool install rust-just). Both recipe files are shown, both using uv run. Anchor changed to #a-task-runner; nothing linked to the old one.

Comment thread standards.md
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ["3.10", "3.13"]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Matrix testing is powerful, but don't go overboard. If your package is actually an application, targeting a single Python version might be ok. CI is great, but it is not free.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 0055f53 — "test what you claim and no more", CI is not free, every cell costs minutes on every push. Uses the library/application split the page already makes, cross-linked: an application has one deployment target, so one cell is the honest matrix.

Comment thread standards.md Outdated
## Release

### Publish from a GitHub release
*Recommended.* Tag a release, let a workflow build and publish. Use Trusted Publishers so

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using git tags to track released versions makes your life easy. Just do it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Promoted to its own rule in 0055f53Tag every release, Recommended, annotated vX.Y.Z pushed, framed as what makes "which commit is 1.2.0?" answerable a year later. Two commands, so "just do it" is in there.

Comment thread standards.md Outdated

```yaml
on:
release:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A viable alternative is to trigger automated releases via git tags.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 0055f53 — the rule is now Publish from a tag or a release and shows on: push: tags: ["v*"], with the release trigger as the inline alternative and one clause on the tradeoff: the release gives you somewhere for release notes, the tag is one step fewer. Trusted Publishers works with either.

standards.md:
- Keyword-only arguments: rewritten around positional count — one or two
  for the data, config after the `*`, three+ is the smell. Now Recommended.
- Changelog demoted to Nice; hand curation is real work, so point at
  towncrier/git-cliff instead of prescribing it.
- One return type: show the function, so the None path is visible.
- New rules: Tag every release, Type checking in CI.
- Publish from a tag or a release, showing the `on: push: tags` trigger.
- Test the matrix: CI is not free; test only what you claim to support.
- Docs: name Quarto, Great Docs and zensical as alternatives; replace the
  robots.txt note with access-controlled Pages on GitHub Enterprise.
- Makefile becomes A task runner, covering `just` for Windows.
- Fix checkout@v3 -> v4 and a backwards `min`/`max` in the clip example.

review-python-package skill:
- Use Glob/Read/Grep instead of `ls`, `cat` and piped `grep` — the reviewer
  may be on Windows with no POSIX shell. Table-escaped pipes had also made
  the mutable-default pattern match nothing.
- `uvx ruff`, and say so when the repo has no ruff config.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a linkable standards reference and a Claude Code skill for auditing Python packages.

Changes:

  • Adds distilled package-development standards.
  • Adds an automated package-review skill.
  • Links and documents both resources across the site.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
standards.md Defines the package standards.
README.md Documents the standards and review skill.
index.qmd Links the standards from the course homepage.
CLAUDE.md Records the new repository structure.
.claude/skills/review-python-package/SKILL.md Implements the package-audit workflow.
Suppressed comments (7)

standards.md:69

  • Missing these two tables does not necessarily make a package un-installable: pip can still build legacy setup.py/setup.cfg projects, and some PEP 517 backends use backend-specific metadata. State this as the DHI standard rather than an installability fact to avoid false blocker findings.
### pyproject.toml
*Blocker.* A package without `[build-system]` and `[project]` is not installable. `uv init
--lib` gives you a working one.

standards.md:128

  • A copyright notice does not grant internal users permission to use or redistribute the package, so it cannot substitute for a license. Internal packages still need applicable proprietary/internal license terms or organization-wide terms granting those rights.
### License
*Blocker.* Without a license the package is "all rights reserved" and legally unusable by
others. MIT for open, a copyright notice for internal-only. Check your dependencies' licenses
too.

standards.md:427

  • Quarto does not execute every fenced snippet automatically. Only executable code cells are run when an execution engine is configured, so this rule currently promises documentation testing that ordinary Markdown examples do not receive.
### Examples that are tested
*Nice.* Documentation that is wrong is worse than documentation that is missing. `doctest`
runs the examples in your docstrings. For prose pages, [Quarto](https://quarto.org/) executes
every snippet as part of the build, so the docs cannot ship broken — the build fails first.

standards.md:437

  • The PR description says issue #39 remains open because Great Docs still needs evaluation, but this text already endorses it as a viable alternative and links a different repository than the issue. Remove the endorsement until that evaluation is complete.
`https://dhi.github.io/<repository>/`. [Quarto](https://quarto.org/),
[Great Docs](https://github.com/machow/great-docs) (which wraps Quarto) and
[zensical](https://zensical.org/) are viable alternatives.

standards.md:557

  • Pre-release versions are still indexed and visible on package pages/search; installers merely avoid selecting them in normal resolution. Describe pip's opt-in behavior instead of claiming the release is hidden.
### Pre-releases for anything unfinished
*Nice.* `1.0.0rc1` is not installed by default and does not appear in search — the safe way to
put something in front of users before committing to it.

.claude/skills/review-python-package/SKILL.md:72

  • This regex requires two dots, so it matches self.child._private but misses the common direct access other._private. Searching any direct private attribute access and then applying the existing self._ exclusion catches both forms.
\w+\.\w*\._[a-z]                                  # reaching into another object's internals

.claude/skills/review-python-package/SKILL.md:105

  • This repeats the incorrect assumption that pip cannot build without an explicit [build-system]; legacy setuptools projects remain buildable. Phrase the example as noncompliance with the DHI standard rather than failed installability.
**my_library** — not installable yet

Blockers
  ✗ pyproject.toml — no [build-system], so pip cannot build this
    https://dhi.github.io/python-package-development/standards.html#pyproject.toml

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread standards.md
Comment on lines +54 to +58
### Underscore means internal
*Recommended.* A leading underscore says "not part of the public API". You may change or
remove `_foo` without it counting as a [breaking change](#breaking-changes-bump-major) —
anyone importing it did so at their own risk. Declare the public surface with `__all__` so
the boundary is explicit rather than implied. (Double underscore, `__foo`, is name mangling —
Comment on lines +35 to +39
| Layout, `.gitignore`, `LICENSE`, `README` | Glob `*` and `.*` at the root |
| Build system, metadata, dependencies, versioning | Read `pyproject.toml` |
| CI | Glob `.github/workflows/*`, then Read each |
| Data committed to git | `git ls-files "*.csv" "*.nc" "*.dfs*" "*.xlsx" "*.zip" "*.parquet"` |
| Commit hygiene | `git log --oneline -20` |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants