Skip to content

Commit fa8ca1d

Browse files
log0sclaude
andcommitted
chore(backend): clear 102 ruff lint errors
Config: drop TCH from select (breaks FastAPI runtime get_type_hints on route signatures) and allowlist fastapi.Depends/Query/etc. for B008 so DI defaults don't fire. Auto-fix I001/F401/UP017/F541 across 15 files. Manual: B023 capture `remaining` via default arg in stac.py score() to pin the closure; SIM102 collapse nested if in demographics; B905 strict=False on zip() in census + imagery; SIM117 combine nested `with patch()` pairs in test_parcels. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 370ba56 commit fa8ca1d

16 files changed

Lines changed: 86 additions & 65 deletions

backend/app/api/v1/events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
PropertyEventsResponse,
2121
)
2222
from app.services import property_events as property_events_service
23-
from app.services.county_adapters import get_adapter_for_county, get_supported_counties
23+
from app.services.county_adapters import get_adapter_for_county
2424

2525
router = APIRouter()
2626

backend/app/api/v1/geocode.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import httpx
99
from fastapi import APIRouter, Depends, HTTPException, Query
10-
from fastapi.responses import JSONResponse
1110
from redis.exceptions import RedisError
1211
from sqlalchemy.orm import Session
1312

backend/app/api/v1/imagery.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
from datetime import date
1717

1818
import httpx
19-
from fastapi import APIRouter, Depends, HTTPException, Query, Response as FastAPIResponse
19+
from fastapi import APIRouter, Depends, HTTPException, Query
20+
from fastapi import Response as FastAPIResponse
2021
from fastapi.responses import Response
2122
from redis.exceptions import RedisError
2223
from sqlalchemy.orm import Session
@@ -171,7 +172,7 @@ async def list_imagery(
171172
return_exceptions=True,
172173
)
173174
signed_map: dict[str, str] = {
174-
u: (r if isinstance(r, str) else u) for u, r in zip(url_list, results)
175+
u: (r if isinstance(r, str) else u) for u, r in zip(url_list, results, strict=False)
175176
}
176177

177178
snapshot_responses: list[ImagerySnapshotResponse] = []

