Skip to content

Commit cb3b0fd

Browse files
rustyconoverclaude
andcommitted
chore: adopt vgi-python ruff/mypy/pydoclint code-quality config
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9d27bef commit cb3b0fd

8 files changed

Lines changed: 91 additions & 11 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ jobs:
5656
run: uv run ruff check .
5757

5858
- name: Type check (mypy)
59-
run: uv run mypy vgi_vision/
59+
run: uv run mypy vgi_vision/ vision_worker.py
60+
61+
- name: Docstring lint (pydoclint)
62+
run: uv run pydoclint vgi_vision/ vision_worker.py
6063

6164
# Resolve the latest published haybarn release once, so the whole matrix tests
6265
# the same version (and we never hardcode/pin it).

conftest.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1-
# Presence of this file puts the repo root on sys.path so tests can
2-
# `import vision_worker` and `import vgi_vision`.
1+
"""Pytest configuration.
2+
3+
Presence of this file puts the repo root on ``sys.path`` so tests can
4+
``import vision_worker`` and ``import vgi_vision``.
5+
"""

pyproject.toml

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ dev = [
4343
"pytest>=8",
4444
"ruff",
4545
"mypy",
46+
"pydoclint",
4647
]
4748

4849

@@ -54,12 +55,36 @@ build-backend = "hatchling.build"
5455
packages = ["vgi_vision"]
5556

5657
[tool.ruff]
57-
line-length = 110
58+
line-length = 120
5859
target-version = "py313"
5960

6061
[tool.ruff.lint]
61-
select = ["E", "F", "I", "UP", "B"]
62+
select = ["E", "F", "I", "UP", "B", "SIM", "D"]
63+
64+
[tool.ruff.lint.per-file-ignores]
65+
"tests/**" = ["D"]
66+
67+
[tool.ruff.lint.pydocstyle]
68+
convention = "google"
69+
70+
[tool.ruff.format]
71+
quote-style = "double"
72+
73+
[tool.pydoclint]
74+
style = "google"
75+
arg_type_hints_in_docstring = false
76+
check_return_types = false
77+
check_yield_types = false
78+
check_class_attributes = true
79+
skip_checking_raises = true
80+
allow_init_docstring = true
6281

6382
[tool.mypy]
6483
python_version = "3.13"
84+
strict = true
85+
warn_return_any = true
86+
warn_unused_ignores = true
87+
88+
[[tool.mypy.overrides]]
89+
module = ["onnxruntime.*"]
6590
ignore_missing_imports = true

tests/test_client_integration.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,7 @@ def test_top_label_scalar_end_to_end() -> None:
4949
# The image arrives as the (sole) input column; its BINARY type selects the
5050
# blob overload of top_label. No positional const arg is passed -- the column
5151
# is the argument (mirrors DuckDB's `top_label(image)` over a BLOB column).
52-
batch = pa.RecordBatch.from_pydict(
53-
{"image": pa.array([png_bytes((200, 30, 30)), None], type=pa.binary())}
54-
)
52+
batch = pa.RecordBatch.from_pydict({"image": pa.array([png_bytes((200, 30, 30)), None], type=pa.binary())})
5553
with _client() as client:
5654
results = list(
5755
client.scalar_function(

uv.lock

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vgi_vision/model.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ def labels() -> list[str]:
206206

207207
@lru_cache(maxsize=1)
208208
def _input_name() -> str:
209-
return _session().get_inputs()[0].name
209+
return str(_session().get_inputs()[0].name)
210210

211211

212212
# ---------------------------------------------------------------------------
@@ -244,7 +244,8 @@ def _softmax(logits: np.ndarray) -> np.ndarray:
244244
"""Numerically-stable softmax over the last axis."""
245245
z = logits - np.max(logits)
246246
e = np.exp(z)
247-
return e / np.sum(e)
247+
result: np.ndarray = e / np.sum(e)
248+
return result
248249

249250

250251
def classify_image(data: bytes | None, top_k: int = 5) -> list[tuple[str, float]] | None:

vgi_vision/scalars.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ class TopLabelFunction(ScalarFunction):
4949
"""``top_label(image)`` -- the #1 predicted ImageNet label for a BLOB image."""
5050

5151
class Meta:
52+
"""VGI function metadata."""
53+
5254
name = "top_label"
5355
description = "The #1 predicted ImageNet label for an image BLOB (NULL if not an image)"
5456
categories = ["vision", "classification"]
@@ -63,6 +65,7 @@ class Meta:
6365
def compute(
6466
cls, image: Annotated[pa.BinaryArray, Param(doc="Image bytes (PNG/JPEG/...).")]
6567
) -> Annotated[pa.StringArray, Returns()]:
68+
"""Return the top label for each image BLOB in the array."""
6669
out = [None if b is None else model.top_label_for(b) for b in image.to_pylist()]
6770
return pa.array(out, type=pa.string())
6871

@@ -71,6 +74,8 @@ class TopLabelPathFunction(ScalarFunction):
7174
"""``top_label(path)`` -- like ``top_label(image)`` but reads a file path."""
7275

7376
class Meta:
77+
"""VGI function metadata."""
78+
7479
name = "top_label"
7580
description = "The #1 predicted ImageNet label for an image file path (NULL if unreadable)"
7681
categories = ["vision", "classification"]
@@ -85,6 +90,7 @@ class Meta:
8590
def compute(
8691
cls, path: Annotated[pa.StringArray, Param(doc="Filesystem path to an image file.")]
8792
) -> Annotated[pa.StringArray, Returns()]:
93+
"""Return the top label for the image at each filesystem path in the array."""
8894
out = [None if p is None else model.top_label_for(_read_path(p)) for p in path.to_pylist()]
8995
return pa.array(out, type=pa.string())
9096

vgi_vision/tables.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@
3030
from vgi.metadata import FunctionExample
3131
from vgi.table_function import (
3232
BindParams,
33-
OutputCollector,
3433
ProcessParams,
3534
TableCardinality,
3635
TableFunctionGenerator,
3736
bind_fixed_schema,
3837
init_single_worker,
3938
)
39+
from vgi_rpc.rpc import OutputCollector
4040

4141
from . import model
4242
from .scalars import _read_path
@@ -99,6 +99,8 @@ class ClassifyFunction(TableFunctionGenerator[_ClassifyBlobArgs]):
9999
FIXED_SCHEMA: ClassVar[pa.Schema] = _CLASSIFY_SCHEMA
100100

101101
class Meta:
102+
"""VGI function metadata."""
103+
102104
name = "classify"
103105
description = "Top-5 ImageNet predictions (label, confidence) for an image BLOB"
104106
categories = ["vision", "classification"]
@@ -111,10 +113,12 @@ class Meta:
111113

112114
@classmethod
113115
def cardinality(cls, params: BindParams[_ClassifyBlobArgs]) -> TableCardinality:
116+
"""Estimate the output row count (the default top-k)."""
114117
return TableCardinality(estimate=_DEFAULT_TOP_K, max=_DEFAULT_TOP_K)
115118

116119
@classmethod
117120
def process(cls, params: ProcessParams[_ClassifyBlobArgs], state: None, out: OutputCollector) -> None:
121+
"""Classify the image BLOB and emit the top-5 predictions."""
118122
preds = model.classify_image(params.args.image, top_k=_DEFAULT_TOP_K)
119123
_emit_classify(preds, out, params.output_schema)
120124

@@ -128,6 +132,8 @@ class ClassifyTopKFunction(TableFunctionGenerator[_ClassifyBlobTopKArgs]):
128132
FIXED_SCHEMA: ClassVar[pa.Schema] = _CLASSIFY_SCHEMA
129133

130134
class Meta:
135+
"""VGI function metadata."""
136+
131137
name = "classify"
132138
description = "Top-k ImageNet predictions (label, confidence) for an image BLOB"
133139
categories = ["vision", "classification"]
@@ -140,11 +146,13 @@ class Meta:
140146

141147
@classmethod
142148
def cardinality(cls, params: BindParams[_ClassifyBlobTopKArgs]) -> TableCardinality:
149+
"""Estimate the output row count (the requested top-k)."""
143150
k = max(1, params.args.top_k)
144151
return TableCardinality(estimate=k, max=k)
145152

146153
@classmethod
147154
def process(cls, params: ProcessParams[_ClassifyBlobTopKArgs], state: None, out: OutputCollector) -> None:
155+
"""Classify the image BLOB and emit the top-k predictions."""
148156
preds = model.classify_image(params.args.image, top_k=params.args.top_k)
149157
_emit_classify(preds, out, params.output_schema)
150158

@@ -174,6 +182,8 @@ class ClassifyPathFunction(TableFunctionGenerator[_ClassifyPathArgs]):
174182
FIXED_SCHEMA: ClassVar[pa.Schema] = _CLASSIFY_SCHEMA
175183

176184
class Meta:
185+
"""VGI function metadata."""
186+
177187
name = "classify"
178188
description = "Top-5 ImageNet predictions for an image file path"
179189
categories = ["vision", "classification"]
@@ -186,10 +196,12 @@ class Meta:
186196

187197
@classmethod
188198
def cardinality(cls, params: BindParams[_ClassifyPathArgs]) -> TableCardinality:
199+
"""Estimate the output row count (the default top-k)."""
189200
return TableCardinality(estimate=_DEFAULT_TOP_K, max=_DEFAULT_TOP_K)
190201

191202
@classmethod
192203
def process(cls, params: ProcessParams[_ClassifyPathArgs], state: None, out: OutputCollector) -> None:
204+
"""Read the image off disk, classify it, and emit the top-5 predictions."""
193205
preds = model.classify_image(_read_path(params.args.path), top_k=_DEFAULT_TOP_K)
194206
_emit_classify(preds, out, params.output_schema)
195207

@@ -203,6 +215,8 @@ class ClassifyPathTopKFunction(TableFunctionGenerator[_ClassifyPathTopKArgs]):
203215
FIXED_SCHEMA: ClassVar[pa.Schema] = _CLASSIFY_SCHEMA
204216

205217
class Meta:
218+
"""VGI function metadata."""
219+
206220
name = "classify"
207221
description = "Top-k ImageNet predictions for an image file path"
208222
categories = ["vision", "classification"]
@@ -215,11 +229,13 @@ class Meta:
215229

216230
@classmethod
217231
def cardinality(cls, params: BindParams[_ClassifyPathTopKArgs]) -> TableCardinality:
232+
"""Estimate the output row count (the requested top-k)."""
218233
k = max(1, params.args.top_k)
219234
return TableCardinality(estimate=k, max=k)
220235

221236
@classmethod
222237
def process(cls, params: ProcessParams[_ClassifyPathTopKArgs], state: None, out: OutputCollector) -> None:
238+
"""Read the image off disk, classify it, and emit the top-k predictions."""
223239
preds = model.classify_image(_read_path(params.args.path), top_k=params.args.top_k)
224240
_emit_classify(preds, out, params.output_schema)
225241

@@ -243,6 +259,8 @@ class ImageClassesFunction(TableFunctionGenerator[_NoArgs]):
243259
FIXED_SCHEMA: ClassVar[pa.Schema] = _CLASSES_SCHEMA
244260

245261
class Meta:
262+
"""VGI function metadata."""
263+
246264
name = "image_classes"
247265
description = "The model's ImageNet label set: (idx, label), 1000 rows"
248266
categories = ["vision", "classification"]
@@ -255,10 +273,12 @@ class Meta:
255273

256274
@classmethod
257275
def cardinality(cls, params: BindParams[_NoArgs]) -> TableCardinality:
276+
"""Estimate the output row count (the model's full label set)."""
258277
return TableCardinality(estimate=model.NUM_CLASSES, max=model.NUM_CLASSES)
259278

260279
@classmethod
261280
def process(cls, params: ProcessParams[_NoArgs], state: None, out: OutputCollector) -> None:
281+
"""Emit every ``(idx, label)`` the classifier can predict."""
262282
rows = model.class_table()
263283
out.emit(
264284
pa.RecordBatch.from_pydict(

0 commit comments

Comments
 (0)