Skip to content

Commit

Permalink
Merge branch 'friendly-names'.
Browse files Browse the repository at this point in the history
This renames patching-related functions to instead refer to enabling and
disabling fast slicing, so as to be more friendly and focus more on their
effect rather than the mechanism that they use.

The renames are:

- `is_dataset_class_patched` -> `is_fast_slicing_enabled`
- `patch_dataset_class` -> `enable_fast_slicing`
- `unpatch_dataset_class` -> `disable_fast_slicing`
- `patching_dataset_class` -> `fast_slicing`
  • Loading branch information
ivilata committed Dec 12, 2023
2 parents 99760f3 + 6e20fec commit 0cd283e
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 79 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ After that, optimization will be attempted for any slicing of a dataset (of the

.. _hdf5plugin: https://github.com/silx-kit/hdf5plugin

You may globally disable the optimization after importing ``b2h5py`` by calling ``b2h5py.unpatch_dataset_class()``, and enable it again with ``b2h5py.patch_dataset_class()``. You may also perform this patching temporarily by using ``b2h5py.patching_dataset_class()`` to get a context manager.
You may globally disable the optimization after importing ``b2h5py`` by calling ``b2h5py.disable_fast_slicing()``, and enable it again with ``b2h5py.enable_fast_slicing()``. You may also enable it temporarily by using ``b2h5py.fast_slicing()`` to get a context manager.

Building
--------
Expand Down
27 changes: 14 additions & 13 deletions b2h5py/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,26 @@
the native byte order.
They are enabled automatically on module import, by monkey-patching the
``h5py.Dataset`` class. You may explicitly undo this patching with
`unpatch_dataset_class()` and redo it with `patch_dataset_class()`. You may
also patch the class temporarily using `patching_dataset_class()` to get a
context manager.
``h5py.Dataset`` class. You may explicitly undo this patching and deactivate
optimization globally with `disable_fast_slicing()` and redo it and activate
it again with `enable_fast_slicing()`. You may also patch the class and
activate optimization temporarily using `fast_slicing()` to get a context
manager.
**Note:** For testing and debugging purposes, you may force-disable the
optimization at any time by setting ``BLOSC2_FILTER=1`` in the environment.
"""

from .blosc2 import (is_dataset_class_patched,
patch_dataset_class,
patching_dataset_class,
unpatch_dataset_class)
from .blosc2 import (disable_fast_slicing,
enable_fast_slicing,
fast_slicing,
is_fast_slicing_enabled)


__all__ = ['is_dataset_class_patched',
'patch_dataset_class',
'patching_dataset_class',
'unpatch_dataset_class']
__all__ = ['disable_fast_slicing',
'enable_fast_slicing',
'fast_slicing',
'is_fast_slicing_enabled']


patch_dataset_class()
enable_fast_slicing()
43 changes: 25 additions & 18 deletions b2h5py/blosc2.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,22 +215,26 @@ def B2Dataset___getitem__(self, args, new_dtype=None):
return B2Dataset___getitem__.__wrapped__(self, args, new_dtype)


def is_dataset_class_patched():
"""Return whether ``h5py.Dataset`` is already patched for Blosc2
def is_fast_slicing_enabled():
"""Return whether global support for Blosc2 optimized slicing is
activated.
This means checking whether``h5py.Dataset`` is already patched for Blosc2
optimizations.
"""
return hasattr(h5py.Dataset, '_blosc2_opt_slicing_ok')


def patch_dataset_class():
"""Patch ``h5py.Dataset`` to support Blosc2 optimizations.
def enable_fast_slicing():
"""Globally activate support for Blosc2 optimized slicing.
This has no effect if the class has already been patched for this purpose.
This means patching ``h5py.Dataset`` to support Blosc2 optimizations. It
has no effect if the class has already been patched for this purpose.
This supports patching the class if it has already been patched by other
code for other purposes.
"""
if is_dataset_class_patched():
if is_fast_slicing_enabled():
return # already patched

h5py.Dataset._blosc2_opt_slicing_ok = B2Dataset__blosc2_opt_slicing_ok
Expand All @@ -241,17 +245,18 @@ def patch_dataset_class():
h5py.Dataset.__getitem__ = B2Dataset___getitem__


def unpatch_dataset_class():
"""Undo the patching of ``h5py.Dataset`` to remove support for Blosc2
optimizations.
def disable_fast_slicing():
"""Globally deactivate support for Blosc2 optimized slicing.
This has no effect if the class has not been patched for this purpose.
This means undoing the patching of ``h5py.Dataset`` to remove support for
Blosc2 optimizations. It has no effect if the class has not been patched
for this purpose.
Raises `ValueError` if the operations patched by this code were already
patched over by some other code. In this case, the latter patch must be
removed first (if the other code supports it).
"""
if not is_dataset_class_patched():
if not is_fast_slicing_enabled():
return # not patched

if h5py.Dataset.__getitem__ is not B2Dataset___getitem__:
Expand All @@ -264,22 +269,24 @@ def unpatch_dataset_class():


@contextlib.contextmanager
def patching_dataset_class():
"""Get a context manager to patch ``h5py.Dataset`` temporarily.
def fast_slicing():
"""Get a context manager to temporarily activate support for Blosc2
optimized slicing.
If the class was already patched when the context manager is entered, it
remains patched on exit. Otherwise, it is unpatched.
This means patching ``h5py.Dataset`` temporarily. If the class was
already patched when the context manager is entered, it remains patched on
exit. Otherwise, it is unpatched.
Note: this change is applied globally while the context manager is active.
"""
already_patched = is_dataset_class_patched()
already_patched = is_fast_slicing_enabled()

if already_patched: # do nothing
yield None
return

patch_dataset_class()
enable_fast_slicing()
try:
yield None
finally:
unpatch_dataset_class()
disable_fast_slicing()
68 changes: 34 additions & 34 deletions b2h5py/tests/test_dataset_patching.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,45 +15,45 @@
class Blosc2DatasetPatchingTestCase(TestCase):
def setUp(self):
super().setUp()
b2h5py.patch_dataset_class()
b2h5py.enable_fast_slicing()

def tearDown(self):
b2h5py.patch_dataset_class()
b2h5py.enable_fast_slicing()
super().tearDown()

def test_default(self):
"""Dataset class is patched by default"""
self.assertTrue(b2h5py.is_dataset_class_patched())
self.assertTrue(b2h5py.is_fast_slicing_enabled())

def test_unpatch_patch(self):
"""Unpatching and patching dataset class again"""
b2h5py.unpatch_dataset_class()
self.assertFalse(b2h5py.is_dataset_class_patched())
b2h5py.disable_fast_slicing()
self.assertFalse(b2h5py.is_fast_slicing_enabled())

b2h5py.patch_dataset_class()
self.assertTrue(b2h5py.is_dataset_class_patched())
b2h5py.enable_fast_slicing()
self.assertTrue(b2h5py.is_fast_slicing_enabled())

def test_patch_again(self):
"""Patching the dataset class twice"""
b2h5py.patch_dataset_class()
b2h5py.enable_fast_slicing()
getitem1 = Dataset.__getitem__
b2h5py.patch_dataset_class()
b2h5py.enable_fast_slicing()
getitem2 = Dataset.__getitem__

self.assertIs(getitem1, getitem2)

def test_unpatch_again(self):
"""Unpatching the dataset class twice"""
b2h5py.unpatch_dataset_class()
b2h5py.disable_fast_slicing()
getitem1 = Dataset.__getitem__
b2h5py.unpatch_dataset_class()
b2h5py.disable_fast_slicing()
getitem2 = Dataset.__getitem__

self.assertIs(getitem1, getitem2)

def test_patch_patched(self):
"""Patching when already patched by someone else"""
b2h5py.unpatch_dataset_class()
b2h5py.disable_fast_slicing()

@functools.wraps(Dataset.__getitem__)
def foreign_getitem(self, args, new_dtype=None):
Expand All @@ -62,15 +62,15 @@ def foreign_getitem(self, args, new_dtype=None):
Dataset.__getitem__ = foreign_getitem

try:
b2h5py.patch_dataset_class()
self.assertTrue(b2h5py.is_dataset_class_patched())
b2h5py.enable_fast_slicing()
self.assertTrue(b2h5py.is_fast_slicing_enabled())
self.assertIs(Dataset.__getitem__.__wrapped__, foreign_getitem)

b2h5py.unpatch_dataset_class()
self.assertFalse(b2h5py.is_dataset_class_patched())
b2h5py.disable_fast_slicing()
self.assertFalse(b2h5py.is_fast_slicing_enabled())
self.assertIs(Dataset.__getitem__, foreign_getitem)
finally:
b2h5py.unpatch_dataset_class()
b2h5py.disable_fast_slicing()
Dataset.__getitem__ = foreign_getitem.__wrapped__

def test_unpatch_foreign(self):
Expand All @@ -84,7 +84,7 @@ def foreign_getitem(self, args, new_dtype=None):

try:
with self.assertRaises(ValueError):
b2h5py.unpatch_dataset_class()
b2h5py.disable_fast_slicing()
finally:
Dataset.__getitem__ = foreign_getitem.__wrapped__

Expand All @@ -100,10 +100,10 @@ class ContextManagerTestCase(TestCase):

def setUp(self):
super().setUp()
b2h5py.unpatch_dataset_class()
b2h5py.disable_fast_slicing()

def tearDown(self):
b2h5py.patch_dataset_class()
b2h5py.enable_fast_slicing()
super().tearDown()

def patching_cmgr(self):
Expand All @@ -114,7 +114,7 @@ class CMTestContextManager(contextlib.ExitStack):
def __enter__(self):
if test_case.shall_raise:
self.enter_context(test_case.assertRaises(CMTestError))
self.enter_context(b2h5py.patching_dataset_class())
self.enter_context(b2h5py.fast_slicing())
return super().__enter__()

return CMTestContextManager()
Expand All @@ -125,39 +125,39 @@ def maybe_raise(self):

def test_default(self):
"""Dataset class is patched then unpatched"""
self.assertFalse(b2h5py.is_dataset_class_patched())
self.assertFalse(b2h5py.is_fast_slicing_enabled())
with self.patching_cmgr():
self.assertTrue(b2h5py.is_dataset_class_patched())
self.assertTrue(b2h5py.is_fast_slicing_enabled())
self.maybe_raise()
self.assertFalse(b2h5py.is_dataset_class_patched())
self.assertFalse(b2h5py.is_fast_slicing_enabled())

def test_exception(self):
"""Exceptions are propagated"""
# This test always raises, do not use `self.patching_cmgr()`.
with self.assertRaises(CMTestError):
with b2h5py.patching_dataset_class():
with b2h5py.fast_slicing():
raise CMTestError

def test_already_patched(self):
"""Not unpatching if already patched before entry"""
b2h5py.patch_dataset_class()
self.assertTrue(b2h5py.is_dataset_class_patched())
b2h5py.enable_fast_slicing()
self.assertTrue(b2h5py.is_fast_slicing_enabled())
with self.patching_cmgr():
self.assertTrue(b2h5py.is_dataset_class_patched())
self.assertTrue(b2h5py.is_fast_slicing_enabled())
self.maybe_raise()
self.assertTrue(b2h5py.is_dataset_class_patched())
self.assertTrue(b2h5py.is_fast_slicing_enabled())

def test_nested(self):
"""Nesting patching context managers"""
self.assertFalse(b2h5py.is_dataset_class_patched())
self.assertFalse(b2h5py.is_fast_slicing_enabled())
with self.patching_cmgr():
self.assertTrue(b2h5py.is_dataset_class_patched())
self.assertTrue(b2h5py.is_fast_slicing_enabled())
with self.patching_cmgr():
self.assertTrue(b2h5py.is_dataset_class_patched())
self.assertTrue(b2h5py.is_fast_slicing_enabled())
self.maybe_raise()
self.assertTrue(b2h5py.is_dataset_class_patched())
self.assertTrue(b2h5py.is_fast_slicing_enabled())
self.maybe_raise()
self.assertFalse(b2h5py.is_dataset_class_patched())
self.assertFalse(b2h5py.is_fast_slicing_enabled())


class ErrorContextManagerTestCase(ContextManagerTestCase):
Expand Down
4 changes: 2 additions & 2 deletions b2h5py/tests/test_slicing_blosc2.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,10 @@ class Blosc2UnpatchTestCase(Blosc2OptSlicingTestCase):

def setUp(self):
super().setUp()
b2h5py.unpatch_dataset_class()
b2h5py.disable_fast_slicing()

def tearDown(self):
b2h5py.patch_dataset_class()
b2h5py.enable_fast_slicing()
super().tearDown()

def should_enable_opt(self):
Expand Down
22 changes: 11 additions & 11 deletions examples/blosc2_optimized_slicing.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def printl(*args, **kwargs):
print("# Using Blosc2 optimized slicing")
with h5py.File(file_name, 'r') as f:
import b2h5py
assert(b2h5py.is_dataset_class_patched())
assert(b2h5py.is_fast_slicing_enabled())
# One just uses slicing as usual.
dataset = f[dataset_name]
# Slices with step == 1 may be optimized.
Expand All @@ -90,14 +90,14 @@ def printl(*args, **kwargs):
print("# Disabling Blosc2 optimized slicing globally")
with h5py.File(file_name, 'r') as f:
import b2h5py
assert(b2h5py.is_dataset_class_patched())
b2h5py.unpatch_dataset_class()
assert(not b2h5py.is_dataset_class_patched())
assert(b2h5py.is_fast_slicing_enabled())
b2h5py.disable_fast_slicing()
assert(not b2h5py.is_fast_slicing_enabled())
dataset = f[dataset_name]
printl("Slice from dataset (filter):", dataset[150:, 150:])
printl("Slice from input array:", data[150:, 150:])
b2h5py.patch_dataset_class() # back to normal
assert(b2h5py.is_dataset_class_patched())
b2h5py.enable_fast_slicing() # back to normal
assert(b2h5py.is_fast_slicing_enabled())
print()

# Enabling Blosc2 optimized slicing temporarily
Expand All @@ -107,13 +107,13 @@ def printl(*args, **kwargs):
print("# Enabling Blosc2 optimized slicing temporarily")
with h5py.File(file_name, 'r') as f:
import b2h5py
b2h5py.unpatch_dataset_class()
assert(not b2h5py.is_dataset_class_patched())
b2h5py.disable_fast_slicing()
assert(not b2h5py.is_fast_slicing_enabled())
dataset = f[dataset_name]
printl("Slice from dataset (filter):", dataset[150:, 150:])
with b2h5py.patching_dataset_class():
assert(b2h5py.is_dataset_class_patched())
with b2h5py.fast_slicing():
assert(b2h5py.is_fast_slicing_enabled())
printl("Slice from dataset (optimized):", dataset[150:, 150:])
assert(not b2h5py.is_dataset_class_patched())
assert(not b2h5py.is_fast_slicing_enabled())
printl("Slice from input array:", data[150:, 150:])
print()

0 comments on commit 0cd283e

Please sign in to comment.