backend/app/services/census.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,14 @@ async def _request(
169169

170170
if resp.status_code in (204, 404):
171171
logger.info(
172-
f"Census API: no data for tract",
172+
"Census API: no data for tract",
173173
extra={"url": url, "status": resp.status_code},
174174
)
175175
return None
176176

177177
if resp.status_code != 200:
178178
logger.error(
179-
f"Census API error",
179+
"Census API error",
180180
extra={"url": url, "status": resp.status_code, "body": resp.text[:500]},
181181
)
182182
raise CensusApiError(
@@ -213,7 +213,7 @@ def _parse_response(data: list[list[str]]) -> dict[str, int | float | None]:
213213

214214
return {
215215
h: _to_number(v)
216-
for h, v in zip(headers, values)
216+
for h, v in zip(headers, values, strict=False)
217217
if h not in geo_fields
218218
}
219219

backend/app/services/county_adapters.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import logging
1616
from abc import ABC, abstractmethod
1717
from dataclasses import dataclass
18-
from datetime import date
18+
from datetime import UTC, date
1919
from typing import Any
2020

2121
from app.services.arcgis import query_feature_service
@@ -720,9 +720,9 @@ def _parse_epoch_ms(value: Any) -> date | None:
720720
if value is None:
721721
return None
722722
try:
723-
from datetime import datetime, timezone
723+
from datetime import datetime
724724

725-
return datetime.fromtimestamp(int(value) / 1000, tz=timezone.utc).date()
725+
return datetime.fromtimestamp(int(value) / 1000, tz=UTC).date()
726726
except (ValueError, TypeError, OSError):
727727
return parse_date(str(value))
728728

backend/app/services/demographics.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,8 @@ def upsert_census_snapshot(
6060
vacancy_rate: float | None = None
6161
total_housing = data.get("total_housing_units")
6262
occupied = data.get("occupied_housing_units")
63-
if total_housing and total_housing > 0:
64-
if occupied is not None:
65-
vacancy_rate = round((total_housing - occupied) / total_housing, 4)
63+
if total_housing and total_housing > 0 and occupied is not None:
64+
vacancy_rate = round((total_housing - occupied) / total_housing, 4)
6665

6766
snap_id = uuid.uuid4()
6867

backend/app/services/imagery.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@
1313
import logging
1414
import uuid
1515
from dataclasses import dataclass
16-
from datetime import date, datetime, timezone
16+
from datetime import UTC, date, datetime
1717

18-
from sqlalchemy import select, text as sa_text
18+
from sqlalchemy import select
19+
from sqlalchemy import text as sa_text
1920
from sqlalchemy.orm import Session
2021

2122
from app.models.parcels import Parcel, TimelineRequest, TimelineRequestTask
@@ -137,9 +138,9 @@ def update_request_task(
137138
task.status = status
138139
task.items_found = items_found
139140
if status == "processing":
140-
task.started_at = datetime.now(tz=timezone.utc)
141+
task.started_at = datetime.now(tz=UTC)
141142
elif status in ("complete", "failed", "skipped"):
142-
task.completed_at = datetime.now(tz=timezone.utc)
143+
task.completed_at = datetime.now(tz=UTC)
143144
if error_message:
144145
task.error_message = error_message
145146
db.commit()
@@ -154,7 +155,7 @@ def update_timeline_request_status(
154155
"""Update the parent timeline request status."""
155156
request.status = status
156157
if status in ("complete", "failed"):
157-
request.completed_at = datetime.now(tz=timezone.utc)
158+
request.completed_at = datetime.now(tz=UTC)
158159
if error_message:
159160
request.error_message = error_message
160161
db.commit()

backend/app/services/stac.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,15 +318,18 @@ def select_naip_items(
318318
candidates = list(year_items)
319319

320320
while candidates and len(selected_for_year) < max_tiles_per_year:
321-
def score(item: dict[str, object]) -> tuple[float, float]:
321+
def score(
322+
item: dict[str, object],
323+
_remaining: tuple[float, float, float, float] = remaining,
324+
) -> tuple[float, float]:
322325
bbox = item.get("bbox")
323326
if not bbox or len(bbox) < 4: # type: ignore[arg-type]
324327
return (0.0, float(abs(_doy(item) - target_doy)))
325328
ib = (
326329
float(bbox[0]), float(bbox[1]), # type: ignore[index]
327330
float(bbox[2]), float(bbox[3]), # type: ignore[index]
328331
)
329-
area = _bbox_intersection_area(ib, remaining)
332+
area = _bbox_intersection_area(ib, _remaining)
330333
# Maximize area, minimize doy distance
331334
return (-area, float(abs(_doy(item) - target_doy)))
332335

backend/app/tasks/timeline.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@
1515

1616
import httpx
1717

18-
from app.services import stac as stac_service
19-
from app.services import imagery as imagery_service
2018
from app.services import demographics as demographics_service
19+
from app.services import imagery as imagery_service
2120
from app.services import property_events as property_events_service
21+
from app.services import stac as stac_service
2222
from app.services.address_normalizer import extract_search_terms, is_address_match
23-
from app.services.county_adapters import get_adapter_for_county
2423
from app.services.census import (
2524
ACS5_YEARS,
2625
DECENNIAL_YEARS,
2726
CensusApiError,
2827
CensusFetcher,
2928
parse_tract_fips,
3029
)
30+
from app.services.county_adapters import get_adapter_for_county
3131
from app.tasks.celery_app import celery_app
3232

3333
logger = logging.getLogger(__name__)
@@ -148,6 +148,7 @@ async def _fetch_source(
148148
with SessionLocal() as db:
149149
# Find and update the task row
150150
from sqlalchemy import select as sa_select
151+
151152
from app.models.parcels import TimelineRequest, TimelineRequestTask
152153

153154
request = db.execute(
@@ -215,9 +216,10 @@ async def _fetch_source(
215216
extra={"error": str(exc)},
216217
)
217218
with SessionLocal() as db:
218-
from app.models.parcels import TimelineRequestTask
219219
from sqlalchemy import select as sa_select
220220

221+
from app.models.parcels import TimelineRequestTask
222+
221223
task_row = db.execute(
222224
sa_select(TimelineRequestTask)
223225
.where(TimelineRequestTask.timeline_request_id == timeline_request_id)
@@ -300,9 +302,10 @@ async def _fetch_source(
300302
items_saved += 1
301303

302304
# Update task status
303-
from app.models.parcels import TimelineRequestTask
304305
from sqlalchemy import select as sa_select
305306

307+
from app.models.parcels import TimelineRequestTask
308+
306309
task_row = db.execute(
307310
sa_select(TimelineRequestTask)
308311
.where(TimelineRequestTask.timeline_request_id == timeline_request_id)
@@ -331,9 +334,10 @@ async def _fetch_census(
331334
332335
Returns the number of census snapshots saved.
333336
"""
337+
from sqlalchemy import select as sa_select
338+
334339
from app.db import SessionLocal
335340
from app.models.parcels import TimelineRequestTask
336-
from sqlalchemy import select as sa_select
337341

338342
try:
339343
state_fips, county_fips, tract_code = parse_tract_fips(tract_fips)
@@ -427,7 +431,7 @@ async def _fetch_census(
427431
db, task_row, "complete", items_found=items_saved
428432
)
429433

430-
logger.info(f"Census fetch complete", extra={"items_saved": items_saved, "tract": tract_fips})
434+
logger.info("Census fetch complete", extra={"items_saved": items_saved, "tract": tract_fips})
431435
return items_saved
432436

433437

@@ -442,9 +446,10 @@ async def _fetch_property(
442446
443447
Returns the number of events saved.
444448
"""
449+
from sqlalchemy import select as sa_select
450+
445451
from app.db import SessionLocal
446452
from app.models.parcels import TimelineRequestTask
447-
from sqlalchemy import select as sa_select
448453

449454
adapter = get_adapter_for_county(county)
450455

@@ -587,9 +592,10 @@ async def _fetch_property(
587592

588593
async def _run_timeline(timeline_request_id: str) -> dict[str, Any]:
589594
"""Orchestrate all imagery sources for a timeline request."""
595+
from sqlalchemy import select as sa_select
596+
590597
from app.db import SessionLocal
591598
from app.models.parcels import TimelineRequest
592-
from sqlalchemy import select as sa_select
593599

594600
req_uuid = uuid.UUID(timeline_request_id)
595601

backend/pyproject.toml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,24 @@ line-length = 100
5454
target-version = "py312"
5555

5656
[tool.ruff.lint]
57-
select = ["E", "F", "I", "UP", "B", "SIM", "TCH"]
57+
select = ["E", "F", "I", "UP", "B", "SIM"]
5858
ignore = ["E501"]
5959

60+
[tool.ruff.lint.flake8-bugbear]
61+
# FastAPI dependency-injection idiom: Depends(), Query(), etc. are required
62+
# as argument defaults. Allowlist them so B008 doesn't fire on route signatures.
63+
extend-immutable-calls = [
64+
"fastapi.Depends",
65+
"fastapi.Query",
66+
"fastapi.Path",
67+
"fastapi.Header",
68+
"fastapi.Cookie",
69+
"fastapi.Body",
70+
"fastapi.Form",
71+
"fastapi.File",
72+
"fastapi.Security",
73+
]
74+
6075
[tool.mypy]
6176
python_version = "3.12"
6277
strict = true

0 commit comments

Comments
 (0)