Skip to content

feat(extension_types): add numpy.ndarray as a native value type (ITL-460)#201

Merged
eywalker merged 10 commits into
mainfrom
eywalker/itl-460-support-numpyndarray-as-a-native-value-type
Jul 1, 2026
Merged

feat(extension_types): add numpy.ndarray as a native value type (ITL-460)#201
eywalker merged 10 commits into
mainfrom
eywalker/itl-460-support-numpyndarray-as-a-native-value-type

Conversation

@kurodo3

@kurodo3 kurodo3 Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add LogicalNumpyArray — maps numpy.ndarray ↔ Arrow large_binary using numpy's .npy binary format, preserving dtype (including structured/record field names), shape, byte order, and data for 1-D, 2-D, N-D, and structured arrays
  • Add NumpyArrayHandler — content hashes arrays via the same .npy bytes stored in Arrow, ensuring hash/storage consistency; registered in both v0.1.json and register_builtin_python_type_handlers
  • Object-dtype arrays are rejected eagerly (dtype.kind == "O" guard fires before np.save; allow_pickle=False in np.save is a secondary guard for structured dtypes with object sub-fields)

Test plan

  • 17 unit tests for LogicalNumpyArray (protocol conformance, storage guards, round-trips: 1-D float64, 2-D int32, 3-D uint8, zero-size, single-element, structured record array, F-order)
  • 7 unit tests for NumpyArrayHandler (bytes return, same content → same bytes, dtype/shape sensitivity, structured field name sensitivity, TypeError/ValueError guards)
  • Integration round-trip test in test_roundtrips.py for both Parquet and Delta Lake backends, covering simple 1-D and structured (record) arrays
  • Full test suite: 3918 passed, 56 skipped, 6 xfailed — zero regressions

Linear

Fixes ITL-460

kurodo3 Bot and others added 9 commits June 30, 2026 23:23
…numpy.ndarray native value type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements LogicalNumpyArray, a new logical type that maps numpy.ndarray
to Arrow large_binary using numpy's .npy format as the storage envelope.
Includes unit tests covering protocol conformance, serialization guards,
and round-trip correctness for 1-D/2-D/N-D/structured/Fortran arrays.
…gicalNumpyArray (ITL-460)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…efault context (ITL-460)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…(ITL-460)

Verifies that numpy.ndarray values (both simple 1-D float64 and structured
record arrays) round-trip correctly through parquet and Delta Lake storage
with the expected Arrow extension name "numpy.ndarray".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…L-460)

Add NumpyArrayHandler to register_builtin_python_type_handlers so the
programmatic API is consistent with the v0.1.json-configured path.
Also update module docstring and v0.1.json changelog.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copilot AI 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.

Pull request overview

Adds first-class support for numpy.ndarray as an OrcaPod native value type by introducing an Arrow extension type backed by NumPy’s .npy format, plus a semantic-hash handler to keep hashing consistent with storage.

Changes:

  • Introduce LogicalNumpyArray to serialize/deserialize numpy.ndarray via .npy bytes stored in Arrow large_binary.
  • Add NumpyArrayHandler and register it (code + default context) to semantic-hash arrays using the same .npy representation.
  • Add comprehensive unit + integration tests and make NumPy an explicit dependency.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
uv.lock Locks NumPy as a dependency for the environment.
pyproject.toml Adds numpy>=1.26.4 as an explicit project dependency.
src/orcapod/extension_types/numpy_type.py New logical type mapping numpy.ndarray ↔ Arrow large_binary via .npy.
src/orcapod/hashing/semantic_hashing/builtin_handlers.py Adds/registers NumpyArrayHandler for array semantic hashing.
src/orcapod/extension_types/init.py Re-exports LogicalNumpyArray.
src/orcapod/contexts/data/v0.1.json Registers the new logical type + hash handler in the default context.
tests/test_hashing/test_numpy_handler.py New unit tests for NumpyArrayHandler.
tests/test_extension_types/test_roundtrips.py Adds integration round-trip coverage for numpy.ndarray.
tests/test_extension_types/test_numpy_type.py New unit tests for LogicalNumpyArray.
superpowers/specs/2026-06-30-itl-460-numpy-ndarray-native-value-type-design.md Design spec documenting the approach and constraints.
superpowers/plans/2026-07-01-itl-460-numpy-ndarray-native-value-type.md Implementation plan/checklist for ITL-460.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/test_extension_types/test_numpy_type.py
Comment on lines +235 to +276
class NumpyArrayHandler:
"""Hasher for ``numpy.ndarray`` — content hash via numpy's ``.npy`` binary format.

Returns the raw ``.npy`` bytes produced by ``np.save``, which encode dtype
(including structured/record field names), shape, byte order, and data in
numpy's stable on-disk format. This is identical to what ``LogicalNumpyArray``
stores in Arrow, so the hash input and storage bytes are always consistent.

Object-dtype arrays are rejected with ``ValueError`` immediately (before
``np.save`` is called). Structured dtypes containing object-typed fields
are caught by ``allow_pickle=False`` in ``np.save``.
"""

