Skip to content

Commit c959d11

Browse files
authored
Merge pull request #459 from jrbourbeau/case-insensitive-directorystore
Add key normalization to DirectoryStore and subclass stores
2 parents 86364ed + c180764 commit c959d11

File tree

4 files changed

+50
-12
lines changed

4 files changed

+50
-12
lines changed

docs/release.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ Release notes
44
Upcoming Release
55
----------------
66

7+
* Add key normalization option for ``DirectoryStore``, ``NestedDirectoryStore``,
8+
``TempStore``, and ``N5Store``.
9+
By :user:`James Bourbeau <jrbourbeau>`; :issue:`459`.
10+
711
* Add ``recurse`` keyword to ``Group.array_keys`` and ``Group.arrays`` methods.
8-
By :user:`James Bourbeau <jrbourbeau>`; :issue:`458`
12+
By :user:`James Bourbeau <jrbourbeau>`; :issue:`458`.
913

1014
* Use uniform chunking for all dimensions when specifying ``chunks`` as an integer.
1115
Also adds support for specifying ``-1`` to chunk across an entire dimension.

zarr/n5.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ class N5Store(NestedDirectoryStore):
3535
----------
3636
path : string
3737
Location of directory to use as the root of the storage hierarchy.
38+
normalize_keys : bool, optional
39+
If True, all store keys will be normalized to use lower case characters
40+
(e.g. 'foo' and 'FOO' will be treated as equivalent). This can be
41+
useful to avoid potential discrepancies between case-senstive and
42+
case-insensitive file system. Default value is False.
3843
3944
Examples
4045
--------

zarr/storage.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,11 @@ class DirectoryStore(MutableMapping):
682682
----------
683683
path : string
684684
Location of directory to use as the root of the storage hierarchy.
685+
normalize_keys : bool, optional
686+
If True, all store keys will be normalized to use lower case characters
687+
(e.g. 'foo' and 'FOO' will be treated as equivalent). This can be
688+
useful to avoid potential discrepancies between case-senstive and
689+
case-insensitive file system. Default value is False.
685690
686691
Examples
687692
--------
@@ -728,16 +733,21 @@ class DirectoryStore(MutableMapping):
728733
729734
"""
730735

731-
def __init__(self, path):
736+
def __init__(self, path, normalize_keys=False):
732737

733738
# guard conditions
734739
path = os.path.abspath(path)
735740
if os.path.exists(path) and not os.path.isdir(path):
736741
err_fspath_exists_notdir(path)
737742

738743
self.path = path
744+
self.normalize_keys = normalize_keys
745+
746+
def _normalize_key(self, key):
747+
return key.lower() if self.normalize_keys else key
739748

740749
def __getitem__(self, key):
750+
key = self._normalize_key(key)
741751
filepath = os.path.join(self.path, key)
742752
if os.path.isfile(filepath):
743753
with open(filepath, 'rb') as f:
@@ -746,6 +756,7 @@ def __getitem__(self, key):
746756
raise KeyError(key)
747757

748758
def __setitem__(self, key, value):
759+
key = self._normalize_key(key)
749760

750761
# coerce to flat, contiguous array (ideally without copying)
751762
value = ensure_contiguous_ndarray(value)
@@ -785,6 +796,7 @@ def __setitem__(self, key, value):
785796
os.remove(temp_path)
786797

787798
def __delitem__(self, key):
799+
key = self._normalize_key(key)
788800
path = os.path.join(self.path, key)
789801
if os.path.isfile(path):
790802
os.remove(path)
@@ -796,6 +808,7 @@ def __delitem__(self, key):
796808
raise KeyError(key)
797809

798810
def __contains__(self, key):
811+
key = self._normalize_key(key)
799812
file_path = os.path.join(self.path, key)
800813
return os.path.isfile(file_path)
801814

@@ -910,14 +923,19 @@ class TempStore(DirectoryStore):
910923
Prefix for the temporary directory name.
911924
dir : string, optional
912925
Path to parent directory in which to create temporary directory.
926+
normalize_keys : bool, optional
927+
If True, all store keys will be normalized to use lower case characters
928+
(e.g. 'foo' and 'FOO' will be treated as equivalent). This can be
929+
useful to avoid potential discrepancies between case-senstive and
930+
case-insensitive file system. Default value is False.
913931
914932
"""
915933

