Skip to content

Enable ty rules: not-iterable, call-non-callable, unsupported-operator, invalid-assignment - #796

Merged
d-chambers merged 4 commits into
devfrom
ty-burn-1
Aug 1, 2026
Merged

Enable ty rules: not-iterable, call-non-callable, unsupported-operator, invalid-assignment#796
d-chambers merged 4 commits into
devfrom
ty-burn-1

Conversation

@d-chambers

@d-chambers d-chambers commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Description

First burn-down PR following #795: re-enables the four smallest ignored ty rules — not-iterable, call-non-callable, unsupported-operator, and invalid-assignment — fixes all of their diagnostics, and deletes their ignore lines from [tool.ty.rules] so new violations fail pre-commit/CI from now on.

The fixes make the hints tell the truth rather than contorting code:

  • ArrayLike (utils/models.py) is now Annotated[np.ndarray, ...] instead of Annotated[object, ...] — the validator already guarantees an ndarray. This one change also removed a large chunk of diagnostics under the still-ignored rules (no-matching-overload 41→12, not-subscriptable 36→19).
  • is_array and is_pathlike are now TypeGuard/TypeIs predicates, so existing guard branches narrow properly.
  • get_quantity/get_factor_and_unit use plain unions instead of the misused str_or_none TypeVar; get_array no longer claims to return a BaseCoord; the abstract BaseCoord.__getitem__ no longer claims Self for int indices; track accepts Iterable; yield_range_tuple_from_kwargs is annotated as the generator it is.
  • A handful of assert x is not None guards document invariants ty can't derive (loop-set variables, structured-dtype field names, registry lookups callers pre-validate).
  • Two tagged inline # ty: ignore comments remain, both for checker limitations rather than code problems (a union-of-time-kinds arithmetic in io/index/planned.py and a TypeVar-intersection artifact in units.py); both are commented and greppable.

Small behavior fixes the rules flushed out:

  • invert_quantity("") now returns None instead of raising TypeError.
  • The TDMS unsupported-data-type error now formats correctly instead of raising TypeError when the type code is unknown.
  • spool.map no longer divides by None when os.cpu_count() returns None.

The ignored-rule counts comment in pyproject.toml is refreshed (invalid-argument-type 179, unresolved-attribute 138, invalid-return-type 72, invalid-method-override 35, not-subscriptable 19, no-matching-overload 12).

Changelog

none

Checklist

I have (if applicable):

  • referenced the GitHub issue this PR closes.
  • documented the new feature with docstrings and/or appropriate doc page.
  • included tests. See testing guidelines.
  • added the "ready_for_review" tag once the PR is ready to be reviewed.

Summary by CodeRabbit

  • New Features

    • Added support for remote and virtual paths when indexing data and writing WAV files.
    • Expanded unit conversion support for quantities, units, dates, and durations.
    • Improved compatibility with iterable inputs and broader resource types.
  • Bug Fixes

    • Fixed lowercase Fourier output handling and empty-unit inversion.
    • Improved coordinate validation, metadata parsing, and callable attribute handling.
    • Unsized iterables now preserve values without displaying a progress bar.
    • Improved reliability when CPU counts or optional configuration values are unavailable.
  • Refactor

    • Strengthened type checking and validation across data processing, transformations, and file I/O.

Fix all diagnostics for not-iterable, call-non-callable,
unsupported-operator, and invalid-assignment, and remove their ignore
lines from [tool.ty.rules]. Highlights:

- ArrayLike is now Annotated[np.ndarray, ...] instead of object, which
  also removes many diagnostics under the still-ignored rules.
- is_array/is_pathlike gained TypeGuard/TypeIs so their checks narrow.
- get_quantity/get_factor_and_unit use plain unions instead of the
  misused str_or_none TypeVar; invert_quantity now returns None instead
  of raising TypeError when given empty units.
- get_array and BaseCoord.__getitem__ annotations no longer claim coord
  returns for array/scalar results.
- TDMS unsupported-data-type error no longer raises TypeError for
  unknown type codes; spool.map guards os.cpu_count() returning None.
- Refresh the burn-down counts comment in pyproject.toml.
- convert_units accepts numeric | Quantity as its tests document.
- get_factor_and_unit accepts the datetime/timedelta inputs it supports.
- invert_quantity signature reflects the Quantity it actually returns.
- patches_to_df keeps accepting any object exposing get_contents().
- fbe uses 1 / sample_rate directly (get_dim_sampling_rate returns float).
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR updates type annotations, adds runtime assertions, and refines input handling across core, I/O, processing, and utility modules. It also updates the ty configuration and adds regression tests.

Changes

Core data and coordinate handling

Layer / File(s) Summary
Core contracts and validation
dascore/compat.py, dascore/core/..., dascore/utils/models.py
Core predicates, models, attributes, coordinate operations, reductions, and spool iteration now use stricter contracts and explicit validation.

I/O resource and metadata handling

Layer / File(s) Summary
I/O paths and metadata
dascore/io/index/..., dascore/io/segy/core.py, dascore/io/sintela/utils.py, dascore/io/tdms/utils.py, dascore/io/wav/core.py
I/O functions broaden supported path types, preserve path values, and validate structured metadata fields.

Processing and unit handling

Layer / File(s) Summary
Processing, transforms, and units
dascore/proc/..., dascore/transform/..., dascore/units.py, tests/test_units.py
Processing and transform functions normalize arrays, quantities, output selectors, and sampling steps. Unit APIs accept broader input types and test empty-unit inversion.

Utility contracts and type-checker cleanup

Layer / File(s) Summary
Utility handling and type-checker cleanup
dascore/utils/..., pyproject.toml, tests/test_utils/test_progress.py
Utilities add assertions, narrow callable and list checks, broaden iterable predicates, support unsized iterables, and remove selected ty ignore rules.

Possibly related PRs

  • DASDAE/dascore#523: Both changes modify units.py’s invert_quantity function.
  • DASDAE/dascore#693: Both changes modify Fourier dft output handling.
  • DASDAE/dascore#721: Both changes modify several functions updated in this PR, including is_array, coordinate reduction, Fourier output handling, and unit utilities.

Suggested labels: CI, patch

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the four ty rules enabled by the pull request.
Description check ✅ Passed The description explains the objectives, fixes, remaining ignores, and checklist in the required sections.
Docstring Coverage ✅ Passed Docstring coverage is 84.62% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ty-burn-1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added CI continuous integration patch related to Patch class labels Aug 1, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
dascore/io/index/planned.py (1)

128-130: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the redundant int cast around round.

Ruff reports RUF046 for this expression. Use round((hi - lo) / step) + 1; it preserves the current behavior for the supported numeric and time branches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dascore/io/index/planned.py` around lines 128 - 130, In the planned range
length calculation, update the expression assigned to length by removing the
redundant int cast around round while preserving the existing formula and
supported numeric/time behavior.

Source: Linters/SAST tools

dascore/io/sintela/utils.py (1)

106-108: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use strict zips for fixed-length metadata pairs.

Ruff reports B905 at all three calls. Add strict=True when the supported Python target is 3.10 or newer. Otherwise, configure the lint rule for the declared target.

  • dascore/io/sintela/utils.py#L106-L108: add strict=True to zip(names, array[0]).
  • dascore/io/sintela/utils.py#L135-L137: add strict=True to zip(names, buf[0]).
  • dascore/io/tdms/utils.py#L180-L180: add strict=True to zip(FILEINFO_NAMES, fields).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dascore/io/sintela/utils.py` around lines 106 - 108, Update the zip calls at
dascore/io/sintela/utils.py lines 106-108 and 135-137, and
dascore/io/tdms/utils.py line 180, to pass strict=True for these fixed-length
metadata pairs; if the declared Python target is below 3.10, configure Ruff’s
B905 rule instead.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dascore/utils/models.py`:
- Around line 39-41: Update the ArrayLike annotation and its validator typing to
use a structural protocol representing the duck-typed contract accepted by
compat.array(), rather than np.ndarray. Ensure BaseCoord.data and related
coordinate paths retain foreign array-like values while exposing only operations
guaranteed by that protocol, and keep array() behavior unchanged.

In `@dascore/utils/progress.py`:
- Line 41: Update the progress handling around the `sequence` length calculation
so unsized iterables do not reach the `length < min_length` comparison with
`length` set to None. Normalize an unknown length to zero before that
comparison, or otherwise require an explicit length consistent with the existing
API.

---

Nitpick comments:
In `@dascore/io/index/planned.py`:
- Around line 128-130: In the planned range length calculation, update the
expression assigned to length by removing the redundant int cast around round
while preserving the existing formula and supported numeric/time behavior.

In `@dascore/io/sintela/utils.py`:
- Around line 106-108: Update the zip calls at dascore/io/sintela/utils.py lines
106-108 and 135-137, and dascore/io/tdms/utils.py line 180, to pass strict=True
for these fixed-length metadata pairs; if the declared Python target is below
3.10, configure Ruff’s B905 rule instead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8037bcec-eecb-412e-b88c-f4a8b308b3c5

📥 Commits

Reviewing files that changed from the base of the PR and between 7394563 and f131af6.

📒 Files selected for processing (30)
  • dascore/compat.py
  • dascore/core/attrs.py
  • dascore/core/coordmanager.py
  • dascore/core/coords.py
  • dascore/core/spool.py
  • dascore/io/index/backend.py
  • dascore/io/index/indexer.py
  • dascore/io/index/planned.py
  • dascore/io/segy/core.py
  • dascore/io/sintela/utils.py
  • dascore/io/tdms/utils.py
  • dascore/io/wav/core.py
  • dascore/proc/coords.py
  • dascore/proc/filter.py
  • dascore/proc/mute.py
  • dascore/transform/fbe.py
  • dascore/transform/fourier.py
  • dascore/units.py
  • dascore/utils/chunk_plan.py
  • dascore/utils/jit.py
  • dascore/utils/mapping.py
  • dascore/utils/misc.py
  • dascore/utils/models.py
  • dascore/utils/moving.py
  • dascore/utils/patch.py
  • dascore/utils/patch_assembly.py
  • dascore/utils/paths.py
  • dascore/utils/pd.py
  • dascore/utils/progress.py
  • pyproject.toml

Comment thread dascore/utils/models.py
Comment on lines 39 to 41
ArrayLike = Annotated[
object,
np.ndarray,
PlainValidator(array),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Keep ArrayLike compatible with preserved array-like values.

Line [40] declares ArrayLike as np.ndarray, but dascore.compat.array() preserves foreign array-like objects accepted by is_array_like(); see dascore/compat.py, lines [53-95]. BaseCoord.data and related coordinate paths can therefore contain values that are not np.ndarray.

This annotation can make valid duck-typed inputs fail static checks. It can also let ty assume NumPy-only operations without a required conversion.

Use a structural protocol matching the supported array-like contract. If foreign array-likes are no longer supported, change array() and its documentation instead.

Suggested direction
 ArrayLike = Annotated[
-    np.ndarray,
+    ArrayLikeProtocol,
     PlainValidator(array),
 ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dascore/utils/models.py` around lines 39 - 41, Update the ArrayLike
annotation and its validator typing to use a structural protocol representing
the duck-typed contract accepted by compat.array(), rather than np.ndarray.
Ensure BaseCoord.data and related coordinate paths retain foreign array-like
values while exposing only operations guaranteed by that protocol, and keep
array() behavior unchanged.

Comment thread dascore/utils/progress.py
@codecov

codecov Bot commented Aug 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (7394563) to head (4082215).

Additional details and impacted files
@@            Coverage Diff            @@
##               dev      #796   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          164       164           
  Lines        17700     17717   +17     
=========================================
+ Hits         17700     17717   +17     
Flag Coverage Δ
network 48.19% <50.66%> (-0.01%) ⬇️
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f131af65bf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread dascore/utils/models.py

ArrayLike = Annotated[
object,
np.ndarray,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Model preserved array-likes instead of ndarray

When a patch is constructed with a non-NumPy object exposing shape, dtype, and an array protocol (for example a lazy or Array API object), PlainValidator(array) deliberately returns that object unchanged because compat.array() preserves array-likes. Declaring the result as np.ndarray therefore makes Patch.data, coordinates, and get_array() statically promise ndarray-only members that may not exist at runtime, allowing the newly enabled checker to approve code that then fails for supported array-like inputs. Use a protocol or union that reflects the objects the validator actually preserves.

Useful? React with 👍 / 👎.

Comment thread dascore/utils/progress.py

def track(
sequence: Sized | Generator,
sequence: Iterable,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle unsized iterables admitted by the new annotation

For an unsized iterable newly admitted by this annotation, such as iter([1, 2]) or map(...), and with length omitted, len(sequence) raises inside the suppressed block and leaves length as None; the subsequent length < min_length then raises TypeError before anything is yielded. Either default length to zero when length detection fails or keep the parameter type restricted to inputs the implementation can handle.

Useful? React with 👍 / 👎.

Comment thread dascore/core/coords.py
@abc.abstractmethod
def __getitem__(self, item) -> Self:
"""Should implement slicing and return new instance."""
def __getitem__(self, item):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Retain a return type for coordinate indexing

Replacing the inaccurate Self annotation by deleting the return type makes calls through the public BaseCoord interface resolve to an unknown/untyped result, so the newly enabled checker cannot validate downstream scalar-versus-coordinate usage at all. The method's documented behavior already identifies the required distinction, so annotate it with an appropriate coordinate-or-scalar union rather than removing the hint. .agents/agents.mdL75-L80

Useful? React with 👍 / 👎.

- track() no longer raises TypeError for unsized iterables passed without
  an explicit length; it now just skips the progress bar (with test).
- BaseCoord.__getitem__ regains typed signatures via overloads: int
  indices yield a value, slice/array indices a coord.
- Comment on ArrayLike records why ndarray is its static face even
  though the validator can preserve duck array-likes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
dascore/core/coords.py (1)

1148-1158: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Route every invalid reducer to ParameterError.

_AGG_FUNCS.get(dim_reduce) requires a hashable key. A list or dictionary raises TypeError. A NumPy array can fail earlier during dim_reduce == "empty" with an ambiguous-truth-value ValueError. These inputs bypass the existing ParameterError path. Check named reducers only for string values, then route all other invalid non-callables to ParameterError.

Proposed fix
-        if dim_reduce == "empty":
+        if isinstance(dim_reduce, str) and dim_reduce == "empty":
             if len(self) == 1:
                 return self
             new_coord = get_coord(shape=(1,), units=self.units, dtype=self.dtype)
-        elif dim_reduce == "squeeze":
+        elif isinstance(dim_reduce, str) and dim_reduce == "squeeze":
             return None
         else:
-            func = dim_reduce if callable(dim_reduce) else _AGG_FUNCS.get(dim_reduce)
+            func = (
+                dim_reduce
+                if callable(dim_reduce)
+                else _AGG_FUNCS.get(dim_reduce)
+                if isinstance(dim_reduce, str)
+                else None
+            )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dascore/core/coords.py` around lines 1148 - 1158, Update the reducer
selection around dim_reduce, dim_reduce == "empty", and _AGG_FUNCS.get so named
reducer checks occur only for string values; preserve callable reducers, and
route every other non-callable value—including unhashable or array-like
inputs—to ParameterError without allowing TypeError or ambiguous-truth-value
errors to escape.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@dascore/core/coords.py`:
- Around line 1148-1158: Update the reducer selection around dim_reduce,
dim_reduce == "empty", and _AGG_FUNCS.get so named reducer checks occur only for
string values; preserve callable reducers, and route every other non-callable
value—including unhashable or array-like inputs—to ParameterError without
allowing TypeError or ambiguous-truth-value errors to escape.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e0531d74-1eb3-448f-98ce-54f3d42adcd9

📥 Commits

Reviewing files that changed from the base of the PR and between 2223be6 and 4082215.

📒 Files selected for processing (4)
  • dascore/core/coords.py
  • dascore/utils/models.py
  • dascore/utils/progress.py
  • tests/test_utils/test_progress.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • dascore/utils/models.py
  • dascore/utils/progress.py

@d-chambers
d-chambers merged commit 6a23ffc into dev Aug 1, 2026
27 checks passed
@d-chambers
d-chambers deleted the ty-burn-1 branch August 1, 2026 08:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI continuous integration patch related to Patch class

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant