Guidance for coding agents working on the PyPew repository.
PyPew is a Flask web app for generating Anglican pew sheets from Book of Common Prayer texts. It assembles service details (date/feast, hymns, clergy names, etc.) into a web page or .docx export.
- Package:
pypew(console scriptpypew→pypew.app:main) - Runtime: Python 3.12+ (Flask 2.x, WTForms, Jinja2, Flask-Pydantic)
- Build/packaging: Hatchling (wheel/sdist). PyInstaller used for desktop binaries.
- Dependency/env management: uv (
uv.lock,pyproject.toml) - Versioning:
hatch.versionreads fromsrc/pypew/__init__.py(__version__)
src/pypew/— installed package__init__.py— package metadata/version__main__.py—python -m pypewentryapp.py— Flask app factory/CLI entry (main())wsgi.py— WSGI app (app) for gunicornmodels.py,models_base.py— liturgical models and logicviews/— route handlers (Flask views/blueprints)forms.py— WTForms formsfilters.py— Jinja template filtersdateexpr.py— date/feast expression parsingutils.py— utilitiespaths.py— filesystem/resource pathstemplates/— Jinja2 templates (bundled)static/— CSS/JS/assets (bundled)pypew.js— shared frontend helpers loaded on every page (tooltips, print buttons, navbar active state, toasts)feastList.js,serviceForm.js,pewSheet.js— per-page behaviourstyles.css— app styles;bootstrap*,favicon_io/are vendored, do not edit
data/— bundled data files (e.g.neh.csv, feast data). Tracked CSVs here are required at runtime and are explicitly included in wheels/sdist by hatch.
tests/— unittest-based teststest_pypew.py,test_dateexpr.pyfeast_dates.json— test fixtures/data
scripts/— development helper scripts (not installed with the package). Install withuv sync --extra scriptsif needed.deploy/— deployment helpers (e.g.deploy/srcf.sh)views/— root-level views directory (separate fromsrc/pypew/views/); treat as repository artifacts, not part of the installed package.- Top-level build/packaging:
pyproject.toml,uv.lock,build.sh,build.bat,pypew.spec,wsgi.spec,.python-version,.env(local config, not committed).
- Line length: 88 (Ruff)
- Target Python: 3.12
- Formatter/linter: Ruff. Configured in
pyproject.tomlunder[tool.ruff]. - Lint:
uv run ruff check . - Format:
uv run ruff format . - Imports: follow Ruff isort rules (
known-first-party = ["pypew"], src layout). Prefer absolute imports within the package. - Comments: do not add comments unless explicitly asked. Preserve existing comments/markers.
- Ruff ignores: Many rules are explicitly ignored in
pyproject.tomlwith# FIXME(<CODE>)markers. Do not remove/“fix” these ignores unless explicitly instructed; the codebase has deliberate style choices tied to them. - Codebase consistency: Mimic existing patterns, naming, error handling style, and library choices. When editing, read surrounding context (especially imports) first.
There is no JS toolchain — no npm, bundler, transpiler, linter or test runner. Assets are served as-is by Flask from src/pypew/static/ via url_for('static', ...). Keep it that way unless explicitly asked.
- No inline
<script>or<style>in templates. Templates render markup and data only. All JS lives insrc/pypew/static/*.js; app CSS lives instyles.css. - Never interpolate Jinja into script source. Pass server values to JS via
data-*attributes on an element, then read them withelem.dataset.*. For URLs, put aurl_fortemplate in a data attribute and substitute a placeholder (slug='__slug__'→.replace('__slug__', slug)). This keepsurl_for(and thereforeSCRIPT_NAME/APPLICATION_ROOT) authoritative. Note that HTML autoescaping is not sufficient protection here — it is the wrong escaping for a JS context (a<script>body does not decode entities, so"stays literal and protects nothing), and newlines are not escaped at all, so a value containing one terminates the string literal and kills the whole script block. Usedata-*regardless of whether a value is currently trusted. - Loading: vendor scripts first (e.g.
bootstrap.bundle.min.js), thenpypew.js, then page scripts. All aredeferred, which guarantees DOM-readiness and document-order execution — so page scripts can rely onbootstrapandwindow.pypewexisting. Page scripts belong in{% block scripts %}of the template that extendsbase.html(not in the{% include %}d partial), which is what keeps that ordering intact. - Module shape: wrap each file in an IIFE with
"use strict", look up your root element first andreturnearly if it's absent. This makes a script safe to load on any page and avoidsTypeErrors onnull. - Shared behaviour goes in
pypew.js(exposed aswindow.pypew), not in a new global. Usepypew.addTooltip+pypew.initTooltipsfor tooltips,.js-printclass for print buttons, andpypew.toastfor toasts.initTooltipsis idempotent, so it is safe to call after injecting new elements. - Data attributes for configuration should live on the container element the script already needs, named
data-<kebab-name>in the template and camelCased ondatasetin JS. Keep element IDs in JS and HTML in sync; if you derive an ID, keep the mapping table explicit rather than relying on case matching. - Progressive enhancement: prefer a server-side default (e.g. a WTForms
default=) over JS that sets a field value on load. Keep Bootstrap toast styles and behaviour —pypew.toastcreates its own toast container at runtime. No external toast CSS is loaded. - Vendored assets (
bootstrap*,favicon_io/) are do-not-edit. Notenotify.js/notify.css(unminified) are the ones actually loaded; there is no minified variant in the tree. - Formatting: 2-space indent, double quotes, semicolons, trailing commas in multi-line literals.
- Verification: there is no JS linter, so after touching JS run
node --check <file>ifnodeis available, thenuv run python -m unittest. Smoke-test that pages render (app.test_client()), that everygetElementById/dataset read in a script resolves against the HTML of the pages that load it, and thatdeferordering still holds.
- Managed with
uv. Install/runtime deps inpyproject.tomldependencies; dev in[dependency-groups].dev(includesruff,parameterized,pandas); build in[dependency-groups].build(pyinstaller>=6). - Optional extras:
hymns(pandas),scripts(pandas, python-slugify). - Do not assume a library exists unless it’s used in the codebase or declared. Check
pyproject.toml/neighboring code before introducing new deps.
- Framework: Python
unittest(no pytest configured). Tests useparameterizedfor parametrization. - Run all tests:
uv run python -m unittest(from repo root) - Test discovery: standard unittest discovery in
tests/. - Adding tests: Follow existing test style in
tests/test_pypew.pyandtests/test_dateexpr.py. Use fixtures liketests/feast_dates.jsonwhen applicable. - Verification: After code changes, run tests. Also run lint/typecheck via ruff (
check/format) as appropriate. If you cannot find the correct verification command, ask. - No frontend tests exist. There is no JS test runner. Cover frontend changes by rendering pages through
app.test_client()and asserting on the HTML, not by adding a JS harness.
- Bundled data: Runtime data lives under
src/pypew/data/.neh.csvis tracked and must remain bundled (Hatch forces it into wheel/sdist artifacts). - Git hygiene: Respect
.gitignore. Do not commit secrets/keys (e.g..env). Do not commit build artifacts (build/,dist/,*.egg-info/, caches). - Editable/source runs:
uv run pypew,uv run python -m pypew,uv run gunicorn pypew.wsgi:app. Debug with--debug, suppress browser with--no-launch.
- Edit existing files; create new files only if explicitly required.
- Keep changes minimal and focused. Avoid broad refactors unless necessary.
- References: When referencing code, include
file_path:line_number(e.g.src/pypew/models.py:123) to help navigation. - Commit policy: Never commit unless explicitly asked. Inspect
git status/diffand stage only intended files. - Git workflow: This repository has branch protection on
main. You cannot push directly tomain. You must push changes to a side branch and open a pull request (PR). Agents may merge PRs as soon as they pass CI. - Local vs installed package: Source lives in
src/pypew/; imports should resolve from the package (src layout). The rootviews/is not part of the installed package.