Contributing¶
Documentation examples¶
pyfsr docs live in docs/source/ as Markdown (rendered by Sphinx + MyST). Every
code example falls into one of three tiers — pick the tier by what the example
needs to prove, not by habit. The goal is simple: examples that show output
must be executed and output-matched in CI, so they cannot silently drift from
the code.
1. {doctest} — return-shape examples (executed + output-matched)¶
Use when the example shows what a call returns. The block runs under
make doctest and CI fails if the shown output drifts from the real return
shape.
Start from the global fixtures pre-imported by
doctest_global_setupinconf.py:demo_client()(aFortiSOARover a replay REST session) anddemo_box()(a healthyApplianceover a replay transport). Neither touches the network.Match the exact output. Mask volatile fields (UUIDs, timestamps, sizes that vary run-to-run) with
# doctest: +ELLIPSISand add a short comment saying what varies.Reserve
# doctest: +SKIPfor calls that genuinely need a live box or SSH and cannot be replayed — and say why in a comment.
>>> client = demo_client()
>>> conn = client.connectors
>>> [c.name for c in conn.list_configured()[:3]]
['smtp', 'code-snippet', ...]
>>> conn.resolve_version("mitre-attack")
'2.0.2'
2. {code-block} python — illustrative / copy-paste (linted, not run)¶
Use for examples that need a live appliance to run — write ops, dev_publish,
install, anything mutating state — and so cannot be replayed. Show the expected
shape as an inline # comment, never as matched output. scripts/check_doc_examples.py
lints that every from pyfsr... import and pyfsr.<attr> resolves, so a rename
cannot silently rot the snippet — but it does not execute the body.
conn.install("fortinet-fortisiem", "6.1.0", wait=True) # by name from Content Hub
conn.install_from_file("hello-world-1.0.0.tgz", replace=True)
3. {code-block} sh — CLI commands (flags linted; offline ones executed)¶
check_doc_examples.py checks every --flag against the real pyfsr --help.
Offline-runnable commands (pyfsr playbook ...) are additionally executed
end-to-end by scripts/exec_cli_examples.py with exit-code + stdout
assertions. Live-only commands (pyfsr appliance ...) are documented with their
return shape as {doctest} blocks in appliance-cli.md instead.
Adding a new return-shape example¶
If a
/api/3capture is missing, record it: extendscripts/capture_responses.py, run it against a lab box (creds viatests/config.toml), then trim the raw JSON fromtests/resources/mock_responses/into a*_RESPONSEconstant insrc/pyfsr/_testing/client_captures.pyand register it in_FIXTURESinsrc/pyfsr/_testing/replay_http.py(extend_path_and_matchif the path has a volatile segment to collapse). Appliance-CLI examples usescripts/capture_appliance_fixtures.py+appliance_captures.pyinstead.Write the
{doctest}block withdemo_client()/demo_box().Run
cd docs && make doctest && make check-examples; both must be green.
Captures must use placeholder hosts/credentials only — never real lab IPs, host
names, passwords, or capture dates in tracked source. Say “captured from a live
8.0 appliance” generically; keep box details in gitignored locations. The
existing CAPTURE_HOST = "fortisoar.example.com" convention is the model.
Validation commands¶
make doctest— execute + output-match every{doctest}block.make check-examples— lint symbols/flags in plain blocks, run offline CLI commands, and enforce the doctest-count floor so a{doctest}cannot quietly be replaced by a plain{code-block}.make html -W -n— strict warnings-as-errors build (xrefs must resolve).pytest tests/unit/test_doc_examples.py— every>>>block in docstrings andindex.rstresolves to a real symbol.
Run python scripts/check_doc_examples.py --coverage for a per-file
{doctest} / python / shell block-count report — it shows where doctest
coverage exists and where plain-block gaps remain.