Skip to content

Commit 5cc8cd6

Browse files
authored
Merge pull request #3907 from PolicyEngine/ruff-docstrings
Enable ruff docstring rules (D)
2 parents 71dba65 + 29cbf71 commit 5cc8cd6

298 files changed

Lines changed: 3406 additions & 673 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/bump_version.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77

88
def get_current_version(pyproject_path: Path) -> str:
9+
"""Return the version in pyproject.toml, exiting with status 1 if it is absent."""
910
text = pyproject_path.read_text()
1011
match = re.search(r'^version\s*=\s*"(\d+\.\d+\.\d+)"', text, re.MULTILINE)
1112
if not match:
@@ -18,6 +19,11 @@ def get_current_version(pyproject_path: Path) -> str:
1819

1920

2021
def infer_bump(changelog_dir: Path) -> str:
22+
"""Return "major", "minor" or "patch" from the pending changelog fragment types.
23+
24+
Any breaking fragment makes a major bump and any added or removed fragment a
25+
minor one; exit with status 1 when there are no fragments.
26+
"""
2127
fragments = [
2228
f for f in changelog_dir.iterdir() if f.is_file() and f.name != ".gitkeep"
2329
]
@@ -39,6 +45,7 @@ def infer_bump(changelog_dir: Path) -> str:
3945

4046

4147
def bump_version(version: str, bump: str) -> str:
48+
"""Return ``version`` raised by one ``bump`` level, resetting the lower parts."""
4249
major, minor, patch = (int(x) for x in version.split("."))
4350
if bump == "major":
4451
return f"{major + 1}.0.0"
@@ -48,6 +55,7 @@ def bump_version(version: str, bump: str) -> str:
4855

4956