916934
# noinspection PyShadowingBuiltins
917-
def __init__(self, suffix='', prefix='zarr', dir=None):
935+
def __init__(self, suffix='', prefix='zarr', dir=None, normalize_keys=False):
918936
path = tempfile.mkdtemp(suffix=suffix, prefix=prefix, dir=dir)
919937
atexit.register(atexit_rmtree, path)
920-
super(TempStore, self).__init__(path)
938+
super(TempStore, self).__init__(path, normalize_keys=normalize_keys)
921939

922940

923941
_prog_ckey = re.compile(r'^(\d+)(\.\d+)+$')
@@ -944,6 +962,11 @@ class NestedDirectoryStore(DirectoryStore):
944962
----------
945963
path : string
946964
Location of directory to use as the root of the storage hierarchy.
965+
normalize_keys : bool, optional
966+
If True, all store keys will be normalized to use lower case characters
967+
(e.g. 'foo' and 'FOO' will be treated as equivalent). This can be
968+
useful to avoid potential discrepancies between case-senstive and
969+
case-insensitive file system. Default value is False.
947970
948971
Examples
949972
--------
@@ -1000,8 +1023,8 @@ class NestedDirectoryStore(DirectoryStore):
10001023
10011024
"""
10021025

1003-
def __init__(self, path):
1004-
super(NestedDirectoryStore, self).__init__(path)
1026+
def __init__(self, path, normalize_keys=False):
1027+
super(NestedDirectoryStore, self).__init__(path, normalize_keys=normalize_keys)
10051028

10061029
def __getitem__(self, key):
10071030
key = _nested_map_ckey(key)

zarr/tests/test_storage.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -735,10 +735,10 @@ def test_deprecated(self):
735735

736736
class TestDirectoryStore(StoreTests, unittest.TestCase):
737737

738-
def create_store(self):
738+
def create_store(self, normalize_keys=False):
739739
path = tempfile.mkdtemp()
740740
atexit.register(atexit_rmtree, path)
741-
store = DirectoryStore(path)
741+
store = DirectoryStore(path, normalize_keys=normalize_keys)
742742
return store
743743

744744
def test_filesystem_path(self):
@@ -782,13 +782,19 @@ def test_setdel(self):
782782
store = self.create_store()
783783
setdel_hierarchy_checks(store)
784784

785+
def test_normalize_keys(self):
786+
store = self.create_store(normalize_keys=True)
787+
store['FOO'] = b'bar'
788+
assert 'FOO' in store
789+
assert 'foo' in store
790+
785791

786792
class TestNestedDirectoryStore(TestDirectoryStore, unittest.TestCase):
787793

788-
def create_store(self):
794+
def create_store(self, normalize_keys=False):
789795
path = tempfile.mkdtemp()
790796
atexit.register(atexit_rmtree, path)
791-
store = NestedDirectoryStore(path)
797+
store = NestedDirectoryStore(path, normalize_keys=normalize_keys)
792798
return store
793799

794800
def test_chunk_nesting(self):
@@ -806,10 +812,10 @@ def test_chunk_nesting(self):
806812

807813
class TestN5Store(TestNestedDirectoryStore, unittest.TestCase):
808814

809-
def create_store(self):
815+
def create_store(self, normalize_keys=False):
810816
path = tempfile.mkdtemp(suffix='.n5')
811817
atexit.register(atexit_rmtree, path)
812-
store = N5Store(path)
818+
store = N5Store(path, normalize_keys=normalize_keys)
813819
return store
814820

815821
def test_equal(self):

0 commit comments

Comments
 (0)