plesty.sdk.commands.check
=========================
.. py:module:: plesty.sdk.commands.check
.. autoapi-nested-parse::
Check a module for plesty standard.
Attributes
----------
.. autoapisummary::
plesty.sdk.commands.check._REQUIRED_PROJECT_FIELDS
plesty.sdk.commands.check._NAME_PATTERN
plesty.sdk.commands.check._VERSION_PATTERN
plesty.sdk.commands.check._PLESTY_LIB_PREFIXES
plesty.sdk.commands.check._REQUIRED_LICENSE_FILES
plesty.sdk.commands.check._COVERAGE_THRESHOLD
plesty.sdk.commands.check._ANALYZER_PIPELINE_GATES
plesty.sdk.commands.check._EXPERIMENT_PIPELINE_GATES
plesty.sdk.commands.check._gate_guidance_cache
plesty.sdk.commands.check._PROTECTED_SCHEMA_NAMES
plesty.sdk.commands.check._DEVICE_DOCS_PROBE
plesty.sdk.commands.check._WRITE_MODE_CHARS
plesty.sdk.commands.check._MANIFEST_FIELDS
plesty.sdk.commands.check._MANIFEST_TIMEOUT
plesty.sdk.commands.check._DEFAULT_SCHEMA_GROUP
Exceptions
----------
.. autoapisummary::
plesty.sdk.commands.check.GateNotApplicable
Functions
---------
.. autoapisummary::
plesty.sdk.commands.check._gate_guidance
plesty.sdk.commands.check._echo_pass
plesty.sdk.commands.check._run_gate
plesty.sdk.commands.check._standard_for_version
plesty.sdk.commands.check.derive_standard
plesty.sdk.commands.check.check_command
plesty.sdk.commands.check._gate_sequence
plesty.sdk.commands.check.check_metadata
plesty.sdk.commands.check.run_linter
plesty.sdk.commands.check.run_formatter_check
plesty.sdk.commands.check.run_type_checker
plesty.sdk.commands.check.check_api_interfaces
plesty.sdk.commands.check._collect_plesty_lib_imports
plesty.sdk.commands.check._resolve_base_name
plesty.sdk.commands.check.check_data_compliance
plesty.sdk.commands.check._missing_return_annotations
plesty.sdk.commands.check._shadowed_schema_classes
plesty.sdk.commands.check.check_documentation
plesty.sdk.commands.check._assert_changelog_updated
plesty.sdk.commands.check.check_dependency_coexistence
plesty.sdk.commands.check.check_test_coverage
plesty.sdk.commands.check._measured_coverage
plesty.sdk.commands.check.check_semantic_versioning
plesty.sdk.commands.check._check_tag_freshness
plesty.sdk.commands.check.check_licensing
plesty.sdk.commands.check._check_authorship_currency
plesty.sdk.commands.check.check_vulnerability_audit
plesty.sdk.commands.check.check_docs_build
plesty.sdk.commands.check._is_device_module
plesty.sdk.commands.check._run_mock_pipeline_drivers
plesty.sdk.commands.check.check_device_api_pipeline
plesty.sdk.commands.check.check_device_status_contract
plesty.sdk.commands.check._status_contract_violations
plesty.sdk.commands.check.check_device_docs
plesty.sdk.commands.check._is_analyzer_module
plesty.sdk.commands.check.check_analyzer_contract
plesty.sdk.commands.check._is_experiment_module
plesty.sdk.commands.check.check_experiment_contract
plesty.sdk.commands.check.check_experiment_persistence
plesty.sdk.commands.check.check_experiment_manifest
plesty.sdk.commands.check._read_manifest
plesty.sdk.commands.check._manifest_problems
plesty.sdk.commands.check._parameter_problems
plesty.sdk.commands.check._schema_drift
plesty.sdk.commands.check._config_keys
plesty.sdk.commands.check._declared_keys
plesty.sdk.commands.check.check_field_test_findings
plesty.sdk.commands.check._adhoc_persistence_calls
plesty.sdk.commands.check._describe_persistence_call
plesty.sdk.commands.check._has_write_mode
plesty.sdk.commands.check._module_name_from_toml
plesty.sdk.commands.check._import_path_from_toml
plesty.sdk.commands.check._tail_text
plesty.sdk.commands.check._run_subprocess
Module Contents
---------------
.. py:data:: _REQUIRED_PROJECT_FIELDS
:value: ('name', 'description', 'authors', 'requires-python', 'license')
.. py:data:: _NAME_PATTERN
.. py:data:: _VERSION_PATTERN
.. py:data:: _PLESTY_LIB_PREFIXES
:value: ('plesty.lib', 'plesty_lib', 'plestylib')
.. py:data:: _REQUIRED_LICENSE_FILES
:value: ('LICENSE',)
.. py:data:: _COVERAGE_THRESHOLD
:value: 80
.. py:data:: _ANALYZER_PIPELINE_GATES
:value: ('test_analyzer_subclass', 'test_schema_integrity', 'test_analyze_signature',...
.. py:data:: _EXPERIMENT_PIPELINE_GATES
:value: ('test_experiment_subclass', 'test_plan_deterministic', 'test_plan_ops_resolve',...
.. py:data:: _gate_guidance_cache
:type: dict[str, Any] | None
:value: None
.. py:function:: _gate_guidance() -> dict[str, Any]
Load the per-gate failure guidance from the packaged asset (cached).
.. py:function:: _echo_pass(message: str) -> None
Echo a gate progress line unless --quiet is active.
.. py:exception:: GateNotApplicable
Bases: :py:obj:`Exception`
The gate does not apply to this module, and says so as a skip.
A device gate on an analyzer has not passed; it has not run. Recording it
as a pass would let ``check.json`` claim a device contract the module was
never held to, and a page that labels only what passed reads as though the
rest passed too.
The message is the full CLI line, so the terminal output is unchanged from
when each gate echoed it itself.
Initialize self. See help(type(self)) for accurate signature.
.. py:function:: _run_gate(key: str, gate: collections.abc.Callable[[], None], report: plesty.sdk.report.CheckReport | None = None) -> None
Run one gate, record its outcome, and explain a failure.
:param key: Gate key into the packaged guidance asset.
:param gate: Zero-argument callable executing the gate.
:param report: Collector for ``reports/check.json``. Outcomes are recorded
whether the gate passes, skips or fails — a run that stopped early
must not read as a clean sweep of a shorter sequence.
.. py:function:: _standard_for_version(version: str) -> str | None
Map a version string to its compliance standard, or None when unparseable.
:param version: Version string such as ``0.2.1`` or ``v1.0.0``.
.. py:function:: derive_standard(project_dir: pathlib.Path, pyproject_toml: dict[str, Any]) -> tuple[str, str]
Resolve the compliance standard for a project.
An explicit ``[tool.plesty] standard`` always wins. Without it, the
standard is derived from the declared release version so that shared
prototypes ramp up automatically (plesty-sdk#19): ``< 0.1.0`` → pixel,
``< 1.0.0`` → nebula, ``>= 1.0.0`` → quantum. For projects with a
dynamic version (versioningit), the latest git tag is used; a project
without any release tag is a fresh prototype and maps to pixel. A
project that declares neither a static nor a dynamic version falls back
to quantum, the conservative default.
:param project_dir: Root directory of the target project.
:param pyproject_toml: Parsed pyproject.toml content.
:returns: A ``(standard, reason)`` tuple; the reason is human-readable and
echoed by ``plesty check``.
.. py:function:: check_command(context: plesty.sdk.context.ApplicationContext, standard: Literal['pixel', 'nebula', 'quantum'] | None, no_report: bool) -> None
Check compliance of a HUB module with the Plesty standard.
Enforcement tiers: ``pixel`` runs Metadata & Code Hygiene only (fast,
suitable for pre-commit); ``nebula`` runs gates 1–6 without network access
and is the standard enforced by the pre-push git hook installed by
``plesty init``; ``quantum`` runs all gates locally, including the
[SDK+CI] tier, for full verification.
Unless ``--no-report`` is given, the run writes ``reports/check.json`` —
the module's standard-gate results in the same envelope the field test
publishes, so a quality page renders both as one story.
.. py:function:: _gate_sequence(standard: str) -> tuple[str, Ellipsis]
Return every gate key this standard runs, in order.
Used to record the gates a failed run never reached: a partial report
listing only the gates that happened to run before the failure would read
as a clean sweep of a shorter sequence.
:param standard: The resolved compliance standard.
.. py:function:: check_metadata(project_dir: pathlib.Path, pyproject_toml: dict[str, Any]) -> None
Validate required pyproject.toml fields and plesty namespace conventions.
:param project_dir: Root directory of the target project.
:param pyproject_toml: Parsed pyproject.toml content.
.. py:function:: run_linter(project_directory: pathlib.Path) -> None
Run ruff linter (E, F, W, D rules) on the project directory.
:param project_directory: The target project directory.
.. py:function:: run_formatter_check(project_directory: pathlib.Path) -> None
Check ruff formatting without modifying files.
:param project_directory: The target project directory.
.. py:function:: run_type_checker(project_directory: pathlib.Path) -> None
Run mypy type checker over the plesty namespace module source.
:param project_directory: The target project directory.
.. py:function:: check_api_interfaces(project_dir: pathlib.Path, pyproject_toml: dict[str, Any]) -> None
Verify public classes inherit from the appropriate plesty-lib base class.
Classes that import from plesty.lib.* must inherit from one of the
imported base classes. The specific base class (e.g. BaseDeviceSyncModel)
is enforced implicitly: if it is imported and not used as a base, the
check fails.
:param project_dir: Root directory of the target project.
:param pyproject_toml: Parsed pyproject.toml content.
.. py:function:: _collect_plesty_lib_imports(tree: ast.AST) -> set[str]
Return names imported from plesty.lib.* in the given AST.
:param tree: Parsed AST of a Python source file.
.. py:function:: _resolve_base_name(node: ast.expr) -> str
Extract the simple name from a base-class AST node.
:param node: An AST expression representing a base class.
.. py:function:: check_data_compliance(project_dir: pathlib.Path, pyproject_toml: dict[str, Any]) -> None
Verify the module respects the unified plesty-lib data-layer schemas.
Two static rules apply to every module (plesty-sdk#17, follow-up to
plesty-lib#4):
1. **Return annotations**: every public function or method declares an
explicit return type annotation.
2. **No schema shadowing** (except plesty-lib itself): the module must
not define its own classes named after the plesty-lib schema models
(:data:`_PROTECTED_SCHEMA_NAMES`) — that silently forks the data
contract.
Module-type-specific data rules live in their own gates: d2 (device
status contract) and e2 (experiment persistence).
:param project_dir: Root directory of the target project.
:param pyproject_toml: Parsed pyproject.toml content.
.. py:data:: _PROTECTED_SCHEMA_NAMES
:value: ('ConfigParameter', 'ConfigGroup', 'Command', 'ConfigSystem', 'FuncMeta', 'FuncParam',...
.. py:function:: _missing_return_annotations(tree: ast.AST, rel_path: pathlib.Path) -> list[str]
Rule 1: report public functions without an explicit return annotation.
:param tree: Parsed AST of a module source file.
:param rel_path: Project-relative path used in violation messages.
.. py:function:: _shadowed_schema_classes(tree: ast.AST, rel_path: pathlib.Path) -> list[str]
Rule 2: report class definitions that shadow a plesty-lib schema model.
:param tree: Parsed AST of a module source file.
:param rel_path: Project-relative path used in violation messages.
.. py:function:: check_documentation(project_dir: pathlib.Path) -> None
Check docs directory structure and that CHANGELOG.md is current.
:param project_dir: Root directory of the target project.
.. py:function:: _assert_changelog_updated(project_dir: pathlib.Path) -> None
Raise if Python source files changed since the last git tag without a CHANGELOG update.
:param project_dir: Root directory of the target project.
.. py:function:: check_dependency_coexistence(pyproject_toml: dict[str, Any]) -> None
Reject exact version pins (==) in project dependencies.
:param pyproject_toml: Parsed pyproject.toml content.
.. py:function:: check_test_coverage(project_dir: pathlib.Path, report: plesty.sdk.report.CheckReport | None = None) -> None
Run pytest with coverage and enforce the 80% threshold.
:param project_dir: Root directory of the target project.
:param report: When given, the measured percentage is recorded on it. The
number is worth publishing: a threshold tells a reader the module
cleared a bar, the percentage tells them by how much.
.. py:function:: _measured_coverage(output: str) -> float | None
Extract the total coverage percentage from pytest-cov's terminal output.
:param output: Captured stdout of the coverage run.
:returns: The percentage, or ``None`` when the output carries no total — a run
that failed before collecting has no coverage to report, and zero
would be a measurement rather than the absence of one.
.. py:function:: check_semantic_versioning(project_dir: pathlib.Path, pyproject_toml: dict[str, Any] | None = None) -> None
Diff the public API against the latest release published on PyPI.
Breaking API changes require a version bump beyond the published release
(a minor bump before 1.0.0, a major bump afterwards), witnessed by a git
tag. The gate is advisory in local runs and authoritative in CI (``CI``
env set). When the package is unpublished or PyPI is unreachable, it
falls back to the tag-freshness reminder.
:param project_dir: Root directory of the target project.
:param pyproject_toml: Parsed pyproject.toml content; when omitted, only the
tag-freshness reminder runs.
.. py:function:: _check_tag_freshness(project_dir: pathlib.Path) -> None
Remind the developer to tag a release if commits exist since the last tag.
Fallback for unpublished packages and offline runs, where the API diff
against PyPI is not possible.
:param project_dir: Root directory of the target project.
.. py:function:: check_licensing(project_dir: pathlib.Path, pyproject_toml: dict[str, Any]) -> None
Verify licensing declarations and REUSE compliance.
Checks that pyproject.toml declares a license, that a root ``LICENSE`` file
exists (so forges such as GitLab detect the license), and that the project
is compliant with the REUSE specification via ``reuse lint``. The deep
dependency tree scan (CI part of this gate) runs in plesty-ci.
:param project_dir: Root directory of the target project.
:param pyproject_toml: Parsed pyproject.toml content.
.. py:function:: _check_authorship_currency(project_dir: pathlib.Path) -> None
Ensure unpushed commit authors are credited on the files they changed.
Part of the Licensing gate. Runs locally only (a no-op in CI, and when there
is no base branch to compare against). When a commit author is not yet listed
as a contributor or copyright holder on a Python file they changed, the push
is blocked until `plesty license author` records them.
:param project_dir: Root directory of the target project.
.. py:function:: check_vulnerability_audit(project_dir: pathlib.Path) -> None
Audit declared dependencies for known CVEs using pip-audit.
Exports a flat requirements.txt from uv.lock and runs pip-audit against it.
Requires network access to query the OSV vulnerability database.
:param project_dir: Root directory of the target project.
.. py:function:: check_docs_build(context: plesty.sdk.context.ApplicationContext) -> None
Build the Sphinx documentation and fail on any errors or warnings.
Runs locally and in merge-request pipelines, so docs-breaking changes
fail before they merge. Skipped in CI branch pipelines (``CI=true``
without ``CI_PIPELINE_SOURCE=merge_request_event``) because
``hub-deploy-docs`` builds and deploys the docs there as a dedicated
pipeline job.
:param context: The application context for the target project.
.. py:function:: _is_device_module(pyproject_toml: dict[str, Any]) -> bool
Return True when ``[tool.plesty] module_type = "device"`` is declared.
:param pyproject_toml: Parsed pyproject.toml content.
.. py:function:: _run_mock_pipeline_drivers(project_dir: pathlib.Path) -> list[str]
Return the names of test functions that drive ``run_mock_pipeline()``.
Read with :mod:`ast` rather than by grepping: a name in a docstring or a
comment is not a test that runs, and accepting one would let a module
claim the contract without exercising it.
:param project_dir: Root directory of the target project.
:returns: Test function names containing a ``run_mock_pipeline`` call, in the
order found.
.. py:function:: check_device_api_pipeline(project_dir: pathlib.Path, pyproject_toml: dict[str, Any]) -> None
Gate d1: Verify device modules run the standard mock pipeline.
Modules that do not declare ``module_type = "device"`` in ``[tool.plesty]``
are silently skipped. For device modules, ``tests/`` must contain a test
calling ``run_mock_pipeline()``, and it must pass without hardware.
One call runs all eight gates and reports every failure it found, so the
eight forwarding functions this gate used to require were boilerplate that
said nothing a single call does not. ``plesty init mock-test`` generates
the test from the module's own declarations.
:param project_dir: Root directory of the target project.
:param pyproject_toml: Parsed pyproject.toml content.
.. py:function:: check_device_status_contract(project_dir: pathlib.Path, pyproject_toml: dict[str, Any]) -> None
Gate d2: Verify device telemetry overrides keep their schema annotations.
Modules that do not declare ``module_type = "device"`` in ``[tool.plesty]``
are silently skipped. For device modules, overrides of ``status()`` must be
annotated to return ``DeviceStatus`` and overrides of ``device_state()`` to
return ``str``, so consumers can rely on the plesty-lib telemetry schema
(plesty-sdk#17, follow-up to plesty-lib#4).
:param project_dir: Root directory of the target project.
:param pyproject_toml: Parsed pyproject.toml content.
.. py:function:: _status_contract_violations(tree: ast.AST, rel_path: pathlib.Path) -> list[str]
Report device-class telemetry overrides with wrong return annotations.
:param tree: Parsed AST of a module source file.
:param rel_path: Project-relative path used in violation messages.
.. py:data:: _DEVICE_DOCS_PROBE
:value: Multiline-String
.. raw:: html
Show Value
.. code-block:: python
"""
import tomllib
from plesty.sdk.commands.docs.generate import load_doc_device, render_device_pages
with open("pyproject.toml", "rb") as fh:
pyproject = tomllib.load(fh)
device = load_doc_device(pyproject)
model = device.doc_model()
if not model.standard_methods:
raise SystemExit("doc_model().standard_methods is empty")
if not model.parameters and not model.functions:
raise SystemExit("device documents neither parameters nor functions")
render_device_pages(model) # must render without error
print("D3_OK")
"""
.. raw:: html
.. py:function:: check_device_docs(project_dir: pathlib.Path, pyproject_toml: dict[str, Any]) -> None
Gate d3: Verify device modules are documentable with zero effort.
Modules that do not declare ``module_type = "device"`` in ``[tool.plesty]``
are silently skipped. For device modules, the package must import, expose a
hardware-free ``DocDevice``/``Device`` factory, and produce a populated
``doc_model()`` that renders into the standard reference pages — so
``plesty docs`` can generate Parameters / Functions / Standard Methods pages
without any hand-written content (plesty-sdk#18).
:param project_dir: Root directory of the target project.
:param pyproject_toml: Parsed pyproject.toml content.
.. py:function:: _is_analyzer_module(pyproject_toml: dict[str, Any]) -> bool
Return True when ``[tool.plesty] module_type = "analyzer"`` is declared.
:param pyproject_toml: Parsed pyproject.toml content.
.. py:function:: check_analyzer_contract(project_dir: pathlib.Path, pyproject_toml: dict[str, Any]) -> None
Gate a1: Verify analyzer modules implement the AnalyzerPipeline test suite.
Modules that do not declare ``module_type = "analyzer"`` in
``[tool.plesty]`` are silently skipped. For analyzer modules, all five
contract gate test functions must be present in ``tests/`` and pass on
schema-generated synthetic inputs — they validate the package against the
``plesty.lib.analyzer.Analyzer`` contract (public subclass, schema
integrity, analyze-signature match, mock round-trip, provenance stamp).
:param project_dir: Root directory of the target project.
:param pyproject_toml: Parsed pyproject.toml content.
.. py:function:: _is_experiment_module(pyproject_toml: dict[str, Any]) -> bool
Return True when ``[tool.plesty] module_type = "experiment"`` is declared.
:param pyproject_toml: Parsed pyproject.toml content.
.. py:function:: check_experiment_contract(project_dir: pathlib.Path, pyproject_toml: dict[str, Any]) -> None
Gate e1: Verify experiment modules implement the ExperimentPipeline test suite.
Modules that do not declare ``module_type = "experiment"`` in
``[tool.plesty]`` are silently skipped. For experiment modules, all five
contract gate test functions must be present in ``tests/`` and pass
without hardware — they validate the package against the
``plesty.lib.experiment.Experiment`` contract (single public subclass,
deterministic ``build_plan()``, resolvable step operations,
JSON-serializable plans, async lifecycle hooks).
:param project_dir: Root directory of the target project.
:param pyproject_toml: Parsed pyproject.toml content.
.. py:function:: check_experiment_persistence(project_dir: pathlib.Path, pyproject_toml: dict[str, Any]) -> None
Gate e2: Verify experiment modules persist results through the data layer.
Modules that do not declare ``module_type = "experiment"`` in
``[tool.plesty]`` are silently skipped. For experiment modules, direct
persistence calls — ``np.save``/``np.savez``/``np.savez_compressed``,
``pickle.dump``, ``h5py.File`` opened with a write mode, and builtin
``open`` with a write mode — are rejected in the module package: results
must be persisted via ``plesty.lib.data.save_result``/``ResultDocument``
(plesty-sdk#17, follow-up to plesty-lib#4). Calls whose mode cannot be
resolved statically are left alone.
:param project_dir: Root directory of the target project.
:param pyproject_toml: Parsed pyproject.toml content.
.. py:data:: _WRITE_MODE_CHARS
.. py:data:: _MANIFEST_FIELDS
:value: ('module', 'title', 'config', 'run_root', 'schema')
.. py:data:: _MANIFEST_TIMEOUT
:value: 60
.. py:function:: check_experiment_manifest(project_dir: pathlib.Path, pyproject_toml: dict[str, Any]) -> None
Gate e3: Verify an experiment module declares how it is run.
Modules that do not declare ``module_type = "experiment"`` in
``[tool.plesty]`` are silently skipped. For experiment modules,
``python -m --manifest`` must print the document described in
:class:`plesty.lib.experiment.Manifest` — the run command, the run root,
the environment the run reads and the configuration schema — so a console
can offer the experiment's settings without being taught them one module
at a time.
Two things are checked beyond the document's shape, and they are the
reason the gate exists:
* **Every parameter says what it is.** A control rendered without a
``dtype`` and a ``description`` is a box with a name on it, and a bounded
one without a ``unit`` shows bare numbers. ``unit=""`` is an answer —
a count has no unit — but leaving it unset is not.
* **The schema and the shipped configuration agree.** A key the
configuration sets and the schema omits cannot be edited; a key the
schema declares and no configuration sets renders as a setting that does
nothing. A schema nobody keeps current is worse than none, because a
console renders it as fact.
:param project_dir: Root directory of the target project.
:param pyproject_toml: Parsed pyproject.toml content.
:raises GateNotApplicable: If the module is not an experiment module.
:raises click.ClickException: If the manifest cannot be produced, is malformed,
or disagrees with the module's own configuration files.
.. py:function:: _read_manifest(project_dir: pathlib.Path, module: str) -> dict[str, Any]
Run ``python -m --manifest`` and return the document it printed.
:param project_dir: Root directory of the target project.
:param module: The module's import path.
:returns: The parsed document.
:raises click.ClickException: If the command fails, times out, or prints
something that is not a JSON object.
.. py:function:: _manifest_problems(document: dict[str, Any], module: str) -> list[str]
Return what the manifest document itself gets wrong.
:param document: The parsed manifest.
:param module: The module's import path, which the manifest must name.
:returns: One line per problem; empty when the document is well formed.
.. py:function:: _parameter_problems(group: str, entry: Any) -> list[str]
Return what one declared configuration parameter is missing.
:param group: The group it is declared under.
:param entry: The parameter mapping.
:returns: One line per problem.
.. py:function:: _schema_drift(project_dir: pathlib.Path, document: dict[str, Any]) -> list[str]
Return where the schema and the module's own configuration disagree.
:param project_dir: Root directory of the target project.
:param document: The parsed manifest.
:returns: One line per key that only one of the two knows about.
.. py:function:: _config_keys(config: dict[str, Any], groups: set[str]) -> set[str]
Return the keys a configuration document sets, as the schema addresses them.
Only the two levels a schema describes are walked: a top-level value, and
the members of a declared group. What lies inside an open-ended document is
the experiment's own business.
:param config: The loaded configuration.
:param groups: The non-default group names the schema declares.
:returns: The keys, ``group.parameter`` for grouped ones.
.. py:function:: _declared_keys(schema: dict[str, Any]) -> set[str]
Return every key the schema declares, addressed as a renderer writes it.
:param schema: The manifest's schema block.
:returns: The declared keys.
.. py:data:: _DEFAULT_SCHEMA_GROUP
:value: 'default'
.. py:function:: check_field_test_findings(project_dir: pathlib.Path) -> None
Gate f1: no published field test report holds an ``error`` finding.
A field test writes ``reports//field-test.json`` after running
against real hardware. An ``error`` finding in one means the module
declares an API the instrument does not have — a parameter the hardware
refuses, or one it will not answer. The module is telling every caller
that something exists which does not, so it must not be published in that
state.
A module that has never been field-tested publishes no report and skips
this gate. Absence is the honest rendering: "never run" and "ran and found
nothing" are different facts, and treating them alike is how an untested
module reads as a verified one.
:param project_dir: Root directory of the target project.
:raises GateNotApplicable: If the module publishes no field test report.
:raises click.ClickException: If any report holds an ``error`` finding, or a
report cannot be read.
.. py:function:: _adhoc_persistence_calls(tree: ast.AST, rel_path: pathlib.Path) -> list[str]
Report direct persistence calls that bypass ``save_result``.
:param tree: Parsed AST of a module source file.
:param rel_path: Project-relative path used in violation messages.
.. py:function:: _describe_persistence_call(node: ast.Call) -> str
Return a description of *node* when it is an ad-hoc persistence call.
:param node: A call expression to classify.
:returns: A short human-readable description, or ``""`` when the call is fine.
.. py:function:: _has_write_mode(node: ast.Call, default_write: bool = False) -> bool
Return True when a call's mode argument statically requests writing.
The mode is taken from the second positional argument or the ``mode``
keyword. A missing mode means read-only; a mode that is not a string
constant cannot be judged statically and is treated as read-only.
:param node: The ``open``/``h5py.File`` call expression.
:param default_write: Result to return when no mode argument is present.
.. py:function:: _module_name_from_toml(pyproject_toml: dict[str, Any]) -> str
Derive the Python module name from the pyproject.toml package name.
:param pyproject_toml: Parsed pyproject.toml content.
.. py:function:: _import_path_from_toml(pyproject_toml: dict[str, Any]) -> str
Return the import path the package is served with by ``python -m``.
``[tool.plesty] module`` wins where a module declares one; otherwise it is
the package name under the ``plesty`` namespace, the convention every
module follows (``plesty-pol-pl`` → ``plesty.pol_pl``).
:param pyproject_toml: Parsed pyproject.toml content.
.. py:function:: _tail_text(text: str, max_lines: int = 20) -> str
Return a compact tail of non-empty build output.
.. py:function:: _run_subprocess(cmd: list[str], gate: str, cwd: pathlib.Path | None = None) -> None
Run a subprocess and exit with its return code on failure.
:param cmd: Command and arguments to run.
:param gate: Gate name used in error messages.
:param cwd: Working directory for the subprocess.