5057
def update_file(path: Path, old_version: str, new_version: str) -> None:
58+
"""Rewrite the ``version = "..."`` line in ``path``; leave other files untouched."""
5159
text = path.read_text()
5260
updated = text.replace(
5361
f'version = "{old_version}"',
@@ -59,6 +67,7 @@ def update_file(path: Path, old_version: str, new_version: str) -> None:
5967

6068

6169
def main() -> None:
70+
"""Bump the version in pyproject.toml according to the changelog.d fragments."""
6271
root = Path(__file__).resolve().parent.parent
6372
pyproject = root / "pyproject.toml"
6473
changelog_dir = root / "changelog.d"

‎.github/find-api-model-versions.py‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
"""CI helper that reads the versions pinned by the installed PolicyEngine bundle.
2+
3+
It checks the package and data versions against the API's version constants,
4+
then writes them to GITHUB_ENV or, with --shell, prints shell assignments.
5+
"""
6+
17
import argparse
28
import os
39
import shlex
@@ -48,9 +54,7 @@ def _data_release_version(bundle: dict, country_id: str) -> str:
4854

4955

5056
def find_api_model_versions() -> dict[str, str]:
51-
"""
52-
Find and validate the package and data versions in the installed bundle.
53-
"""
57+
"""Find and validate the package and data versions in the installed bundle."""
5458
bundle = get_current_bundle()
5559
if not isinstance(bundle, dict):
5660
raise RuntimeError("Installed PolicyEngine bundle manifest is not an object.")
@@ -89,9 +93,7 @@ def find_api_model_versions() -> dict[str, str]:
8993

9094

9195
def find_api_model_versions_and_output_to_github() -> None:
92-
"""
93-
Find the API model versions and output them to a file for GitHub.
94-
"""
96+
"""Find the API model versions and output them to a file for GitHub."""
9597
versions = find_api_model_versions()
9698
with open(os.environ["GITHUB_ENV"], "a") as f:
9799
for key, value in versions.items():

‎.github/scripts/check_updates.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#!/usr/bin/env python3
2-
"""
3-
Fetch and format changelog entries between two versions of a package.
2+
"""Fetch and format changelog entries between two versions of a package.
43
54
Fetches CHANGELOG.md from the package's GitHub repo and extracts
65
entries between the old and new version, formatted as markdown.
@@ -25,6 +24,8 @@
2524

2625

2726
class ChangelogEntry(TypedDict):
27+
"""One released version's changelog items, grouped by lowercase category."""
28+
2829
version: str
2930
changes: dict[str, list[str]]
3031

@@ -119,6 +120,10 @@ def format_changes(entries: list[ChangelogEntry]) -> str:
119120

120121

121122
def main() -> int:
123+
"""Print the markdown changes between two versions of a package.
124+
125+
Always return 0, so an unreachable changelog never fails the update job.
126+
"""
122127
parser = argparse.ArgumentParser(
123128
description="Fetch changelog entries between two package versions."
124129
)

‎.github/scripts/verify_observability_collector.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@
6464

6565

6666
class VerificationProbe(NamedTuple):
67+
"""Identifiers of one verification export, for finding its stored telemetry."""
68+
6769
verification_id: str
6870
trace_id: str
6971
started_at: float
@@ -315,7 +317,6 @@ def verify(
315317
delivery_timeout_seconds: float = 600.0,
316318
) -> VerificationProbe:
317319
"""Export real telemetry, verify storage, and require log rejection."""
318-
319320
started_at = time.time()
320321
verification_id = f"collector-verify-{uuid.uuid4().hex}"
321322
trace_request, metric_request, log_request, trace_id = _probe_requests(
@@ -370,6 +371,11 @@ def verify(
370371

371372

372373
def main() -> None:
374+
"""Verify the collector, reading both tokens from the environment.
375+
376+
COLLECTOR_ID_TOKEN authenticates to the collector and GOOGLE_OAUTH_ACCESS_TOKEN
377+
reads Cloud Trace and Cloud Monitoring.
378+
"""
373379
parser = argparse.ArgumentParser()
374380
parser.add_argument("--endpoint", required=True)
375381
parser.add_argument("--project-id", required=True)

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ To add or change a rule, read `docs/engineering/skills/rules-registry.md`.
123123
- `api.quality.environment-variables-load`: Required environment variables and credentials load in a deployed-like environment. (blocking: CI job `test_env_vars`)
124124
- `api.quality.mypy-v2`: Shared query models, FastAPI dependencies, v2 routes and services, and the user sign-in and account-deletion libraries pass mypy with untyped definitions disallowed. (blocking: mypy)
125125
- `api.quality.ruff-format`: All Python files are formatted with ruff format. (blocking: ruff format)
126-
- `api.quality.ruff-lint`: All Python code passes ruff check with every rule family selected in pyproject.toml, with no ratchet; families are added one themed group at a time until the selection is ALL. (blocking: ruff check (ANN, E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, TC, UP, W))
126+
- `api.quality.ruff-lint`: All Python code passes ruff check with every rule family selected in pyproject.toml, with no ratchet; families are added one themed group at a time until the selection is ALL. (blocking: ruff check (ANN, D, E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, TC, UP, W))
127127
- `api.quality.runtime-evaluated-models-resolve`: Every package model is one ruff treats as runtime-evaluated (a base resolves, through the import in its own module, to a name in runtime-evaluated-base-classes, or is a same-module class that does), and every Pydantic model and SQLAlchemy mapper resolves its annotations once the package is imported. (blocking: pytest `tests/unit/test_runtime_evaluated_annotations.py`)
128128
- `api.quality.tests-pass`: The unit, contract and to-refactor test suites pass. (blocking: CI job `test`)
129129
- `api.quality.tests-ruff-exemptions`: Test files may use pytest's bare assert and need no docstrings, annotations or named constants for magic numbers (S101, D1, ANN, PLR2004); every other ruff rule applies to tests. (blocking: ruff per-file ignore of ANN, D1, PLR2004, S101 for `**/tests/**`)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Enabled ruff's docstring rules (D) and documented every module, class and function outside tests; published models and routes now carry these descriptions in their JSON schema and OpenAPI, with no structural schema change.

‎dashboard/experiments/gpt4_api_user.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
"""Streamlit experiment that answers a UK policy question with GPT-4 and the API.
2+
3+
It finds related parameters by embedding similarity, asks GPT-4 to write the
4+
reform as JSON, runs it on the public PolicyEngine API, and asks GPT-4 to answer
5+
from the resulting economic impact.
6+
"""
7+
18
import json
29
import os
310
import time
@@ -14,6 +21,7 @@
1421

1522
@st.cache_data
1623
def get_embeddings_df() -> pd.DataFrame:
24+
"""Load parameter_embeddings.csv.gz, evaluating each embedding string to a list."""
1725
embeddings_df = pd.read_csv("parameter_embeddings.csv.gz", compression="gzip")
1826
embeddings_df.parameter_embedding = embeddings_df.parameter_embedding.apply(
1927
lambda x: eval(x)
@@ -23,6 +31,7 @@ def get_embeddings_df() -> pd.DataFrame:
2331

2432
@st.cache_data
2533
def embed(prompt: str, engine: str = "text-embedding-ada-002") -> list[float]:
34+
"""Return the OpenAI embedding of ``prompt``, cached across Streamlit reruns."""
2635
return get_embedding(prompt, engine=engine)
2736

2837

@@ -59,6 +68,7 @@ def embed(prompt: str, engine: str = "text-embedding-ada-002") -> list[float]:
5968

6069

6170
def write_prompt(question: str, relevant_parameters: object) -> str:
71+
"""Build the GPT-4 prompt that rewrites ``question`` as a reform JSON object."""
6272
return f"""Question: {question}
6373
Here's some metadata about the parameters that are relevant to your question:
6474
Relevant parameters: {relevant_parameters}
@@ -79,6 +89,7 @@ def write_prompt(question: str, relevant_parameters: object) -> str:
7989

8090
@st.cache_data
8191
def get_gpt4_response(prompt: str) -> str:
92+
"""Return GPT-4's reply to ``prompt`` as one user message, cached across reruns."""
8293
return openai.ChatCompletion.create(
8394
model="gpt-4",
8495
messages=[{"role": "user", "content": prompt}],
@@ -94,6 +105,10 @@ def get_gpt4_response(prompt: str) -> str:
94105

95106
@st.cache_data
96107
def get_policyengine_impact(reform: object) -> object:
108+
"""Create ``reform`` on the public UK API and return its 2023 UK economic impact.
109+
110+
Poll every five seconds, with no time limit, while the impact is computing.
111+
"""
97112
payload = {"data": reform}
98113
url = "https://api.policyengine.org/uk/policy"
99114

‎docs/generated/rules.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ flowchart LR
272272
| `api.quality.environment-variables-load` | Required environment variables and credentials load in a deployed-like environment. | mechanical | enforced | yes | CI job `test_env_vars` | `pr.yml:test_env_vars` | — |
273273
| `api.quality.mypy-v2` | Shared query models, FastAPI dependencies, v2 routes and services, and the user sign-in and account-deletion libraries pass mypy with untyped definitions disallowed. | mechanical | enforced | yes | mypy | `pr.yml:typecheck-v2` | — |
274274
| `api.quality.ruff-format` | All Python files are formatted with ruff format. | mechanical | enforced | yes | ruff format | `pr.yml:lint` | — |
275-
| `api.quality.ruff-lint` | All Python code passes ruff check with every rule family selected in pyproject.toml, with no ratchet; families are added one themed group at a time until the selection is ALL. | mechanical | enforced | yes | ruff check (ANN, E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, TC, UP, W) | `pr.yml:lint` | — |
275+
| `api.quality.ruff-lint` | All Python code passes ruff check with every rule family selected in pyproject.toml, with no ratchet; families are added one themed group at a time until the selection is ALL. | mechanical | enforced | yes | ruff check (ANN, D, E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, TC, UP, W) | `pr.yml:lint` | — |
276276
| `api.quality.runtime-evaluated-models-resolve` | Every package model is one ruff treats as runtime-evaluated (a base resolves, through the import in its own module, to a name in runtime-evaluated-base-classes, or is a same-module class that does), and every Pydantic model and SQLAlchemy mapper resolves its annotations once the package is imported. | mechanical | enforced | yes | pytest `tests/unit/test_runtime_evaluated_annotations.py` | `pr.yml:test` | **unproven** |
277277
| `api.quality.tests-pass` | The unit, contract and to-refactor test suites pass. | mechanical | enforced | yes | CI job `test` | `pr.yml:test` | — |
278278
| `api.quality.tests-ruff-exemptions` | Test files may use pytest's bare assert and need no docstrings, annotations or named constants for magic numbers (S101, D1, ANN, PLR2004); every other ruff rule applies to tests. | mechanical | enforced | yes | ruff per-file ignore of ANN, D1, PLR2004, S101 for `**/tests/**` | `pr.yml:lint` | — |
@@ -292,7 +292,7 @@ flowchart LR
292292
e_ruff_format["ruff format"]
293293
j_CI_pr_yml_lint["CI pr.yml:lint"]
294294
r_api_quality_ruff_lint("api.quality.ruff-lint (enforced)")
295-
e_ruff_check__ANN__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__TC__UP__W_["ruff check (ANN, E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, TC, UP, W)"]
295+
e_ruff_check__ANN__D__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__TC__UP__W_["ruff check (ANN, D, E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, TC, UP, W)"]
296296
r_api_quality_runtime_evaluated_models_resolve("api.quality.runtime-evaluated-models-resolve (enforced)")
297297
e_pytest_tests_unit_test_runtime_evaluated_annotations_py["pytest tests/unit/test_runtime_evaluated_annotations.py"]
298298
j_CI_pr_yml_test["CI pr.yml:test"]
@@ -308,8 +308,8 @@ flowchart LR
308308
e_mypy --> j_CI_pr_yml_typecheck_v2
309309
r_api_quality_ruff_format --> e_ruff_format
310310
e_ruff_format --> j_CI_pr_yml_lint
311-
r_api_quality_ruff_lint --> e_ruff_check__ANN__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__TC__UP__W_
312-
e_ruff_check__ANN__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__TC__UP__W_ --> j_CI_pr_yml_lint
311+
r_api_quality_ruff_lint --> e_ruff_check__ANN__D__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__TC__UP__W_
312+
e_ruff_check__ANN__D__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__TC__UP__W_ --> j_CI_pr_yml_lint
313313
r_api_quality_runtime_evaluated_models_resolve --> e_pytest_tests_unit_test_runtime_evaluated_annotations_py
314314
e_pytest_tests_unit_test_runtime_evaluated_annotations_py --> j_CI_pr_yml_test
315315
r_api_quality_tests_pass --> e_CI_job_test

‎migrations/v1/env.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040

4141

4242
def run_migrations_offline() -> None:
43+
"""Emit the migration SQL for the configured MySQL URL without connecting."""
4344
context.configure(
4445
url=config.get_main_option("sqlalchemy.url"),
4546
target_metadata=target_metadata,
@@ -52,6 +53,7 @@ def run_migrations_offline() -> None:
5253

5354

5455
def run_migrations_online() -> None:
56+
"""Migrate the connection the caller supplied, or else a new configured engine."""
5557
if provided_connection is not None:
5658
_run_migrations_with_connection(provided_connection)
5759
return

‎migrations/v2/env.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ def _include_application_object(
3838
_compare_to: SchemaItem | None,
3939
) -> bool:
4040
"""Keep Alembic's own version table outside application drift."""
41-
4241
return not (type_ == "table" and name == "alembic_version")
4342

4443

@@ -65,13 +64,19 @@ def _configure(connection: Connection) -> None:
6564

6665

6766
def run_migrations_offline() -> None:
67+
"""Raise, because v2 migrations need a live connection to qualify the target."""
6868
raise RuntimeError(
6969
"v2 migrations require an online connection so target identity and "
7070
"generated application-data before/after states can be verified"
7171
)
7272

7373

7474
def run_migrations_online() -> None:
75+
"""Migrate the caller's connection, or else a new engine in one transaction.
76+
77+
The new engine's transaction commits, so the target qualification's reads
78+
cannot leave the completed migration to be rolled back.
79+
"""
7580
provided_connection = config.attributes.get("connection")
7681
if provided_connection is not None:
7782
_configure(provided_connection)

0 commit comments

Comments
 (0)