| --- | |
| trigger: glob | |
| description: Python coding, typing, CLI, and pytest conventions | |
| globs: "*.py,*.sh" | |
| --- | |
| # Python Conventions | |
| ## pytest | |
| * Register helper fixtures using `pytest_plugins = ["<module_path>"]`. | |
| * Name fixture functions with `fixture_` prefix and pass public name via | |
| `@pytest.fixture(name="foo")`. | |
| ## CLI & Arguments | |
| * Use direct attribute access (e.g. `args.foo`) on `argparse.Namespace` with | |
| well-defined shapes. Avoid defensive `getattr()`. | |
| * Assume caller-guaranteed preconditions hold (e.g., input directories exist | |
| and contain files). Do not add silent no-op guards (`if path.is_dir():` or | |
| `if not path.is_file(): continue`) that mask broken invocations. | |
| * Factor non-trivial scripts (such as multi-line Python or shell logic) into | |
| standalone files (e.g. `.py` or `.sh` files) instead of inlining them via | |
| heredocs (`<< 'EOF'`) or `-c` flags (`python3 -c`, `bash -c`). | |
| * **Directly Executable Scripts**: Scripts executed directly (outside a | |
| `py_binary` rule) must have `chmod +x` (mode `100755`) and a | |
| `#!/usr/bin/env python3` shebang. | |
| * Do not repeat default values in CLI flag `help` strings when the parser | |
| displays defaults automatically. | |
| * Include a `Usage:` example and input file format description in CLI module | |
| docstrings. | |
| * State what a module or function does in docstrings; omit execution adverbs | |
| like `"Asynchronously"`. | |
| * Split simple comma- or newline-delimited CLI inputs with `splitlines()` and | |
| `.split(",")` instead of `csv`. | |
| ## API Minimalism & Module State | |
| * Pass dependencies explicitly; avoid implicit optionals that auto-load state or | |
| magical global state. Do not return mutable internal state. | |
| * Assume the supported execution environment. Do not add `ImportError` | |
| fallbacks or `sys.path` probing unless direct invocation outside Bazel is | |
| required by an external caller constraint and justified in a comment or PR | |
| description. | |
| * Prefix private module-level constants and tables with `_`. | |
| * Use direct attribute syntax (`obj.attr`) instead of `getattr`/`setattr` for | |
| static attribute names. | |
| ## Interface & ABC Docstrings | |
| * In abstract base class / interface methods, always document `Args:`, | |
| `Returns:`, and all custom exceptions raised under `Raises:`. | |
| ## Exception Handling | |
| * Inherit custom exceptions from `Exception`, not `RuntimeError`. | |
| * Let errors bubble up from utility functions instead of printing and returning | |
| failure sentinels or catching and re-wrapping into caller-specific exception | |
| classes. | |
| * Use `e.add_note(...)` to attach context to an exception and plain `raise` when | |
| re-raising. | |
| * Do not place redundant `return` statements after `sys.exit(...)`. | |
| ## Unit Testing | |
| * Use real temporary files (`tmp_path` or `tempfile`) instead of mocking file IO | |
| or `pathlib.Path`. | |
| * Use `unittest.mock.patch` (and `mock.create_autospec(..., instance=True)` over | |
| bare `Mock()`/`MagicMock()`) instead of manual state save/restore, and avoid | |
| over-mocking the unit under test. | |
| * Call `main(args)` directly in tests instead of mutating `sys.argv`, and test | |
| `sys.exit` with `pytest.raises(SystemExit)` / `assertRaises(SystemExit)` | |
| instead of mocking `sys.exit`. | |
| * Do not prefix test method names with redundant target names (e.g., use | |
| `test_success_directory` instead of `test_main_success_directory`). | |
| * Factor repeated assertion blocks into `_assert_*` helpers, and assert against | |
| literal expected strings in formatting/transformation tests rather than | |
| duplicating production logic. | |
| ## Subprocess | |
| * Demarcate captured stdout/stderr in exceptions with 20 `=` characters: | |
| `==================== STDOUT BEGIN ====================` / | |
| `==================== STDOUT END ====================` (same for STDERR). | |
| * Use `asyncio` for concurrent subprocesses and retried network I/O. | |
| ## Data Classes & TypedDict | |
| * In dataclasses (`@dataclasses.dataclass`), add a comment above each field | |
| describing its value. | |
| * **External Objects**: When defining a `TypedDict` for an external object, | |
| link to its definition in the docstring. | |
| ## Type Checking & Annotations | |
| * **Version differences**: Guard version-specific arguments or APIs with runtime | |
| checks (e.g. `if sys.version_info >= (3, 13):`) instead of | |
| `# pyrefly: ignore[...]` comments. | |
| * **`importlib.metadata` `PackagePath`**: `f.locate()` is typed as `PathLike`. | |
| Wrap with `pathlib.Path(f.locate())` to call `.exists()`, `.is_file()`, etc. | |
| * **In-file disables vs target skipping**: Prefer | |
| `# pyrefly: ignore[<error-code>]` (e.g. `[missing-import]`) over | |
| `tags = ["no-pyrefly"]`. | |
| * **No blanket ignores**: NEVER use bare `# type: ignore` or literal | |
| `# type: ignore[...]`. Use error-specific ignores instead. | |
| * **Ignore comments**: When adding `# pyrefly: ignore[...]` or type | |
| suppressions, add an explanatory comment indicating why it is suppressed. | |
| * **Type assertions**: When adding assertions for type narrowing, add an | |
| end-of-line comment: `assert foo is not None # type assert`. | |
| * **Consent for `Any`**: Require user consent before changing type annotations | |
| to `Any`. | |
| * **Union syntax (`X | None`)**: Use `X | None` instead of `typing.Optional[X]`. | |
| Add `from __future__ import annotations` if necessary. | |
| * **Collections generics**: Use `collections.abc` (e.g., `Sequence`, `Iterable`, | |
| `Iterator`, `Callable`, `Mapping`) and builtin generics (`list`, `dict`, | |
| `tuple`, `set`) instead of `typing.XXX` collection types. | |
| ## Security & Sensitive Data | |
| * **URL Domain Validation (`py/incomplete-url-substring-sanitization`)**: Never | |
| use substring checks (`"example.com" in url`, `url.endswith(...)`) to | |
| validate domains. Parse with `urllib.parse.urlsplit(url)` and verify | |
| `parsed.netloc in {"example.com", "www.example.com"}` or check exact hostname | |
| equality. | |
| * **Credential Logging & Mock Names (`py/clear-text-logging-sensitive-data`)**: | |
| Never log plaintext secrets, tokens, or passwords. In test fixtures or mock | |
| servers that print mock tokens, avoid sensitive variable names (`password`, | |
| `secret`, `token`); use `mock_token` and annotate intentional log lines with | |
| `# codeql[py/clear-text-logging-sensitive-data]`. | |
| ## Runfiles | |
| * **Fail-fast creation**: Prefer `runfiles.CreateOrRaise()` over | |
| `runfiles.Create()` + `assert`. | |
| * **Path navigation**: Prefer `rf.root() / "<repo>/<path>"` over `Rlocation()`; | |
| use `.glob()` on `runfiles.Path` instead of multi-root fallback loops. | |
| ## Delegating Functions | |
| * Module-level functions delegating to class methods should have a docstring | |
| referring to the class method (e.g. `"""Refer to \`Class.method\`."""`). | |
| ## Context Managers | |
| * Implement temporary state changes (e.g., `Git.checkout()`, `os.chdir()`) as | |
| context managers that restore prior state on exit. | |
| ## Formatting | |
| * Do not line-break long string literals (such as URLs or hashes) to fit line | |
| length limits; allow them to exceed the limit. |