From 47795e9a6c7ceff6e146655bfdd7469f7274f0fc Mon Sep 17 00:00:00 2001
From: "allcontributors[bot]"
<46447321+allcontributors[bot]@users.noreply.github.com>
Date: Wed, 18 Oct 2023 09:15:08 -0500
Subject: [PATCH 1/5] docs: add GaetanLepage as a contributor for test (#995)
* docs: update README.md [skip ci]
* docs: update .all-contributorsrc [skip ci]
---------
Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
---
.all-contributorsrc | 9 +++++++++
README.md | 1 +
2 files changed, 10 insertions(+)
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 7ffdff5e1..a5ed074fd 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -506,6 +506,15 @@
"contributions": [
"doc"
]
+ },
+ {
+ "login": "GaetanLepage",
+ "name": "Gaétan Lepage",
+ "avatar_url": "https://avatars.githubusercontent.com/u/33058747?v=4",
+ "profile": "http://glepage.com",
+ "contributions": [
+ "test"
+ ]
}
],
"contributorsPerLine": 7,
diff --git a/README.md b/README.md
index 82e07b561..d58bb564b 100644
--- a/README.md
+++ b/README.md
@@ -178,6 +178,7 @@ Thanks especially to the gracious help of Uproot contributors (including the [or
ioanaif 💻 |
OTABI Tomoya ⚠️ |
Jost Migenda 📖 |
+ Gaétan Lepage ⚠️ |
From 2bc23dcda5e0a396894392dadd4161d2245c62ed Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ga=C3=A9tan=20Lepage?=
<33058747+GaetanLepage@users.noreply.github.com>
Date: Wed, 18 Oct 2023 16:20:36 +0200
Subject: [PATCH 2/5] fix: make hist import optional in test_0965 (#994)
---
tests/test_0965-inverted-axes-variances-hist-888.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/test_0965-inverted-axes-variances-hist-888.py b/tests/test_0965-inverted-axes-variances-hist-888.py
index cbc3fd8ab..5dd33dad0 100644
--- a/tests/test_0965-inverted-axes-variances-hist-888.py
+++ b/tests/test_0965-inverted-axes-variances-hist-888.py
@@ -2,10 +2,10 @@
import pytest
import numpy
-import hist
import uproot
import skhep_testdata
+pytest.importorskip("hist")
ROOT = pytest.importorskip("ROOT")
From 2f998151cdf92e99bcbaf0f0b26d66bc6d00b6b1 Mon Sep 17 00:00:00 2001
From: Luis Antonio Obis Aparicio <35803280+lobis@users.noreply.github.com>
Date: Wed, 18 Oct 2023 13:16:57 -0500
Subject: [PATCH 3/5] chore: add types to most of the `uproot.source` module
(#996)
* typing
* annotations
* annotations on chunk
* annotations on cursor
* add annotations to sources
* Apply suggestions from code review
Co-authored-by: Jim Pivarski
* style: pre-commit fixes
* type file paths as str
* use | instead of "or" for typing annotations
---------
Co-authored-by: Jim Pivarski
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
src/uproot/source/chunk.py | 46 ++++++++++------
src/uproot/source/cursor.py | 104 ++++++++++++++++++++++++++---------
src/uproot/source/file.py | 14 ++---
src/uproot/source/fsspec.py | 6 +-
src/uproot/source/futures.py | 14 +++--
src/uproot/source/http.py | 51 +++++++++++------
src/uproot/source/object.py | 5 +-
src/uproot/source/s3.py | 4 +-
src/uproot/source/xrootd.py | 30 +++++-----
9 files changed, 180 insertions(+), 94 deletions(-)
diff --git a/src/uproot/source/chunk.py b/src/uproot/source/chunk.py
index 85905d8d4..9a268f63a 100644
--- a/src/uproot/source/chunk.py
+++ b/src/uproot/source/chunk.py
@@ -31,7 +31,7 @@ class Resource:
"""
@property
- def file_path(self):
+ def file_path(self) -> str:
"""
A path to the file (or URL).
"""
@@ -49,7 +49,7 @@ class Source:
the file.
"""
- def chunk(self, start, stop) -> Chunk:
+ def chunk(self, start: int, stop: int) -> Chunk:
"""
Args:
start (int): Seek position of the first byte to include.
@@ -60,7 +60,9 @@ def chunk(self, start, stop) -> Chunk:
:doc:`uproot.source.chunk.Chunk`.
"""
- def chunks(self, ranges, notifications: queue.Queue) -> list[Chunk]:
+ def chunks(
+ self, ranges: list[(int, int)], notifications: queue.Queue
+ ) -> list[Chunk]:
"""
Args:
ranges (list of (int, int) 2-tuples): Intervals to fetch
@@ -90,7 +92,7 @@ def chunks(self, ranges, notifications: queue.Queue) -> list[Chunk]:
"""
@property
- def file_path(self):
+ def file_path(self) -> str:
"""
A path to the file (or URL).
"""
@@ -154,7 +156,7 @@ def __repr__(self):
type(self).__name__, path, self.num_workers, id(self)
)
- def chunk(self, start, stop) -> Chunk:
+ def chunk(self, start: int, stop: int) -> Chunk:
self._num_requests += 1
self._num_requested_chunks += 1
self._num_requested_bytes += stop - start
@@ -164,7 +166,9 @@ def chunk(self, start, stop) -> Chunk:
self._executor.submit(future)
return chunk
- def chunks(self, ranges, notifications: queue.Queue) -> list[Chunk]:
+ def chunks(
+ self, ranges: list[(int, int)], notifications: queue.Queue
+ ) -> list[Chunk]:
self._num_requests += 1
self._num_requested_chunks += len(ranges)
self._num_requested_bytes += sum(stop - start for start, stop in ranges)
@@ -186,7 +190,7 @@ def executor(self):
return self._executor
@property
- def num_workers(self):
+ def num_workers(self) -> int:
"""
The number of :doc:`uproot.source.futures.ResourceWorker` threads in
the :doc:`uproot.source.futures.ResourceThreadPoolExecutor`.
@@ -240,7 +244,7 @@ class Chunk:
_dtype = numpy.dtype(numpy.uint8)
@classmethod
- def wrap(cls, source, data, start=0):
+ def wrap(cls, source: Source, data: numpy.ndarray | bytes, start: int = 0):
"""
Args:
source (:doc:`uproot.source.chunk.Source`): Source to attach to
@@ -255,7 +259,9 @@ def wrap(cls, source, data, start=0):
future = uproot.source.futures.TrivialFuture(data)
return Chunk(source, start, start + len(data), future)
- def __init__(self, source, start, stop, future, is_memmap=False):
+ def __init__(
+ self, source: Source, start: int, stop: int, future, is_memmap: bool = False
+ ):
self._source = source
self._start = start
self._stop = stop
@@ -267,21 +273,21 @@ def __repr__(self):
return f""
@property
- def source(self):
+ def source(self) -> Source:
"""
Source from which this Chunk is derived.
"""
return self._source
@property
- def start(self):
+ def start(self) -> int:
"""
Seek position of the first byte to include.
"""
return self._start
@property
- def stop(self):
+ def stop(self) -> int:
"""
Seek position of the first byte to exclude (one greater than the last
byte to include).
@@ -297,7 +303,7 @@ def future(self):
return self._future
@property
- def is_memmap(self):
+ def is_memmap(self) -> bool:
"""
If True, the `raw_data` is or will be a view into a memmap file, which
must be handled carefully. Accessing that data after the file is closed
@@ -326,7 +332,7 @@ def detach_memmap(self):
else:
return self
- def __contains__(self, range):
+ def __contains__(self, range: tuple[int, int]):
start, stop = range
if isinstance(start, uproot.source.cursor.Cursor):
start = start.index
@@ -334,7 +340,7 @@ def __contains__(self, range):
stop = stop.index
return self._start <= start and stop <= self._stop
- def wait(self, insist=True):
+ def wait(self, insist: bool = True):
"""
Args:
insist (bool or int): If True, raise an OSError if ``raw_data`` does
@@ -368,7 +374,7 @@ def wait(self, insist=True):
self._future = None
@property
- def raw_data(self):
+ def raw_data(self) -> numpy.ndarray | bytes:
"""
Data from the Source as a ``numpy.ndarray`` of ``numpy.uint8``.
@@ -379,7 +385,9 @@ def raw_data(self):
self.wait()
return self._raw_data
- def get(self, start, stop, cursor, context):
+ def get(
+ self, start: int, stop: int, cursor: uproot.source.cursor.Cursor, context: dict
+ ) -> numpy.ndarray:
"""
Args:
start (int): Seek position of the first byte to include.
@@ -417,7 +425,9 @@ def get(self, start, stop, cursor, context):
self._source.file_path,
)
- def remainder(self, start, cursor, context):
+ def remainder(
+ self, start: int, cursor: uproot.source.cursor.Cursor, context: dict
+ ) -> numpy.ndarray:
"""
Args:
start (int): Seek position of the first byte to include.
diff --git a/src/uproot/source/cursor.py b/src/uproot/source/cursor.py
index fc0c4f9e0..f28386484 100644
--- a/src/uproot/source/cursor.py
+++ b/src/uproot/source/cursor.py
@@ -6,6 +6,7 @@
the lowest level of interpretation (numbers, strings, raw arrays, etc.).
"""
+from __future__ import annotations
import datetime
import struct
@@ -44,7 +45,7 @@ class Cursor:
requested by :doc:`uproot.deserialization.read_object_any`.
"""
- def __init__(self, index, origin=0, refs=None):
+ def __init__(self, index: int, origin: int = 0, refs: dict | None = None):
self._index = index
self._origin = origin
self._refs = refs
@@ -66,7 +67,7 @@ def __repr__(self):
return f"Cursor({self._index}{o}{r})"
@property
- def index(self):
+ def index(self) -> int:
"""
Global seek position in the ROOT file or local position in an
uncompressed :doc:`uproot.source.chunk.Chunk`.
@@ -74,7 +75,7 @@ def index(self):
return self._index
@property
- def origin(self):
+ def origin(self) -> int:
"""
Zero-point for numerical keys in
:ref:`uproot.source.cursor.Cursor.refs`.
@@ -82,7 +83,7 @@ def origin(self):
return self._origin
@property
- def refs(self):
+ def refs(self) -> dict:
"""
References to data already read in
:doc:`uproot.deserialization.read_object_any`.
@@ -91,7 +92,7 @@ def refs(self):
self._refs = {}
return self._refs
- def displacement(self, other=None):
+ def displacement(self, other: Cursor = None) -> int:
"""
The number of bytes between this :doc:`uproot.source.cursor.Cursor`
and its :ref:`uproot.source.cursor.Cursor.origin` (if None)
@@ -105,7 +106,7 @@ def displacement(self, other=None):
else:
return self._index - other._index
- def copy(self, link_refs=True):
+ def copy(self, link_refs: bool = True) -> Cursor:
"""
Returns a copy of this :doc:`uproot.source.cursor.Cursor`. If
``link_refs`` is True, any :ref:`uproot.source.cursor.Cursor.refs`
@@ -116,14 +117,14 @@ def copy(self, link_refs=True):
else:
return Cursor(self._index, origin=self._origin, refs=dict(self._refs))
- def move_to(self, index):
+ def move_to(self, index: int):
"""
Move the :ref:`uproot.source.cursor.Cursor.index` to a specified seek
position.
"""
self._index = index
- def skip(self, num_bytes):
+ def skip(self, num_bytes: int):
"""
Move the :ref:`uproot.source.cursor.Cursor.index` forward
``num_bytes``.
@@ -148,7 +149,7 @@ def skip_after(self, obj):
)
self._index = start_cursor.index + num_bytes
- def skip_over(self, chunk, context):
+ def skip_over(self, chunk: uproot.source.chunk.Chunk, context: dict):
"""
Args:
chunk (:doc:`uproot.source.chunk.Chunk`): Buffer of contiguous data
@@ -171,7 +172,13 @@ def skip_over(self, chunk, context):
self._index += num_bytes
return True
- def fields(self, chunk, format, context, move=True):
+ def fields(
+ self,
+ chunk: uproot.source.chunk.Chunk,
+ format: struct.Struct,
+ context: dict,
+ move: bool = True,
+ ):
"""
Args:
chunk (:doc:`uproot.source.chunk.Chunk`): Buffer of contiguous data
@@ -193,7 +200,13 @@ def fields(self, chunk, format, context, move=True):
self._index = stop
return format.unpack(chunk.get(start, stop, self, context))
- def field(self, chunk, format, context, move=True):
+ def field(
+ self,
+ chunk: uproot.source.chunk.Chunk,
+ format: struct.Struct,
+ context: dict,
+ move: bool = True,
+ ):
"""
Args:
chunk (:doc:`uproot.source.chunk.Chunk`): Buffer of contiguous data
@@ -215,7 +228,9 @@ def field(self, chunk, format, context, move=True):
self._index = stop
return format.unpack(chunk.get(start, stop, self, context))[0]
- def double32(self, chunk, context, move=True):
+ def double32(
+ self, chunk: uproot.source.chunk.Chunk, context: dict, move: bool = True
+ ) -> float:
"""
Args:
chunk (:doc:`uproot.source.chunk.Chunk`): Buffer of contiguous data
@@ -235,7 +250,13 @@ def double32(self, chunk, context, move=True):
self._index = stop
return _raw_double32.unpack(chunk.get(start, stop, self, context))[0]
- def float16(self, chunk, num_bits, context, move=True):
+ def float16(
+ self,
+ chunk: uproot.source.chunk.Chunk,
+ num_bits: int,
+ context: dict,
+ move: bool = True,
+ ) -> float:
"""
Args:
chunk (:doc:`uproot.source.chunk.Chunk`): Buffer of contiguous data
@@ -268,7 +289,7 @@ def float16(self, chunk, num_bits, context, move=True):
return out.item()
- def byte(self, chunk, context, move=True):
+ def byte(self, chunk: uproot.source.chunk.Chunk, context: dict, move: bool = True):
"""
Args:
chunk (:doc:`uproot.source.chunk.Chunk`): Buffer of contiguous data
@@ -306,7 +327,14 @@ def bytes(self, chunk, length, context, move=True):
self._index = stop
return chunk.get(start, stop, self, context)
- def array(self, chunk, length, dtype, context, move=True):
+ def array(
+ self,
+ chunk: uproot.source.chunk.Chunk,
+ length: int,
+ dtype: numpy.dtype,
+ context: dict,
+ move: bool = True,
+ ) -> numpy.ndarray:
"""
Args:
chunk (:doc:`uproot.source.chunk.Chunk`): Buffer of contiguous data
@@ -330,7 +358,9 @@ def array(self, chunk, length, dtype, context, move=True):
_u1 = numpy.dtype("u1")
_i4 = numpy.dtype(">i4")
- def bytestring(self, chunk, context, move=True):
+ def bytestring(
+ self, chunk: uproot.source.chunk.Chunk, context: dict, move: bool = True
+ ) -> bytes:
"""
Args:
chunk (:doc:`uproot.source.chunk.Chunk`): Buffer of contiguous data
@@ -361,7 +391,9 @@ def bytestring(self, chunk, context, move=True):
self._index = stop
return uproot._util.tobytes(chunk.get(start, stop, self, context))
- def string(self, chunk, context, move=True):
+ def string(
+ self, chunk: uproot.source.chunk.Chunk, context: dict, move: bool = True
+ ) -> str:
"""
Args:
chunk (:doc:`uproot.source.chunk.Chunk`): Buffer of contiguous data
@@ -382,7 +414,13 @@ def string(self, chunk, context, move=True):
errors="surrogateescape"
)
- def bytestring_with_length(self, chunk, context, length, move=True):
+ def bytestring_with_length(
+ self,
+ chunk: uproot.source.chunk.Chunk,
+ context: dict,
+ length: int,
+ move: bool = True,
+ ) -> bytes:
"""
Args:
chunk (:doc:`uproot.source.chunk.Chunk`): Buffer of contiguous data
@@ -403,7 +441,13 @@ def bytestring_with_length(self, chunk, context, length, move=True):
data = chunk.get(start, stop, self, context)
return uproot._util.tobytes(data)
- def string_with_length(self, chunk, context, length, move=True):
+ def string_with_length(
+ self,
+ chunk: uproot.source.chunk.Chunk,
+ context: dict,
+ length: int,
+ move: bool = True,
+ ) -> str:
"""
Args:
chunk (:doc:`uproot.source.chunk.Chunk`): Buffer of contiguous data
@@ -421,7 +465,9 @@ def string_with_length(self, chunk, context, length, move=True):
errors="surrogateescape"
)
- def classname(self, chunk, context, move=True):
+ def classname(
+ self, chunk: uproot.source.chunk.Chunk, context: dict, move: bool = True
+ ) -> str:
"""
Args:
chunk (:doc:`uproot.source.chunk.Chunk`): Buffer of contiguous data
@@ -455,7 +501,9 @@ def classname(self, chunk, context, move=True):
errors="surrogateescape"
)
- def rntuple_string(self, chunk, context, move=True):
+ def rntuple_string(
+ self, chunk: uproot.source.chunk.Chunk, context: dict, move: bool = True
+ ) -> str:
if move:
length = self.field(chunk, _rntuple_string_length, context)
return self.string_with_length(chunk, context, length)
@@ -465,17 +513,19 @@ def rntuple_string(self, chunk, context, move=True):
self._index = index
return out
- def rntuple_datetime(self, chunk, context, move=True):
+ def rntuple_datetime(
+ self, chunk: uproot.source.chunk.Chunk, context: dict, move: bool = True
+ ) -> datetime.datetime:
raw = self.field(chunk, _rntuple_datetime, context, move=move)
return datetime.datetime.fromtimestamp(raw)
def debug(
self,
- chunk,
- context={}, # noqa: B006 (it's not actually mutated in the function)
- limit_bytes=None,
- dtype=None,
- offset=0,
+ chunk: uproot.source.chunk.Chunk,
+ context: dict = {}, # noqa: B006 (it's not actually mutated in the function)
+ limit_bytes: int | None = None,
+ dtype: numpy.dtype = None,
+ offset: int = 0,
stream=sys.stdout,
):
"""
diff --git a/src/uproot/source/file.py b/src/uproot/source/file.py
index feae3d0b9..2cf03c01c 100644
--- a/src/uproot/source/file.py
+++ b/src/uproot/source/file.py
@@ -32,7 +32,7 @@ class FileResource(uproot.source.chunk.Resource):
A :doc:`uproot.source.chunk.Resource` for a simple file handle.
"""
- def __init__(self, file_path):
+ def __init__(self, file_path: str):
self._file_path = file_path
try:
self._file = open(self._file_path, "rb")
@@ -56,7 +56,7 @@ def __enter__(self):
def __exit__(self, exception_type, exception_value, traceback):
self._file.__exit__(exception_type, exception_value, traceback)
- def get(self, start, stop):
+ def get(self, start: int, stop: int) -> bytes:
"""
Args:
start (int): Seek position of the first byte to include.
@@ -69,7 +69,7 @@ def get(self, start, stop):
return self._file.read(stop - start)
@staticmethod
- def future(source, start, stop):
+ def future(source: uproot.source.chunk.Source, start: int, stop: int):
"""
Args:
source (:doc:`uproot.source.file.MultithreadedFileSource`): The
@@ -99,7 +99,7 @@ class MemmapSource(uproot.source.chunk.Source):
_dtype = uproot.source.chunk.Chunk._dtype
- def __init__(self, file_path, **options):
+ def __init__(self, file_path: str, **options):
self._num_fallback_workers = options["num_fallback_workers"]
self._fallback_opts = options
self._num_requests = 0
@@ -139,7 +139,7 @@ def __repr__(self):
fallback = " with fallback"
return f"<{type(self).__name__} {path}{fallback} at 0x{id(self):012x}>"
- def chunk(self, start, stop) -> uproot.source.chunk.Chunk:
+ def chunk(self, start: int, stop: int) -> uproot.source.chunk.Chunk:
if self._fallback is None:
if self.closed:
raise OSError(f"memmap is closed for file {self._file_path}")
@@ -156,7 +156,7 @@ def chunk(self, start, stop) -> uproot.source.chunk.Chunk:
return self._fallback.chunk(start, stop)
def chunks(
- self, ranges, notifications: queue.Queue
+ self, ranges: list[(int, int)], notifications: queue.Queue
) -> list[uproot.source.chunk.Chunk]:
if self._fallback is None:
if self.closed:
@@ -242,7 +242,7 @@ class MultithreadedFileSource(uproot.source.chunk.MultithreadedSource):
ResourceClass = FileResource
- def __init__(self, file_path, **options):
+ def __init__(self, file_path: str, **options):
self._num_requests = 0
self._num_requested_chunks = 0
self._num_requested_bytes = 0
diff --git a/src/uproot/source/fsspec.py b/src/uproot/source/fsspec.py
index 7f4680f3a..fd4f59672 100644
--- a/src/uproot/source/fsspec.py
+++ b/src/uproot/source/fsspec.py
@@ -22,7 +22,7 @@ class FSSpecSource(uproot.source.chunk.Source):
to get many chunks in one request.
"""
- def __init__(self, file_path, **options):
+ def __init__(self, file_path: str, **options):
import fsspec.core
default_options = uproot.reading.open.defaults
@@ -76,7 +76,7 @@ def __exit__(self, exception_type, exception_value, traceback):
self._executor.shutdown()
self._file.__exit__(exception_type, exception_value, traceback)
- def chunk(self, start, stop) -> uproot.source.chunk.Chunk:
+ def chunk(self, start: int, stop: int) -> uproot.source.chunk.Chunk:
"""
Args:
start (int): Seek position of the first byte to include.
@@ -98,7 +98,7 @@ def chunk(self, start, stop) -> uproot.source.chunk.Chunk:
return uproot.source.chunk.Chunk(self, start, stop, future)
def chunks(
- self, ranges, notifications: queue.Queue
+ self, ranges: list[(int, int)], notifications: queue.Queue
) -> list[uproot.source.chunk.Chunk]:
"""
Args:
diff --git a/src/uproot/source/futures.py b/src/uproot/source/futures.py
index e5bca5d53..f10b84e5a 100644
--- a/src/uproot/source/futures.py
+++ b/src/uproot/source/futures.py
@@ -21,6 +21,8 @@
These classes implement a *subset* of Python's Future and Executor interfaces.
"""
+from __future__ import annotations
+
import os
import queue
import sys
@@ -77,7 +79,7 @@ def submit(self, task, *args):
"""
return TrivialFuture(task(*args))
- def shutdown(self, wait=True):
+ def shutdown(self, wait: bool = True):
"""
Does nothing, since this object does not have threads to stop.
"""
@@ -144,13 +146,13 @@ class Worker(threading.Thread):
:doc:`uproot.source.futures.ThreadPoolExecutor`.
"""
- def __init__(self, work_queue):
+ def __init__(self, work_queue: queue.Queue):
super().__init__()
self.daemon = True
self._work_queue = work_queue
@property
- def work_queue(self):
+ def work_queue(self) -> queue.Queue:
"""
The worker calls ``get`` on this queue for tasks in the form of
:doc:`uproot.source.futures.Future` objects and runs them. If it ever
@@ -188,7 +190,7 @@ class ThreadPoolExecutor:
class.
"""
- def __init__(self, max_workers=None):
+ def __init__(self, max_workers: int | None = None):
if max_workers is None:
if hasattr(os, "cpu_count"):
self._max_workers = os.cpu_count()
@@ -224,7 +226,7 @@ def num_workers(self) -> int:
return len(self._workers)
@property
- def workers(self):
+ def workers(self) -> list[Worker]:
"""
A list of workers (:doc:`uproot.source.futures.Worker`).
"""
@@ -241,7 +243,7 @@ def submit(self, task, *args):
self._work_queue.put(future)
return future
- def shutdown(self, wait=True):
+ def shutdown(self, wait: bool = True):
"""
Stop every :doc:`uproot.source.futures.Worker` by putting None
on the :ref:`uproot.source.futures.Worker.work_queue` until none of
diff --git a/src/uproot/source/http.py b/src/uproot/source/http.py
index ac80cf038..956f7f32d 100644
--- a/src/uproot/source/http.py
+++ b/src/uproot/source/http.py
@@ -17,9 +17,11 @@
from __future__ import annotations
import base64
+import http.client
import queue
import re
import sys
+import urllib.parse
from urllib.parse import urlparse
import uproot
@@ -27,7 +29,7 @@
import uproot.source.futures
-def make_connection(parsed_url, timeout):
+def make_connection(parsed_url: urllib.parse.ParseResult, timeout: float | None):
"""
Args:
parsed_url (``urllib.parse.ParseResult``): The URL to connect to, which
@@ -51,7 +53,7 @@ def make_connection(parsed_url, timeout):
)
-def full_path(parsed_url):
+def full_path(parsed_url) -> str:
"""
Returns the ``parsed_url.path`` with ``"?"`` and the ``parsed_url.query``
if it exists, just the path otherwise.
@@ -78,7 +80,7 @@ def basic_auth_headers(parsed_url):
return ret
-def get_num_bytes(file_path, parsed_url, timeout):
+def get_num_bytes(file_path: str, parsed_url: urllib.parse.ParseResult, timeout) -> int:
"""
Args:
file_path (str): The URL to access as a raw string.
@@ -158,7 +160,7 @@ def __init__(self, file_path, timeout):
self._auth_headers = basic_auth_headers(self._parsed_url)
@property
- def timeout(self):
+ def timeout(self) -> float | None:
"""
The timeout in seconds or None.
"""
@@ -184,7 +186,7 @@ def __enter__(self):
def __exit__(self, exception_type, exception_value, traceback):
pass
- def get(self, connection, start, stop):
+ def get(self, connection, start: int, stop: int) -> bytes:
"""
Args:
start (int): Seek position of the first byte to include.
@@ -235,7 +237,7 @@ def get(self, connection, start, stop):
connection.close()
@staticmethod
- def future(source, start, stop):
+ def future(source: uproot.source.chunk.Source, start: int, stop: int):
"""
Args:
source (:doc:`uproot.source.http.HTTPSource` or :doc:`uproot.source.http.MultithreadedHTTPSource`): The
@@ -260,7 +262,9 @@ def task(resource):
return uproot.source.futures.ResourceFuture(task)
@staticmethod
- def multifuture(source, ranges, futures, results):
+ def multifuture(
+ source: uproot.source.chunk.Source, ranges: list[(int, int)], futures, results
+ ):
"""
Args:
source (:doc:`uproot.source.http.HTTPSource`): The data source.
@@ -351,7 +355,9 @@ def task(resource):
)
_content_range = re.compile(b"Content-Range: bytes ([0-9]+-[0-9]+)", re.I)
- def is_multipart_supported(self, ranges, response):
+ def is_multipart_supported(
+ self, ranges: list[(int, int)], response: http.client.HTTPResponse
+ ) -> bool:
"""
Helper function for :ref:`uproot.source.http.HTTPResource.multifuture`
to check for multipart GET support.
@@ -368,7 +374,13 @@ def is_multipart_supported(self, ranges, response):
else:
return True
- def handle_no_multipart(self, source, ranges, futures, results):
+ def handle_no_multipart(
+ self,
+ source: uproot.source.chunk.Source,
+ ranges: list[(int, int)],
+ futures,
+ results,
+ ):
"""
Helper function for :ref:`uproot.source.http.HTTPResource.multifuture`
to handle a lack of multipart GET support.
@@ -383,7 +395,14 @@ def handle_no_multipart(self, source, ranges, futures, results):
results[chunk.start, chunk.stop] = chunk.raw_data
futures[chunk.start, chunk.stop]._run(self)
- def handle_multipart(self, source, futures, results, response, ranges):
+ def handle_multipart(
+ self,
+ source: uproot.source.chunk.Source,
+ futures,
+ results,
+ response: http.client.HTTPResponse,
+ ranges: list[(int, int)],
+ ):
"""
Helper function for :ref:`uproot.source.http.HTTPResource.multifuture`
to handle the multipart GET response.
@@ -477,7 +496,7 @@ def next_header(self, response_buffer):
return range_string, size
@staticmethod
- def partfuture(results, start, stop):
+ def partfuture(results, start: int, stop: int):
"""
Returns a :doc:`uproot.source.futures.ResourceFuture` to simply select
the ``(start, stop)`` item from the ``results`` dict.
@@ -500,7 +519,7 @@ def __init__(self, stream):
self.already_read = b""
self.stream = stream
- def read(self, length):
+ def read(self, length: int):
if length < len(self.already_read):
out = self.already_read[:length]
self.already_read = self.already_read[length:]
@@ -514,7 +533,7 @@ def read(self, length):
else:
return self.stream.read(length)
- def readline(self):
+ def readline(self) -> bytes:
while True:
try:
index = self.already_read.index(b"\n")
@@ -545,7 +564,7 @@ class HTTPSource(uproot.source.chunk.Source):
ResourceClass = HTTPResource
- def __init__(self, file_path, **options):
+ def __init__(self, file_path: str, **options):
self._num_fallback_workers = options["num_fallback_workers"]
self._timeout = options["timeout"]
self._num_requests = 0
@@ -594,7 +613,7 @@ def __repr__(self):
fallback = " with fallback"
return f"<{type(self).__name__} {path}{fallback} at 0x{id(self):012x}>"
- def chunk(self, start, stop) -> uproot.source.chunk.Chunk:
+ def chunk(self, start: int, stop: int) -> uproot.source.chunk.Chunk:
self._num_requests += 1
self._num_requested_chunks += 1
self._num_requested_bytes += stop - start
@@ -605,7 +624,7 @@ def chunk(self, start, stop) -> uproot.source.chunk.Chunk:
return chunk
def chunks(
- self, ranges, notifications: queue.Queue
+ self, ranges: list[(int, int)], notifications: queue.Queue
) -> list[uproot.source.chunk.Chunk]:
if self._fallback is None:
self._num_requests += 1
diff --git a/src/uproot/source/object.py b/src/uproot/source/object.py
index 25d02a74a..ce7c3ad14 100644
--- a/src/uproot/source/object.py
+++ b/src/uproot/source/object.py
@@ -8,6 +8,7 @@
has exactly one worker (we can't assume that the object is thread-safe).
"""
+from __future__ import annotations
import uproot
import uproot.source.chunk
@@ -52,7 +53,7 @@ def __exit__(self, exception_type, exception_value, traceback):
if hasattr(self._obj, "__exit__"):
self._obj.__exit__(exception_type, exception_value, traceback)
- def get(self, start, stop):
+ def get(self, start: int, stop: int):
"""
Args:
start (int): Seek position of the first byte to include.
@@ -65,7 +66,7 @@ def get(self, start, stop):
return self._obj.read(stop - start)
@staticmethod
- def future(source, start, stop):
+ def future(source: uproot.source.chunk.Source, start: int, stop: int):
"""
Args:
source (:doc:`uproot.source.object.ObjectSource`): The data source.
diff --git a/src/uproot/source/s3.py b/src/uproot/source/s3.py
index 44e432ae1..fbcb3adbd 100644
--- a/src/uproot/source/s3.py
+++ b/src/uproot/source/s3.py
@@ -4,6 +4,8 @@
This module defines a physical layer for remote files, accessed via S3.
"""
+from __future__ import annotations
+
import os
from urllib.parse import parse_qsl, urlparse
@@ -27,7 +29,7 @@ class S3Source(uproot.source.http.HTTPSource):
def __init__(
self,
- file_path,
+ file_path: str,
endpoint="s3.amazonaws.com",
access_key=None,
secret_key=None,
diff --git a/src/uproot/source/xrootd.py b/src/uproot/source/xrootd.py
index 0efd8d883..0571b6713 100644
--- a/src/uproot/source/xrootd.py
+++ b/src/uproot/source/xrootd.py
@@ -85,7 +85,7 @@ class XRootDResource(uproot.source.chunk.Resource):
A :doc:`uproot.source.chunk.Resource` for XRootD connections.
"""
- def __init__(self, file_path, timeout):
+ def __init__(self, file_path: str, timeout: float | None):
self._file_path = file_path
self._timeout = timeout
self._open()
@@ -108,7 +108,7 @@ def __setstate__(self, state):
self.__dict__ = state
self._open()
- def _xrd_timeout(self):
+ def _xrd_timeout(self) -> int:
if self._timeout is None:
return 0
else:
@@ -129,7 +129,7 @@ def _xrd_error(self, status):
)
@property
- def timeout(self):
+ def timeout(self) -> float | None:
"""
The timeout in seconds or None.
"""
@@ -159,7 +159,7 @@ def __enter__(self):
def __exit__(self, exception_type, exception_value, traceback):
self._file.close(timeout=self._xrd_timeout())
- def get(self, start, stop):
+ def get(self, start: int, stop: int) -> bytes:
"""
Args:
start (int): Seek position of the first byte to include.
@@ -176,7 +176,7 @@ def get(self, start, stop):
return data
@staticmethod
- def future(source, start, stop):
+ def future(source: uproot.source.chunk.Source, start: int, stop: int):
"""
Args:
source (:doc:`uproot.source.xrootd.MultithreadedXRootDSource`): The
@@ -196,7 +196,7 @@ def task(resource):
return uproot.source.futures.ResourceFuture(task)
@staticmethod
- def partfuture(results, start, stop):
+ def partfuture(results, start: int, stop: int):
"""
Returns a :doc:`uproot.source.futures.ResourceFuture` to simply select
the ``(start, stop)`` item from the ``results`` dict.
@@ -265,7 +265,7 @@ class XRootDSource(uproot.source.chunk.Source):
ResourceClass = XRootDResource
- def __init__(self, file_path, **options):
+ def __init__(self, file_path: str, **options):
self._timeout = options["timeout"]
self._desired_max_num_elements = options["max_num_elements"]
self._use_threads = options["use_threads"]
@@ -315,7 +315,7 @@ def __repr__(self):
path = repr("..." + self._file_path[-10:])
return f"<{type(self).__name__} {path} at 0x{id(self):012x}>"
- def chunk(self, start, stop) -> uproot.source.chunk.Chunk:
+ def chunk(self, start: int, stop: int) -> uproot.source.chunk.Chunk:
self._num_requests += 1
self._num_requested_chunks += 1
self._num_requested_bytes += stop - start
@@ -325,7 +325,7 @@ def chunk(self, start, stop) -> uproot.source.chunk.Chunk:
return uproot.source.chunk.Chunk(self, start, stop, future)
def chunks(
- self, ranges, notifications: queue.Queue
+ self, ranges: list[(int, int)], notifications: queue.Queue
) -> list[uproot.source.chunk.Chunk]:
self._num_requests += 1
self._num_requested_chunks += len(ranges)
@@ -339,7 +339,9 @@ def chunks(
# this is to track which requests were split into smaller ranges and have to be merged
sub_ranges = {}
- def add_request_range(start, length, sub_ranges_list):
+ def add_request_range(
+ start: int, length: int, sub_ranges_list: list[(int, int)]
+ ):
if len(all_request_ranges[-1]) >= self._max_num_elements:
all_request_ranges.append([])
all_request_ranges[-1].append((start, length))
@@ -412,7 +414,7 @@ def resource(self):
return self._resource
@property
- def timeout(self):
+ def timeout(self) -> float | None:
"""
The timeout in seconds or None.
"""
@@ -455,7 +457,7 @@ class MultithreadedXRootDSource(uproot.source.chunk.MultithreadedSource):
ResourceClass = XRootDResource
- def __init__(self, file_path, **options):
+ def __init__(self, file_path: str, **options):
self._num_workers = options["num_workers"]
self._timeout = options["timeout"]
self._use_threads = options["use_threads"]
@@ -472,7 +474,7 @@ def _open(self):
self._executor = uproot.source.futures.ResourceThreadPoolExecutor(
[
XRootDResource(self._file_path, self._timeout)
- for x in range(self._num_workers)
+ for _ in range(self._num_workers)
]
)
else:
@@ -490,7 +492,7 @@ def __setstate__(self, state):
self._open()
@property
- def timeout(self):
+ def timeout(self) -> float | None:
"""
The timeout in seconds or None.
"""
From fff5ebeccd3ceb0a780e839068f3b3919936d007 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
<66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Thu, 19 Oct 2023 08:40:37 -0500
Subject: [PATCH 4/5] chore: update pre-commit hooks (#993)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
updates:
- [github.com/asottile/pyupgrade: v3.13.0 → v3.15.0](https://github.com/asottile/pyupgrade/compare/v3.13.0...v3.15.0)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
.pre-commit-config.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 998444e07..742949656 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -30,7 +30,7 @@ repos:
- repo: https://github.com/asottile/pyupgrade
- rev: v3.13.0
+ rev: v3.15.0
hooks:
- id: pyupgrade
args: ["--py38-plus"]
From 6b1952ea572471c7b64da3650566d6ce7ff6a4bc Mon Sep 17 00:00:00 2001
From: ioanaif
Date: Thu, 19 Oct 2023 16:48:07 +0200
Subject: [PATCH 5/5] feat: add support for current RNTuple files (#962)
* Adds changes, but ignores some to keep the old tests passing for now.
* style: pre-commit fixes
* Adds changes to footer and skips old file tests with the exception of 0662.
* style: pre-commit fixes
* Adds new version of file for test 0662 and renames new test file to match pr nr.
* Swaps rntuple file for new one form RNTuple in test 0662, the footer reading part should not complain anymore.
* Applies changes to const file.
* Adds new format test file.
* style: pre-commit fixes
* Tests only the new file for now.
* style: pre-commit fixes
* Adds new RNTuple schema for the footer and the split cases without the split functions.
* style: pre-commit fixes
* Updates testing / adds more files.
* style: pre-commit fixes
* Adds changes to split functionality.
* style: pre-commit fixes
* Fixes split for uint16 case.
* style: pre-commit fixes
* Fixes split for 32 and 64.
* style: pre-commit fixes
* Fixes spelling errors.
* style: pre-commit fixes
* Fixes spelling errors.
* Changes zigzag function.
* style: pre-commit fixes
* Uses test files from skhep_testdata and removes local ones.
* style: pre-commit fixes
* Reverts changes to test_0662-rntuple-stl-containers.py
* Vectorized split-decoding for 32 and 64-bits.
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Jim Pivarski
Co-authored-by: Jim Pivarski
---
src/uproot/const.py | 51 ++++++++----
src/uproot/models/RNTuple.py | 89 ++++++++++++++++++++-
tests/test_0630-rntuple-basics.py | 1 +
tests/test_0705-rntuple-writing-metadata.py | 1 +
tests/test_0962-RNTuple-update.py | 37 +++++++++
5 files changed, 160 insertions(+), 19 deletions(-)
create mode 100644 tests/test_0962-RNTuple-update.py
diff --git a/src/uproot/const.py b/src/uproot/const.py
index 731453c98..0d4424b5a 100644
--- a/src/uproot/const.py
+++ b/src/uproot/const.py
@@ -129,18 +129,25 @@
7: "float64",
8: "float32",
9: "float16",
- 10: "int64",
- 11: "int32",
- 12: "int16",
- 13: "int8",
- 14: "uint32", # SplitIndex64 delta encoding
- 15: "uint64", # SplitIndex32 delta encoding
+ 10: "uint64",
+ 11: "uint32",
+ 12: "uint16",
+ 13: "uint8",
+ 14: "uint64", # SplitIndex64 delta encoding
+ 15: "uint32", # SplitIndex32 delta encoding
16: "float64", # split
17: "float32", # split
18: "float16", # split
- 19: "int64", # split
- 20: "int32", # split
- 21: "int16", # split
+ 19: "uint64", # split
+ 20: "uint32", # split
+ 21: "uint16", # split
+ 22: "int64",
+ 23: "int32",
+ 24: "int16",
+ 25: "int8",
+ 26: "int64", # split + zigzag encoding
+ 27: "int32", # split + zigzag encoding
+ 28: "int16", # split + zigzag encoding
}
rntuple_col_num_to_size_dict = {
1: 64,
@@ -156,14 +163,21 @@
11: 32,
12: 16,
13: 8,
- 14: 32, # SplitIndex64 delta encoding
- 15: 64, # SplitIndex32 delta encoding
+ 14: 64, # SplitIndex64 delta encoding
+ 15: 32, # SplitIndex32 delta encoding
16: 64, # split
17: 32, # split
18: 16, # split
19: 64, # split
20: 32, # split
21: 16, # split
+ 22: 64,
+ 23: 32,
+ 24: 16,
+ 25: 8,
+ 26: 64, # split + zigzag encoding
+ 27: 32, # split + zigzag encoding
+ 28: 16, # split + zigzag encoding
}
rntuple_col_type_to_num_dict = {
@@ -176,10 +190,10 @@
"real64": 7,
"real32": 8,
"real16": 9,
- "int64": 10,
- "int32": 11,
- "int16": 12,
- "int8": 13,
+ "uint64": 10,
+ "uint32": 11,
+ "uint16": 12,
+ "uint8": 13,
"splitindex64": 14,
"splitindex32": 15,
"splitreal64": 16,
@@ -188,6 +202,13 @@
"splitin64": 19,
"splitint32": 20,
"splitint16": 21,
+ "int64": 22,
+ "int32": 23,
+ "int16": 24,
+ "int8": 25,
+ "splitzigzagint64": 26,
+ "splitzigzagint32": 27,
+ "splitzigzagint16": 28,
}
rntuple_role_leaf = 0
diff --git a/src/uproot/models/RNTuple.py b/src/uproot/models/RNTuple.py
index 7bb3f3d52..b3d4fe871 100644
--- a/src/uproot/models/RNTuple.py
+++ b/src/uproot/models/RNTuple.py
@@ -28,6 +28,10 @@
_rntuple_cluster_summary_format = struct.Struct("> 1 ^ -(n & 1)
+
+
def _envelop_header(chunk, cursor, context):
env_version, min_version = cursor.fields(
chunk, uproot.const._rntuple_frame_format, context
@@ -326,7 +330,7 @@ def to_akform(self):
form = ak.forms.RecordForm(recordlist, topnames, form_key="toplevel")
return form
- def read_pagedesc(self, destination, desc, dtype_str, dtype):
+ def read_pagedesc(self, destination, desc, dtype_str, dtype, nbits, split):
loc = desc.locator
context = {}
# bool in RNTuple is always stored as bits
@@ -339,6 +343,44 @@ def read_pagedesc(self, destination, desc, dtype_str, dtype):
content = cursor.array(
decomp_chunk, num_elements_toread, dtype, context, move=False
)
+
+ if split:
+ content = content.view(numpy.uint8)
+
+ if nbits == 16:
+ # AAAAABBBBB needs to become
+ # ABABABABAB
+ res = numpy.empty(len(content), numpy.uint8)
+ res[0::2] = content[len(res) * 0 // 2 : len(res) * 1 // 2]
+ res[1::2] = content[len(res) * 1 // 2 : len(res) * 2 // 2]
+ res = res.view(numpy.uint16)
+
+ elif nbits == 32:
+ # AAAAABBBBBCCCCCDDDDD needs to become
+ # ABCDABCDABCDABCDABCD
+ res = numpy.empty(len(content), numpy.uint8)
+ res[0::4] = content[len(res) * 0 // 4 : len(res) * 1 // 4]
+ res[1::4] = content[len(res) * 1 // 4 : len(res) * 2 // 4]
+ res[2::4] = content[len(res) * 2 // 4 : len(res) * 3 // 4]
+ res[3::4] = content[len(res) * 3 // 4 : len(res) * 4 // 4]
+ res = res.view(numpy.uint32)
+
+ elif nbits == 64:
+ # AAAAABBBBBCCCCCDDDDDEEEEEFFFFFGGGGGHHHHH needs to become
+ # ABCDEFGHABCDEFGHABCDEFGHABCDEFGHABCDEFGH
+ res = numpy.empty(len(content), numpy.uint8)
+ res[0::8] = content[len(res) * 0 // 8 : len(res) * 1 // 8]
+ res[1::8] = content[len(res) * 1 // 8 : len(res) * 2 // 8]
+ res[2::8] = content[len(res) * 2 // 8 : len(res) * 3 // 8]
+ res[3::8] = content[len(res) * 3 // 8 : len(res) * 4 // 8]
+ res[4::8] = content[len(res) * 4 // 8 : len(res) * 5 // 8]
+ res[5::8] = content[len(res) * 5 // 8 : len(res) * 6 // 8]
+ res[6::8] = content[len(res) * 6 // 8 : len(res) * 7 // 8]
+ res[7::8] = content[len(res) * 7 // 8 : len(res) * 8 // 8]
+ res = res.view(numpy.uint64)
+
+ content = res
+
if isbit:
content = (
numpy.unpackbits(content.view(dtype=numpy.uint8))
@@ -368,14 +410,24 @@ def read_col_page(self, ncol, cluster_i):
total_len = numpy.sum([desc.num_elements for desc in pagelist])
res = numpy.empty(total_len, dtype)
tracker = 0
+ split = 14 <= dtype_byte <= 21 or 26 <= dtype_byte <= 28
+ nbits = uproot.const.rntuple_col_num_to_size_dict[dtype_byte]
for page_desc in pagelist:
n_elements = page_desc.num_elements
tracker_end = tracker + n_elements
- self.read_pagedesc(res[tracker:tracker_end], page_desc, dtype_str, dtype)
+ self.read_pagedesc(
+ res[tracker:tracker_end], page_desc, dtype_str, dtype, nbits, split
+ )
tracker = tracker_end
if dtype_byte <= uproot.const.rntuple_col_type_to_num_dict["index32"]:
res = numpy.insert(res, 0, 0) # for offsets
+ zigzag = 26 <= dtype_byte <= 28
+ delta = 14 <= dtype_byte <= 15
+ if zigzag:
+ res = from_zigzag(res)
+ elif delta:
+ numpy.cumsum(res)
return res
def arrays(
@@ -645,6 +697,15 @@ def read(self, chunk, cursor, context):
return out
+ def read_extension_header(self, out, chunk, cursor, context):
+ out.field_records = self.list_field_record_frames.read(chunk, cursor, context)
+ out.column_records = self.list_column_record_frames.read(chunk, cursor, context)
+ out.alias_columns = self.list_alias_column_frames.read(chunk, cursor, context)
+ out.extra_type_infos = self.list_extra_type_info_reader.read(
+ chunk, cursor, context
+ )
+ return out
+
class ColumnGroupRecordReader:
def read(self, chunk, cursor, context):
@@ -672,9 +733,29 @@ def read(self, chunk, cursor, context):
return out
+class RNTupleSchemaExtension:
+ def read(self, chunk, cursor, context):
+ out = MetaData(type(self).__name__)
+ out.size = cursor.field(chunk, struct.Struct("