Conversation
ZDType._check_native_dtype compared the dtype class with
type(dtype) is cls.dtype_cls
which is too strict for NumPy's C type-name spellings. np.dtype("q")
is a np.dtypes.LongLongDType instance, not a np.dtypes.Int64DType
instance, even though "long long" is 64-bit on the platform NumPy was
built for, so a plain 64-bit signed integer was rejected:
>>> zarr.create_array(store={}, shape=(3,), dtype="q")
ValueError: No Zarr data type found that matches dtype 'dtype(int64)'
np.dtype("<i8") and np.dtype("l") resolved fine, so two spellings of the
same layout disagreed.
Keep the exact type check as the fast path, and fall back to comparing the
layout (kind and item size), which is what a Zarr data type actually
encodes. Flexible/parametric dtypes such as VoidDType have no fixed layout
to compare against, so they keep the exact type check only.
np.dtype("Q") is fixed by the same change.
Fixes zarr-developers#3282
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4416 +/- ##
=======================================
Coverage 94.37% 94.37%
=======================================
Files 93 93
Lines 13171 13181 +10
=======================================
+ Hits 12430 12440 +10
Misses 741 741
🚀 New features to boost your workflow:
|
|
i'm not really convinced we need to do this. the whole idea of zarr is to save data in a platform-agnostic way. good for numpy that they have many ways of saying the same thing, but I think it's OK to not copy that. |
|
in fact I wonder if we should actually reject numpy data types that are platform-dependent |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the review, @d-v-b. One clarification that might change the picture: this doesn't make Zarr copy NumPy's aliasing into what it stores. The data type that ends up in the metadata is still the platform-agnostic Today the behaviour is already inconsistent. On the same machine, On rejecting platform-dependent dtypes: that would be the stricter and more explicit option, but it's a behaviour change. (I also pushed a fix for the mypy failure in Lint.) |
Documentation build overview
10 files changed ·
|
this is the concerning part. if I understand correctly, with these data types, the same python script that writes data from numpy arrays will produce different zarr data, on different platforms. I think we should avoid this, and only accept numpy data types that are platform-independent. as you note this would require a behavioral change, because we do accept some of these platform-sensitive data types today. we would need to implement this change through a deprecation cycle, which I'm open to. cc @zarr-developers/python-core-devs, thoughts on having this library adhere to the following principle: when coercing numpy data types to zarr data types, we reject ambiguous numpy data types that have different widths on different platforms, and encourage users to use a platform-independent numpy data type instead. |
I agree |
Fixes #3282.
ZDType._check_native_dtypeinsrc/zarr/core/dtype/wrapper.pytested the dtype class withThat is too strict for NumPy's C type-name spellings.
np.dtype("q")is anp.dtypes.LongLongDTypeinstance, not anp.dtypes.Int64DTypeinstance, even thoughlong longis 64-bit on the platform NumPy was built for. The result is that a plain 64-bit signed integer is rejected:while two other spellings of the identical layout work fine:
The width of
long longdepends on the build platform, so the dtype class on its own is not a reliable test. What a Zarr data type actually encodes is the layout.Change
Keep the exact type check as the fast path, and fall back to comparing
kindanditemsize:np.dtype("Q")(ULongLongDTypevsUInt64DType) is fixed by the same change, as isnp.dtype("l")on a platform wherelongis 32-bit.The
except TypeErrorbranch matters:VoidDType,DateTime64DTypeandTimeDelta64DTypecannot be instantiated (Preliminary-API: Flexible/Parametric legacy DType), andDateTime64relies on the base_check_native_dtype. Without that guard those classes would start raisingTypeErrorwhere they previously returnedFalse.Structuredoverrides_check_native_dtypeoutright and is unaffected.Why this is safe
The registry requires exactly one wrapper to match a dtype, so a looser test could in principle create ambiguity. Comparing on
(kind, itemsize)cannot, because that pair is unique across every registered wrapper:Verification
I diffed the complete resolution behaviour before and after the patch, over every C spelling, every explicit byte-order/width spelling,
float128/complex256, string, bytes, void, object and datetime dtypes, structured dtypes, and every registered wrapper asked about every one of those dtypes (2065 recorded results). The only differences are the two intended ones:Everything else is byte-identical, including:
float128andcomplex256still resolve to nothing (no Zarr data type has that layout), so they still raise rather than silently becomingFloat64/Complex128.object, fixed-width bytes, null-terminated bytes, structured and datetime dtypes are unchanged.qandQstill matches exactly one wrapper, so the "multiple wrappers match" error cannot trigger.Test runs:
pytest tests/test_dtype/ tests/test_dtype_registry.py— 1948 passed, 8 skippedruff checkandruff format --checkon the three changed files — cleanThe new coverage fails on
mainand passes with the patch:tests/test_dtype/test_npy/test_int.py:np.dtype("q")andnp.dtype("Q")added to theTestInt64/TestUInt64valid_dtypetuples, which the sharedBaseTestZDTypedrives through_check_native_dtypeandfrom_native_dtype.tests/test_dtype_registry.py: a newtest_match_dtype_c_type_spellingasserting that both spellings resolve to exactly one data type throughmatch_dtypeandparse_dtypefor Zarr formats 2 and 3.