Skip to content

Commit 122a6e4

Browse files
committed
Fix all ruff and mypy issues; CI passes clean
1 parent 394b4e0 commit 122a6e4

18 files changed

+102
-72
lines changed

jqueue/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,12 @@ async def write(content, if_match=None) -> str
6363
from jqueue.core.broker import BrokerQueue
6464
from jqueue.core.direct import DirectQueue
6565
from jqueue.core.heartbeat import HeartbeatManager
66-
from jqueue.domain.errors import CASConflictError, JQueueError, JobNotFoundError, StorageError
66+
from jqueue.domain.errors import (
67+
CASConflictError,
68+
JobNotFoundError,
69+
JQueueError,
70+
StorageError,
71+
)
6772
from jqueue.domain.models import Job, JobStatus, QueueState
6873
from jqueue.ports.storage import ObjectStoragePort
6974

jqueue/adapters/storage/gcs.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
Note: google-cloud-storage is synchronous. All operations are wrapped in
1919
asyncio.to_thread to avoid blocking the event loop.
2020
"""
21+
2122
from __future__ import annotations
2223

2324
import asyncio
@@ -50,13 +51,13 @@ def _get_client(self) -> GCSClient:
5051
if self.client is not None:
5152
return self.client
5253
try:
53-
from google.cloud import storage # type: ignore[import-untyped]
54+
from google.cloud import storage
5455
except ImportError as exc:
5556
raise ImportError(
5657
"GCSStorage requires google-cloud-storage. "
5758
"Install with: pip install 'jqueue[gcs]'"
5859
) from exc
59-
return storage.Client() # type: ignore[return-value]
60+
return storage.Client()
6061

6162
async def read(self) -> tuple[bytes, str | None]:
6263
"""Read the state blob. Returns (b"", None) if the blob does not exist."""
@@ -86,15 +87,15 @@ async def write(
8687

8788
def _sync_read(self) -> tuple[bytes, str | None]:
8889
try:
89-
from google.api_core import exceptions as gapi_exc # type: ignore[import-untyped]
90+
from google.api_core import exceptions as gapi_exc
9091
except ImportError as exc:
9192
raise ImportError(
9293
"GCSStorage requires google-cloud-storage. "
9394
"Install with: pip install 'jqueue[gcs]'"
9495
) from exc
9596

9697
client = self._get_client()
97-
blob = client.bucket(self.bucket_name).blob(self.blob_name) # type: ignore[attr-defined]
98+
blob = client.bucket(self.bucket_name).blob(self.blob_name)
9899
try:
99100
content: bytes = blob.download_as_bytes()
100101
return content, str(blob.generation)
@@ -103,27 +104,27 @@ def _sync_read(self) -> tuple[bytes, str | None]:
103104

104105
def _sync_write(self, content: bytes, if_match: str | None) -> str:
105106
try:
106-
from google.api_core import exceptions as gapi_exc # type: ignore[import-untyped]
107+
from google.api_core import exceptions as gapi_exc
107108
except ImportError as exc:
108109
raise ImportError(
109110
"GCSStorage requires google-cloud-storage. "
110111
"Install with: pip install 'jqueue[gcs]'"
111112
) from exc
112113

113114
client = self._get_client()
114-
blob = client.bucket(self.bucket_name).blob(self.blob_name) # type: ignore[attr-defined]
115+
blob = client.bucket(self.bucket_name).blob(self.blob_name)
115116

116117
# if_generation_match=0 → "blob must not exist yet"
117118
gen_match: int = 0 if if_match is None else int(if_match)
118119

119120
try:
120-
blob.upload_from_string( # type: ignore[attr-defined]
121+
blob.upload_from_string(
121122
content,
122123
content_type="application/json",
123124
if_generation_match=gen_match,
124125
)
125126
except gapi_exc.PreconditionFailed as exc:
126127
raise CASConflictError("GCS generation mismatch") from exc
127128

128-
blob.reload() # type: ignore[attr-defined]
129+
blob.reload()
129130
return str(blob.generation)

jqueue/adapters/storage/s3.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
Compatible with S3-compatible storage that supports conditional writes:
1818
MinIO, Cloudflare R2, Tigris, etc.
1919
"""
20+
2021
from __future__ import annotations
2122

2223
import dataclasses
@@ -52,12 +53,12 @@ def _get_session(self) -> AioBoto3Session:
5253
if self.session is not None:
5354
return self.session
5455
try:
55-
import aioboto3 # type: ignore[import-untyped]
56+
import aioboto3
5657
except ImportError as exc:
5758
raise ImportError(
5859
"S3Storage requires aioboto3. Install with: pip install 'jqueue[s3]'"
5960
) from exc
60-
return aioboto3.Session() # type: ignore[return-value]
61+
return aioboto3.Session()
6162

6263
def _client_kwargs(self) -> dict[str, str]:
6364
"""Build kwargs forwarded to the S3 client constructor."""
@@ -72,7 +73,7 @@ async def read(self) -> tuple[bytes, str | None]:
7273
"""Read the state object. Returns (b"", None) if the key does not exist."""
7374
session = self._get_session()
7475
try:
75-
async with session.client("s3", **self._client_kwargs()) as s3: # type: ignore[attr-defined]
76+
async with session.client("s3", **self._client_kwargs()) as s3:
7677
try:
7778
response = await s3.get_object(Bucket=self.bucket, Key=self.key)
7879
content: bytes = await response["Body"].read()
@@ -95,7 +96,7 @@ async def write(
9596
"""CAS write. Raises CASConflictError on ETag mismatch (PreconditionFailed)."""
9697
session = self._get_session()
9798
try:
98-
async with session.client("s3", **self._client_kwargs()) as s3: # type: ignore[attr-defined]
99+
async with session.client("s3", **self._client_kwargs()) as s3:
99100
put_kwargs: dict[str, str | bytes] = {
100101
"Bucket": self.bucket,
101102
"Key": self.key,

jqueue/core/broker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def __post_init__(self) -> None:
5757
stale_timeout=self.stale_timeout,
5858
)
5959

60-
async def __aenter__(self) -> "BrokerQueue":
60+
async def __aenter__(self) -> BrokerQueue:
6161
await self._loop.start()
6262
return self
6363

jqueue/core/direct.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919

2020
import asyncio
2121
import dataclasses
22-
from datetime import datetime, timedelta, timezone
23-
from typing import Callable
22+
from collections.abc import Callable
23+
from datetime import UTC, datetime, timedelta
2424

2525
from jqueue.core import codec
2626
from jqueue.domain.errors import CASConflictError, JobNotFoundError
@@ -78,7 +78,7 @@ def _fn(state: QueueState) -> QueueState:
7878
new_state = state
7979
for job in available:
8080
updated = job.with_status(JobStatus.IN_PROGRESS).with_heartbeat(
81-
datetime.now(timezone.utc)
81+
datetime.now(UTC)
8282
)
8383
new_state = new_state.with_job_replaced(updated)
8484
claimed.append(updated)
@@ -112,7 +112,7 @@ def _fn(state: QueueState) -> QueueState:
112112
if job is None:
113113
raise JobNotFoundError(job_id)
114114
return state.with_job_replaced(
115-
job.with_heartbeat(datetime.now(timezone.utc))
115+
job.with_heartbeat(datetime.now(UTC))
116116
)
117117

118118
await self._mutate(_fn)
@@ -123,7 +123,7 @@ async def requeue_stale(self, timeout: timedelta) -> int:
123123
124124
Returns the number of jobs re-queued.
125125
"""
126-
cutoff = datetime.now(timezone.utc) - timeout
126+
cutoff = datetime.now(UTC) - timeout
127127
requeued = 0
128128

129129
def _fn(state: QueueState) -> QueueState:

jqueue/core/group_commit.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727

2828
import asyncio
2929
import dataclasses
30-
from datetime import datetime, timedelta, timezone
31-
from typing import Callable
30+
from collections.abc import Callable
31+
from datetime import UTC, datetime, timedelta
3232

3333
from jqueue.core import codec
3434
from jqueue.domain.errors import CASConflictError, JQueueError
@@ -133,7 +133,7 @@ def _fn(state: QueueState) -> QueueState:
133133
new_state = state
134134
for job in available:
135135
updated = job.with_status(JobStatus.IN_PROGRESS).with_heartbeat(
136-
datetime.now(timezone.utc)
136+
datetime.now(UTC)
137137
)
138138
new_state = new_state.with_job_replaced(updated)
139139
claimed.append(updated)
@@ -169,7 +169,7 @@ def _fn(state: QueueState) -> QueueState:
169169
if job is None:
170170
raise JobNotFoundError(job_id)
171171
return state.with_job_replaced(
172-
job.with_heartbeat(datetime.now(timezone.utc))
172+
job.with_heartbeat(datetime.now(UTC))
173173
)
174174

175175
await self._submit(_fn)
@@ -224,7 +224,7 @@ async def _commit_batch(self, batch: list[_PendingOp]) -> None:
224224
state = codec.decode(content)
225225

226226
# Sweep stale jobs on every write cycle (free — no extra I/O)
227-
cutoff = datetime.now(timezone.utc) - self.stale_timeout
227+
cutoff = datetime.now(UTC) - self.stale_timeout
228228
state = state.requeue_stale(cutoff)
229229

230230
per_op_errors: dict[int, Exception] = {}

jqueue/core/heartbeat.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class HeartbeatManager:
5959
default=None, init=False, repr=False
6060
)
6161

62-
async def __aenter__(self) -> "HeartbeatManager":
62+
async def __aenter__(self) -> HeartbeatManager:
6363
self._task = asyncio.create_task(
6464
self._beat(), name=f"jqueue-heartbeat-{self.job_id}"
6565
)

jqueue/domain/models.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313

1414
import base64
1515
import uuid
16-
from datetime import datetime, timezone
17-
from enum import Enum
16+
from datetime import UTC, datetime
17+
from enum import StrEnum
1818

1919
from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator
2020

2121

22-
class JobStatus(str, Enum):
22+
class JobStatus(StrEnum):
2323
"""Lifecycle states for a queued job."""
2424

2525
QUEUED = "queued"
@@ -47,7 +47,7 @@ class Job(BaseModel):
4747
payload: bytes
4848
status: JobStatus = JobStatus.QUEUED
4949
priority: int = 0
50-
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
50+
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
5151
heartbeat_at: datetime | None = None
5252

5353
@field_validator("payload", mode="before")
@@ -61,7 +61,8 @@ def _decode_payload(cls, v: str | bytes) -> bytes:
6161
return base64.b64decode(v)
6262
case _:
6363
raise ValueError(
64-
f"payload must be bytes or a base64-encoded str, got {type(v).__name__}"
64+
"payload must be bytes or a base64-encoded str, "
65+
f"got {type(v).__name__}"
6566
)
6667

6768
@field_serializer("payload")
@@ -154,9 +155,7 @@ def with_job_removed(self, job_id: str) -> "QueueState":
154155
new_jobs = tuple(j for j in self.jobs if j.id != job_id)
155156
if len(new_jobs) == original_len:
156157
raise JobNotFoundError(job_id)
157-
return self.model_copy(
158-
update={"jobs": new_jobs, "version": self.version + 1}
159-
)
158+
return self.model_copy(update={"jobs": new_jobs, "version": self.version + 1})
160159

161160
def requeue_stale(self, cutoff: datetime) -> "QueueState":
162161
"""
@@ -175,6 +174,4 @@ def requeue_stale(self, cutoff: datetime) -> "QueueState":
175174
)
176175
if new_jobs == self.jobs:
177176
return self
178-
return self.model_copy(
179-
update={"jobs": new_jobs, "version": self.version + 1}
180-
)
177+
return self.model_copy(update={"jobs": new_jobs, "version": self.version + 1})

pyproject.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,11 @@ select = ["E", "F", "I", "UP"]
3232
[tool.mypy]
3333
strict = true
3434
python_version = "3.12"
35+
36+
[[tool.mypy.overrides]]
37+
module = ["aioboto3", "aioboto3.*", "google.cloud.storage", "google.cloud.*", "google.api_core", "google.api_core.*"]
38+
ignore_missing_imports = true
39+
40+
[[tool.mypy.overrides]]
41+
module = ["tests.*"]
42+
disallow_untyped_defs = false

tests/test_broker_queue.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from jqueue.domain.errors import JobNotFoundError
88
from jqueue.domain.models import JobStatus
99

10-
1110
# ---------------------------------------------------------------------------
1211
# Lifecycle
1312
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)