Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 5 additions & 26 deletions src/uproot/writing/_cascadentuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ def __init__(self, location, header_checksum):
super().__init__(location, None)

def __repr__(self):
return f"{type(self).__name__}(extension_field_record_frames = {self.extension_field_record_frames}, column_group_record_frames = {self.extension_column_record_frames}, cluster_summary_record_frames={self.extension_column_record_frames}, cluster_group_record_frames{self.cluster_group_record_frames}, metadata_block_envelope_link = {self.metadata_block_envelope_links})"
return f"{type(self).__name__}(extension_field_record_frames={self.extension_field_record_frames}, extension_column_record_frames={self.extension_column_record_frames}, cluster_group_record_frames={self.cluster_group_record_frames})"

def serialize(self):
out = []
Expand Down Expand Up @@ -756,19 +756,6 @@ def serialize(self):
return out


class Ntuple_PageLink:
def __init__(self, num_bytes, offset):
self.num_bytes = num_bytes
self.offset = offset

def serialize(self):
outbytes = _rntuple_locator_size_format.pack(self.num_bytes, self.offset)
return outbytes

def __repr__(self):
return f"{type(self).__name__}({self.num_bytes}, {self.offset})"


class NTuple(CascadeNode):
"""
Writes a RNTuple, including all fields, columns, and (upon ``extend``) pages.
Expand Down Expand Up @@ -827,18 +814,10 @@ def name(self):
def title(self):
return self._key.title

@property
def branch_types(self):
return self._branch_types

@property
def freesegments(self):
return self._freesegments

@property
def field_name(self):
return self._field_name

@property
def location(self):
return self._key.location
Expand Down Expand Up @@ -956,7 +935,10 @@ def extend(self, file, sink, data):

old_footer_key = self._footer_key
self._freesegments.release(
old_footer_key.location, old_footer_key.location + old_footer_key.allocation
old_footer_key.location,
old_footer_key.location
+ old_footer_key.num_bytes
+ old_footer_key.compressed_bytes,
)
footer_raw_data = self._footer.serialize()
self._footer_key = self.add_rblob(sink, footer_raw_data, len(footer_raw_data))
Expand Down Expand Up @@ -1042,9 +1024,6 @@ def write(self, sink):

self._freesegments.write(sink)

def write_updates(self, sink):
sink.flush()


def _to_packed_form(form):
match form.__class__:
Expand Down
4 changes: 0 additions & 4 deletions src/uproot/writing/_cascadetree.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,10 +414,6 @@ def name(self):
def title(self):
return self._key.title

@property
def branch_types(self):
return self._branch_types

@property
def freesegments(self):
return self._freesegments
Expand Down
76 changes: 15 additions & 61 deletions src/uproot/writing/writable.py
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,14 @@ def get_chunk(start, stop):

def _del(self, name, cycle):
key = self._cascading.data.get_key(name, cycle)
if key is None:
raise uproot.exceptions.KeyInFileError(
name,
cycle="any" if cycle is None else cycle,
keys=self._cascading.data.key_names,
file_path=self.file_path,
object_path=self.object_path,
)
start = key.seek_location
stop = start + key.num_bytes + key.compressed_bytes
self._cascading.freesegments.release(start, stop)
Expand Down Expand Up @@ -2127,72 +2135,18 @@ def __exit__(self, exception_type, exception_value, traceback):
def compression(self):
"""
Compression algorithm and level (:doc:`uproot.compression.Compression` or None)
for new blobs added to the RNTuple.

This property can be changed and doesn't have to be the same as the compression
of the file, which allows you to write different objects with different
compression settings.

The following are equivalent:
used when writing pages to this RNTuple.

.. code-block:: python

my_directory["tree"]["branch1"].compression = uproot.ZLIB(1)
my_directory["tree"]["branch2"].compression = uproot.LZMA(9)

and

.. code-block:: python

my_directory["tree"].compression = {"branch1": uproot.ZLIB(1),
"branch2": uproot.LZMA(9)}
RNTuple compression is file-wide and is taken from the file header; individual
per-column compression is not yet supported.
"""
out = {}
last = None
for datum in self._cascading._branch_data:
if datum["kind"] != "record":
last = out[datum["fName"]] = datum["compression"]
if all(x == last for x in out.values()):
return last
else:
return out
return self._cascading._freesegments.fileheader.compression

@compression.setter
def compression(self, value):
if value is None or isinstance(value, uproot.compression.Compression):
for datum in self._cascading._branch_data:
if datum["kind"] != "record":
datum["compression"] = value

elif (
isinstance(value, Mapping)
and all(
isinstance(k, str)
and (v is None or isinstance(v, uproot.compression.Compression))
for k, v in value.items()
)
and all(
datum["fName"] in value
for datum in self._cascading._branch_data
if datum["kind"] != "record"
)
and len(value)
== len(
[
datum
for datum in self._cascading._branch_data
if datum["kind"] != "record"
]
)
):
for datum in self._cascading._branch_data:
if datum["kind"] != "record":
datum["compression"] = value[datum["fName"]]

else:
raise TypeError(
"compression must be None, a uproot.compression.Compression object, like uproot.ZLIB(4) or uproot.ZSTD(0), or a mapping of branch names to such objects"
)
raise NotImplementedError(
"per-RNTuple compression is not yet supported; set compression on the file instead"
)

@property
def num_entries(self) -> int:
Expand Down
25 changes: 25 additions & 0 deletions tests/test_1650_rntuple_writing_bugfixes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# BSD 3-Clause License; see https://github.com/scikit-hep/uproot5/blob/main/LICENSE

from __future__ import annotations

import numpy as np
import pytest

import uproot


def test_writable_directory_delitem_nonexistent_raises_key_error(tmp_path):
"""
Deleting a nonexistent key from a WritableDirectory must raise a KeyError
subclass (uproot.exceptions.KeyInFileError), not AttributeError.

Before the fix, _del() called key.seek_location without checking whether
get_key() returned None, resulting in:
AttributeError: 'NoneType' object has no attribute 'seek_location'
"""
fname = tmp_path / "del_nonexistent.root"
f = uproot.recreate(fname)
f["t"] = {"x": np.array([1, 2, 3], dtype="i4")}
with pytest.raises(KeyError):
del f["nonexistent_key"]
f.close()
Loading