feat: attrs() reports every attribute; unsigned arrays stay unsigned
attrs() silently omitted any attribute whose datatype had no AttrValue variant
— including every Python bool, which h5py stores as an enum — plus complex,
compound and reference attributes, and cast unsigned 64-bit arrays to
I64Array so values above i64::MAX came back negative.
- numpy/h5py-style booleans (an enum of exactly FALSE=0 / TRUE=1 over an
integer base) decode as I64 / I64Array of 0/1.
- AttrValue::U64Array keeps unsigned arrays unsigned. Behaviour change: an
unsigned array attribute no longer arrives as I64Array; the netCDF-4 CF
helpers (_FillValue, valid_range) and the Python bindings handle it.
- AttrValue::Raw { datatype, shape, data } carries any other attribute
verbatim (also used when a value fails to decode as its declared type), so
the attribute list is always complete. Decodable with data_read against the
datatype; Python receives {"dtype", "shape", "data"}.
- Both new variants are writable, so attributes round-trip between files.
h5py interop tests cover reading 13 attribute kinds and h5py reading back a
compound and a u64 attribute written by clawhdf5.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
5dd95a6cf8
commit
97ab658c11
@@ -279,6 +279,43 @@ pub(crate) fn build_attr_message(name: &str, value: &AttrValue) -> AttributeMess
|
||||
dataspace: scalar_ds(),
|
||||
raw_data: v.to_le_bytes().to_vec(),
|
||||
},
|
||||
AttrValue::U64Array(arr) => {
|
||||
let mut raw = Vec::with_capacity(arr.len() * 8);
|
||||
for v in arr {
|
||||
raw.extend_from_slice(&v.to_le_bytes());
|
||||
}
|
||||
AttributeMessage {
|
||||
name: name.to_string(),
|
||||
datatype: Datatype::FixedPoint {
|
||||
size: 8,
|
||||
byte_order: DatatypeByteOrder::LittleEndian,
|
||||
signed: false,
|
||||
bit_offset: 0,
|
||||
bit_precision: 64,
|
||||
},
|
||||
dataspace: simple_1d(arr.len() as u64),
|
||||
raw_data: raw,
|
||||
}
|
||||
}
|
||||
AttrValue::Raw {
|
||||
datatype,
|
||||
shape,
|
||||
data,
|
||||
} => AttributeMessage {
|
||||
name: name.to_string(),
|
||||
datatype: datatype.clone(),
|
||||
dataspace: if shape.is_empty() {
|
||||
scalar_ds()
|
||||
} else {
|
||||
Dataspace {
|
||||
space_type: DataspaceType::Simple,
|
||||
rank: shape.len() as u8,
|
||||
dimensions: shape.clone(),
|
||||
max_dimensions: None,
|
||||
}
|
||||
},
|
||||
raw_data: data.clone(),
|
||||
},
|
||||
AttrValue::String(s) => {
|
||||
let bytes = s.as_bytes();
|
||||
AttributeMessage {
|
||||
@@ -334,7 +371,7 @@ pub(crate) fn simple_1d(n: u64) -> Dataspace {
|
||||
|
||||
// ---- Attribute values ----
|
||||
|
||||
/// Convenient attribute values for the write API.
|
||||
/// Attribute values, for both the write API and what reading returns.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AttrValue {
|
||||
F64(f64),
|
||||
@@ -342,8 +379,21 @@ pub enum AttrValue {
|
||||
I64(i64),
|
||||
I64Array(Vec<i64>),
|
||||
U64(u64),
|
||||
/// Unsigned integers, kept unsigned so values above `i64::MAX` survive.
|
||||
U64Array(Vec<u64>),
|
||||
String(String),
|
||||
StringArray(Vec<String>),
|
||||
/// An attribute whose datatype has no dedicated variant above (compound,
|
||||
/// general enum, complex, reference, opaque, array, ...), carried verbatim
|
||||
/// so it is never silently lost: the datatype, the dataspace dimensions
|
||||
/// (empty for a scalar) and the element bytes exactly as stored. Decode
|
||||
/// `data` with `clawhdf5_format::data_read` (e.g. `read_compound_fields`)
|
||||
/// against `datatype`. Writing a `Raw` value stores it back unchanged.
|
||||
Raw {
|
||||
datatype: Datatype,
|
||||
shape: Vec<u64>,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
// ---- Dataset builder ----
|
||||
|
||||
Reference in New Issue
Block a user