def handle(self, obj: Any, hasher: "SemanticHasherProtocol") -> bytes:
"""Return the ``.npy`` bytes for ``obj``.

Args:
obj: A ``numpy.ndarray`` instance.
hasher: Ignored. Present for protocol conformance.

Returns:
Raw ``.npy`` bytes encoding dtype, shape, byte order, and data.

Raises:
TypeError: If ``obj`` is not a ``numpy.ndarray``.
ValueError: If ``obj`` has ``dtype.kind == "O"`` (object dtype).
"""
import io
import numpy as np
if not isinstance(obj, np.ndarray):
raise TypeError(
f"NumpyArrayHandler: expected numpy.ndarray, got {type(obj)!r}"
)
if obj.dtype.kind == "O":
raise ValueError(
f"NumpyArrayHandler does not support object-dtype arrays "
f"(dtype={obj.dtype!r}). Object arrays require pickling, "
"which is disabled for security."
)
buf = io.BytesIO()
np.save(buf, obj, allow_pickle=False)
return buf.getvalue()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit c75562b. NumpyArrayHandler.handle() now returns ContentHash("sha256", hashlib.sha256(npy_bytes).digest()) instead of the raw .npy bytes.

This matches the FileHandler/DirectoryHandler pattern and short-circuits the semantic hasher at its isinstance(obj, ContentHash) terminal check (semantic_hasher.py line 165), bypassing the BytesHandler hex-expansion + JSON-serialisation path entirely. For a 100 MB neural data array this avoids the ~400 MB of temporary allocations that the old path would require.

Tests in test_numpy_handler.py updated: test_returns_bytestest_returns_content_hash, with assertions checking isinstance(result, ContentHash) and result.method == "sha256". The equality/inequality comparisons continue to work because ContentHash is a frozen dataclass with __eq__.

…nsion overhead

- Remove unused `import io` from test_numpy_type.py
- Change NumpyArrayHandler.handle() to return ContentHash("sha256", sha256_digest)
  instead of raw .npy bytes, avoiding the BytesHandler hex-expansion + JSON
  serialisation path for large arrays. This matches the FileHandler/DirectoryHandler
  pattern and short-circuits the semantic hasher at the ContentHash terminal check.
- Update test_numpy_handler.py: test_returns_bytes → test_returns_content_hash,
  equality/inequality assertions updated to compare ContentHash objects

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kurodo3

kurodo3 Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Review round 2 — changes made (commit c75562b)

Two comments from Copilot addressed:

1. Unused import io in test_numpy_type.py
Removed the top-level import io from tests/test_extension_types/test_numpy_type.py. The import was never referenced in the test file itself — io.BytesIO is used internally by the module under test, not directly by the tests.

2. NumpyArrayHandler returning raw .npy bytes → now returns ContentHash
Changed NumpyArrayHandler.handle() to compute ContentHash("sha256", hashlib.sha256(npy_bytes).digest()) and return it directly, rather than returning the raw .npy bytes.

The semantic hasher (SemanticAwarePythonHasher.hash_object) checks isinstance(obj, ContentHash) first and short-circuits if true. Previously, returning raw bytes caused the hasher to route through BytesHandler (hex expansion, 2× memory) → JSON serialisation → SHA-256. For large neural data arrays this was wasteful. The new path matches how FileHandler and DirectoryHandler work and avoids all intermediate expansion.

test_numpy_handler.py updated: test_returns_bytes renamed to test_returns_content_hash with assertions on isinstance(result, ContentHash) and result.method == "sha256". Equality/inequality tests continue to work because ContentHash is a frozen dataclass.

All 39 numpy-related tests pass after these changes.

@eywalker eywalker merged commit 45c9147 into main Jul 1, 2026
11 checks passed
@eywalker eywalker deleted the eywalker/itl-460-support-numpyndarray-as-a-native-value-type branch July 1, 2026 08:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants