This document defines coding guidance for AI contributors in PyPNM-CMTS. It complements AGENTS.md and does not replace it.
- Reuse before adding new code.
- Keep diffs minimal and focused.
- Preserve existing naming, spacing, and alignment patterns.
- Favor explicit typing and clear behavior over clever shortcuts.
- Avoid broad refactors unless explicitly requested.
- Read
README.mdbefore making non-trivial changes. - Do not auto-format unless the user explicitly requests formatting.
- Preserve whitespace/alignment in touched files; avoid formatting churn.
- Keep changes scoped to the request; do not refactor unrelated modules.
- Prefer extending existing patterns and shared modules over creating new patterns.
- Use strict typing in production code and avoid generic container imports like
Dict,List,Tuple, andUnion. - Avoid
Anyunless unavoidable and explicitly justified. - Prefer
match/caseover long if/else chains. - No code should contain 3+ nested loops. 2 nested loops are discouraged unless necessary.
system.jsonis the single source of truth.- New configuration namespaces must be implemented as Pydantic BaseModels.
- BaseModels must use one-line
Field(..., description="..."). - Avoid generic
strfor semantic identifiers or paths in public models and APIs; use an existing semantic type or add a new alias insrc/pypnm/lib/types.py. - When working with MAC or inet strings, validate using
MacAddress()orInet()instead of assumingstr(...)formatting is valid. - Request override defaults: missing or null means use
system.jsondefaults; blank strings are invalid. - Treat external
PYTHONPATHentries as install/runtime hazards. - Do not run install or serve commands with
PYTHONPATHpointing at a sibling PyPNM source checkout. - If install/runtime behavior suggests the wrong lower-layer package is being imported, check
PYTHONPATHfirst and preferunset PYTHONPATHbefore deeper debugging.
- All stored timestamps are epoch seconds.
- Convert to ISO-8601 only at display or external response boundaries.
- PyPNM-CMTS FastAPI default port is
8080. - PyPNM-CMTS MkDocs default local docs port is
8081. - When updating scripts, docs, examples, aliases, or smoke helpers, treat
8080and8081as the defaults unless the user explicitly asks otherwise.
- If request via chat request starts with commit-msg, then preface command ./tools/git/git-save.sh with commit-msg ""
- One line summary (max 50 characters)
- One line Summary start: Feature: , Bugfix: , Docs: , Refactor: , Test:
- Detailed description lines (max 72 characters per line); every line after the first must start with
- - When the user asks for a commit message, provide plain text for direct paste into the terminal or UI text box.
- Do not wrap commit message suggestions in quotes (
"), backticks (`), or code fences unless the user explicitly asks for that format. - Prefer detailed commit messages that describe the current change set clearly.
- Do not default to a one-line commit message when the change set is broad; provide a title plus concise bullet points.
- Avoid redundant wording and avoid repeating the exact prior commit message suggestion unless the diff is unchanged and the user explicitly asks to reuse it.
- If the user asks for "in a text box", return plain text only (no markdown fence).
- If the user asks for "in a markdown text box", return the commit message inside a fenced code block with
text.
Example:
Fix RxMER SNMP transport lifecycle between precheck and capture
- Build separate CableModem instances for precheck and capture
- Add CableModem factory helper in RxMER worker
- Add regression test for distinct precheck and capture CableModem instances
Before introducing new types, constants, validators, or storage patterns:
- Check
src/pypnm_cmts/lib/types.pyfor an existing alias. - Check
src/pypnm_cmts/lib/constants.pyfor an existing constant. - Check
src/pypnm_cmts/api/common/for existing shared models and helpers. - Check PyPNM equivalents in the active virtualenv install first (for example
./.env/lib/python*/site-packages/pypnm/) and reuse when suitable. - Prefer existing Pydantic models for public payloads over raw dictionaries.
- Reuse existing PyPNM constants before adding new local constants.
- Reuse PyPNM MacAddress, Inet, and shared PyPNM types before defining CMTS-local equivalents.
- Prefer PyPNM utility functions for shared parsing, normalization, and conversion behavior.
- Introduce CMTS-local constants or types only when no PyPNM equivalent exists, and record the rationale in change notes.
- Keep CaptureWorker classes lean.
- CaptureWorker classes should focus on the PNM operation flow only.
- Move shared back-and-forth behavior to common utilities or base services.
- Do not embed generic transport, parsing, and cross-operation lifecycle mechanics directly in endpoint-specific CaptureWorker classes.
- Use built-in generics like
list[str]and unions likeA | B. - Avoid
Anyunless unavoidable and clearly justified. - Annotate all function arguments and return types.
- Prefer Pydantic
BaseModelfor public interfaces instead of raw dict payloads. - Use shared public aliases from
src/pypnm_cmts/lib/types.py. - Define local aliases only when strictly private and not reused.
- Avoid generic dict arguments for public method contracts; prefer typed models.
- Do not introduce raw generic integer collections like
list[int]in production source. - Use named aliases from
pypnm_cmtslib types for integer collections and optional integer collections. - When a needed alias does not exist in
pypnm_cmtslib types, add it there first, then use the alias.
- Except Python special methods (for example
__init__), place private methods at the bottom of the class. - This applies to methods prefixed with
_,__, or more leading underscores. - Methods with three or more leading underscores (for example
___helper) must include a docstring that states they are internal-only and must not be used outside the class. - Public methods should have detailed docstrings; private methods should have concise docstrings.
- Use absolute package imports in source files.
- Do not use relative imports such as
from .module import ....
- Router files must be lean:
router.pycontains routing glue only (APIRouter configuration, endpoint registration, HTTP status translation).- No business logic in
router.py. Business logic must live inservice.pyfor that route group (same folder) or a shared service module if reused.
- All request/response bodies must be Pydantic BaseModels.
- Prefer POST for payload submission and endpoint contracts (PyPNM default).
- Allow GET only where already present or clearly appropriate (health, readiness, version, status).
- Reuse shared models under the existing
src/pypnm_cmts/api/common/structure (inspect current tree before adding anything new). - Do not block request paths with
time.sleep(). - Do not use
-in endpoint URL path segments. Prefer existing repo-style camelCase such aswebService,servingGroupWorker, andresetCounters.
- All debug-only API endpoints must live under a dedicated debug route area, not mixed into operational, system, orchestrator, or PNM route modules.
- Use a dedicated route package such as
src/pypnm_cmts/api/routes/debug/for debug-only endpoints and related service/schema code. - Public debug endpoint paths must live under
/ops/debug/.... - Debug endpoints must not be enabled by environment-variable export as the primary user workflow.
- Debug endpoints must be enabled explicitly through CLI debug mode, for example
pypnm-cmts serve --debug. - When debug mode is not enabled:
/ops/debug/...routes must not be advertised in OpenAPI/docs./ops/debug/...routes must not be reachable.- Hidden or muted debug routes must return a controlled non-success response if probed.
- Prefer startup-time route registration or schema exclusion over ad hoc per-handler checks when keeping debug endpoints hidden by default.
- User docs must describe debug routes as debug-mode-only tools and show
serve --debugas the enablement path.
- Do not use full module-path logger names in production classes.
- Avoid
logging.getLogger(__name__)in class-based service/router/runtime modules. - Use class-name loggers for class-based components, for example
logging.getLogger(self.__class__.__name__).
- For Markdown documentation examples that include sysDescr payloads, use LANCity as the example vendor and model naming.
- Use class-name logger names in production classes.
- Keep operation labels in CaseSnake inside brackets for all log operations, for example
[REFRESH_HEAVY]and[CM_SYSDESCR_RESULT]. - Use clear operation names with consistent casing in log messages.
- Log only required troubleshooting context fields (for example
sg_id,mac,ip,community) and avoid redundant keys. - Do not expose SNMP community values in INFO, WARNING, or ERROR logs.
- SNMP community values may appear only at DEBUG level, using inline conditional formatting in a single log call.
- Before any scheduled or unscheduled restart of a process, worker, supervisor loop, or background runtime, emit a log line that states the restart is about to happen and why.
- Use
INFOfor planned or non-memory restarts. - Use
WARNINGfor restarts triggered by memory growth, memory creep, RSS threshold breaches, or similar resource pressure.
- Place all PNM SNMP table data type models under
src/pypnm_cmts/pnm/data_type. - Do not place PNM SNMP table data type models under
src/pypnm_cmts/docsis/data_type.
- When a new SNMP table
getorsetentry method is added tosrc/pypnm_cmts/docsis/cmts_operation.py, add a corresponding CLI example undersrc/pypnm_cmts/examples/cliin the same change. - The CLI example must include JSON output support.
- Any CLI example that supports JSON output must provide a
--json-prettyflag to format JSON with indentation. - For set-operation CLI examples, use
--cmts-community-writefor the write community argument.
- For code changes, run
ruff check srcandpytest -q. - For substantial code changes, also run
python3 -m compileall srcandruff format --check .. - For Markdown-only changes, run
mkdocs build -s. - Add focused tests for new behavior and keep tests hermetic when possible.
- Reuse existing test patterns from similar modules before introducing new structures.
- Use
monkeypatchonly in tests. - Do not use
monkeypatchin production modules, runtime code paths, tools, or scripts. - This applies to both
PyPNM-CMTSandPyPNM.
- Every phase deliverable MUST include pytest coverage for new or changed behavior.
- Do not claim a phase item is complete unless pytest has been added and executed (or a concrete blocker is documented).
- Tests must remain hermetic: no live CMTS/cable modem dependencies.
- Keep Markdown compatible with both MkDocs and GitHub rendering.
- Use LANCity for sysDescr examples in Markdown.
- Use consistent placeholders in docs examples:
- MAC:
aa:bb:cc:dd:ee:ff - IP:
192.168.0.100 - system_description JSON:
{"HW_REV":"1.0","VENDOR":"LANCity","BOOTR":"NONE","SW_REV":"1.0.0","MODEL":"LCPET-3"}
- MAC:
- Use SPDX headers in code files where required by repository conventions.
- Do not add SPDX headers to Markdown files.
- Avoid formatting churn unrelated to the requested change.
Before responding:
- Re-read this file and
README.md. - Confirm pytest coverage exists or is explicitly blocked.
- Confirm pytest and ruff output have no deprecation warnings (treat as failures).
- Confirm changes align to the current phase and do not leak scope.
- Confirm formatting and alignment are preserved.
When the user requests "train", read the following sources:
AGENTS.mdsrc/pypnm/lib/(DB/persistence + config helpers)src/pypnm/api/(routing/service patterns, where applicable)src/pypnm_cmts/lib/(DB/persistence + config helpers)src/pypnm_cmts/api/(routing/service patterns, where applicable)tools/agent-review/(all files, if present)