Add FSStore - #546

Merged
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec
Sep 14, 2020
Merged

Add FSStore#546
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec

Conversation

@martindurant

@martindurantmartindurant commented Mar 17, 2020

Copy link
Copy Markdown
Member

Fixes#540
Ref #373 (comment) (@rabernat )

Introduces a short Store implementation for generic fsspec url+options. Allows both lowercasing of keys and choice between '.' and '/'-based ("nested") keys.

For testing, have hijacked the TestNestedDirectoryStore tests just as an example - this is not how things will remain.

@alimanfoo

Copy link
Copy Markdown
Member

Thanks Martin, great to see this.

@rabernat

Copy link
Copy Markdown
Contributor

Thanks a lot @martindurant for getting this started! Let me know when you're ready for a review or some feedback.

It would be really nice to implement getsize and rename as well.

@martindurant

Copy link
Copy Markdown
MemberAuthor

getsize is already there ( https://github.com/zarr-developers/zarr-python/pull/546/files#diff-31d15042dbeedbf2942ace2ad4b9b2e2R1022 )
Do you have the signature for rename?

@chrisroat

Copy link
Copy Markdown

@martindurant I was just reloading the underlying issue into my brain's cache this morning, and excited to see this. What is the overall plan, with regard to how this would interact with things like N5Store and NestedDirectoryStore? How can I help?

@martindurant

Copy link
Copy Markdown
MemberAuthor

The idea is, that nested and consolidated, at least, would be options to this store, no need for extra classes. I don't know enough about the N5 case to know if that is as simple.

As far as I know, the little code that is here "works", but needs a good deal of testing and doubtless some massage to get totally right. At the moment, the PR just hijacks some existing nested tests, which all then pass locally, and almost all on CI.

@chrisroat

Copy link
Copy Markdown

If this is to fix 540, then I would like to see how I could help update N5Store to use this. The N5Store is a NestedDirectoryStore (also a MutableMapping like FSStore) which lays out the data according the N5 spec.

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality. It's possible that N5Store could take a FSStore in it's c'tor, but that may not be the cleanest way to combine the N5, FS, and NestedDirectory functionality.

Is FSStore a possible replacement for a DirectoryStore?

Regarding the comment above about the rename method, here is the DirectoryStore
implementation.

@martindurant

Copy link
Copy Markdown
MemberAuthor

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality

In this POC, FSStore is tested as if it were a nested store, but it can do both, depending on the arguments passed. That's what I meant bu not needing to have separate classes.

here is the DirectoryStore implementation.

OK, so simple enough

@alimanfoo

Copy link
Copy Markdown
Member

Just to say FWIW I think the best way forward for N5Store is to modify it so it becomes a transformation layer over another store, as suggested here and in zarr-developers/n5py#9. That way N5Store can be used with any type of underlying storage. I.e., work on N5Store can be orthogonal to this PR.

@chrisroat

Copy link
Copy Markdown

@martindurant I did a quick swap on N5Store to have it inherit from FSStore (and use the FSStore listdir within N5Store.listdir), and it does seem to correctly read/write a single array to GCS. I didn't test other functionality, like groups or anything related to listdir explicitly.

@alimanfoo OK. I'll pick up the discussion about the correct interaction of N5Store and FSStore on 540.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Right, there will be problems - so far I only ran the nestedstore tests, and see that they pass. You must of course pass key_separator='/' to the init, which for a subclass would be

 super().__init__(key_separator='/', other-kwargs)

I haven't got very far yet, but the code here should show that it isn't too complicated.

Comment threadzarr/storage.py Outdated
Martin Durant added 3 commits April 16, 2020 09:46
This demonstrates the purpose of this PR!
Now allows, for exmple `zarr.open('http://localhost:8000')`,
although there is no way to pass on further args with this method
yet.
@pep8speaks

pep8speaks commented Apr 16, 2020

Copy link
Copy Markdown

Hello @martindurant! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:

There are currently no PEP 8 issues detected in this Pull Request. Cheers! 🍻

Comment last updated at 2020-09-11 14:08:37 UTC

Comment threadzarr/storage.py Outdated

def __init__(self, url, normalize_keys=True, key_separator='.',
**storage_options):
mode='r', **storage_options):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change, the TestNestedDirectoryStore failure becomes:

_____________________________________________ TestNestedDirectoryStore.test_chunk_nesting _____________________________________________
self = <zarr.tests.test_storage.TestNestedDirectoryStore testMethod=test_chunk_nesting>
def test_chunk_nesting(self):
store = self.create_store()
# any path where last segment looks like a chunk key gets special handling
> store['0.0'] = b'xxx'
zarr/tests/test_storage.py:773:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <zarr.storage.FSStore object at 0x7fb7501c2c18>, key = '0.0', value = b'xxx'
def __setitem__(self, key, value):
if self.mode == 'r':
> raise PermissionError
E PermissionError
zarr/storage.py:974: PermissionError

We realized recently that the default mode for a regular DirectoryStore is w meaning that directories are automatically created:

In [18]: z=zarr.open('this/file/does/not/exist')
In [19]: list(z.groups()), list(z.arrays())
Out[19]: ([], [])

I was debating suggesting a change to "r" on a separate issue, but we'll probably need to keep it in mind for this PR as well.

If I force a "w" mode:

@@ -764,7 +764,7 @@ class TestNestedDirectoryStore(TestDirectoryStore, unittest.TestCase):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = NestedDirectoryStore(path, normalize_keys=normalize_keys,
- key_separator='/', auto_mkdir=True)
+ key_separator='/', auto_mkdir=True, mode="w")
return store

then I again see the previous error:

zarr/tests/test_storage.py:230:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
zarr/storage.py:970: in __getitem__
return self.map[key]
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/mapping.py:76: in __getitem__
result = self.fs.cat(k)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:587: in cat
return self.open(path, "rb").read()
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:775: in open
**kwargs
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:108: in _open
return LocalFileOpener(path, mode, fs=self, **kwargs)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:175: in __init__
self._open()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <fsspec.implementations.local.LocalFileOpener object at 0x7faa10221908>
def _open(self):
if self.f is None or self.f.closed:
if self.autocommit or "w" not in self.mode:
> self.f = open(self.path, mode=self.mode)
E IsADirectoryError: [Errno 21] Is a directory: '/var/folders/z5/txc_jj6x5l5cm81r56ck1n9c0000gn/T/tmpwebfv41i/c'
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:180: IsADirectoryError

which seems related, but I'm unclear how to get started with it, since it seems to be saying that fsspec expects one of the intermediate keys to be a file rather than a directory. I'll keep poking around but suggestions welcome.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably should have started with the non-nested stores for testing...
Exactly when directory trees should be removed and when files should be removed to make way for directories, I tries to reverse-engineer from the existing tests.

The actual error here, is that zarr expects a KeyError when trying to read "c", but fsspec as of fsspec/filesystem_spec#259 only converts FileNotFound into a KeyError. I suppose IsADirectory should be caught too, or the code should call fs.isfile()?

Comment threadzarr/tests/test_storage.py Outdated
@martindurant

Copy link
Copy Markdown
MemberAuthor

This appears to pass all tests with fsspec master, where I have substituted FSStore for DirectoryStore and NestedDirectoryStore in test_storage (not the final solution!).

Note that FSStore now directly implements consolidation, also in write mode (which is controversial, since it makes each metadata write take two remote calls, and introduces possible sync problems).

@martindurant

Copy link
Copy Markdown
MemberAuthor

Would it be useful to have fsspec master in this POC to show it will pass?

@rabernat

Copy link
Copy Markdown
Contributor

I appreciate all the work on this from @martindurant. At the same time, I do find it confusing to hear things like "FSStore now directly implements consolidation". We should have a clear separation between a storage layer and the zarr layer. It makes me nervous to see more features seeping into the storage layer.

@martindurant

Copy link
Copy Markdown
MemberAuthor

We should have a clear separation between a storage layer and the zarr layer

In my mind, fsspec is the storage layer, and everything in zarr.storage can be specific/specialised (such as the original conslolidated). You could use the previous consolidated with FSStore if you wanted, but this new version seems to me to remove duplication, as well as adding write-mode.

On of the issues I am trying to solve is "which store do I subclass": anyone coming to zarr and wanting to implement something new now would have to accommodate regular keys, nested keys or consolidated, not any combination.

Comment threadzarr/storage.py Outdated
self.fs = self.map.fs # for direct operations
self.mode = mode
self.exceptions = exceptions
# TODO: should warn if consolidated and write mode?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO ?

Comment threadzarr/storage.py Outdated
self.consolidated = consolidated
self.metadata_key = metadata_key
if consolidated:
self.meta = json.loads(self.map.get(metadata_key, b"{}").decode())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a json_loads utils function used in other place of this file:

def json_loads(s):
"""Read JSON in a consistent way."""
return json.loads(ensure_text(s, 'ascii'))

Do you want to use it ?

@alimanfoo

Copy link
Copy Markdown
Member

Dropping in comments from today's discussion on the community call. I have no objection to this PR, would be great to see it go in. Two points that come up are:

  • Should this store support consolidated metadata internally? Or can it let that be handled in the layer above? I would have a mild preference to let this be handled in the layer above, but happy to go either way.

  • Should this store support a chunk key separator argument? Or can it let that be handled in a layer above via composable stores? Ultimately composable stores would seem like a good idea, but given they don't exist yet it could be useful for @joshmoore and others to have FSStore support this for now.

@martindurant happy for you to make the call on how to round this one off.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Has anyone any idea why numcodecs failed to build during the docs stage here ?

@joshmoore

Copy link
Copy Markdown
Member

Has anyone any idea why numcodecs failed to build during the docs stage here ?

Due to the now yanked https://pypi.org/project/numcodecs/0.7.0/ as we try to get the wheels deployed. (Sorry 'bout that)

@martindurant

Copy link
Copy Markdown
MemberAuthor

Should the problem go away then?

Also, I have coverage failure even though "169 of 169 new or added lines in 5 files covered. (100.0%)" (because fsspec is not tested on py35?). That's annoying...

@martindurant

Copy link
Copy Markdown
MemberAuthor

(never mind on the latter, coverage just updated itself)

@joshmoore

joshmoore commented Sep 10, 2020

Copy link
Copy Markdown
Member

Should the problem go away then?

It should already be gone! Oddly travis is still finding it, perhaps because of some caching??

https://files.pythonhosted.org/packages/a1/b2/9c4fc0e4bc10a59442ced40dafc474f31bdc49699479da2e8912714e88af/numcodecs-0.7.0.tar.gz (3.0MB) ...

All the more reason to get 0.7.1 out ASAP.

@martindurant

Copy link
Copy Markdown
MemberAuthor

@joshmoore : is that S3 test OK for you?

@martindurant

Copy link
Copy Markdown
MemberAuthor

(I plan to merge this as is, unless there are further comments)

@joshmoorejoshmoore left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests pass, thanks @martindurant. I also tried adding a ```@pytest.mark.skipif(have_fsspec is False, reason="needs fsspec")
class TestNestedFSStore2(TestFSStore):

def create_store(self, normalize_keys=False):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = FSStore(path, normalize_keys=normalize_keys,
key_separator='/', auto_mkdir=True)
return store
locally which equally passed. :+1:

@martindurant
martindurant merged commit bb6b905 into zarr-developers:masterSep 14, 2020
@martindurant
martindurant deleted the fsspec branch September 14, 2020 13:41
@CarreauCarreau added this to the v2.5 milestone Sep 14, 2020
@shoyer

Copy link
Copy Markdown
Contributor

Quick question -- is there a good reason why FSStore defaults to normalize_keys=True, unlike the other Zarr stores? We were pretty surprised to find different behavior using Zarr directly and GCSFS.

@joshmoore

Copy link
Copy Markdown
Member

I had the same question. I've need to hard-code the argument in all of my uses. cc: @martindurant

@martindurant

Copy link
Copy Markdown
MemberAuthor

I certainly don't mind the default changing, if that is the consensus. I would like to claim there was a good reason for the current , but ...

@joshmoore

Copy link
Copy Markdown
Member

Bubbling this up into a new issue: #739

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

N5Store support of cloud buckets

8 participants

@martindurant@alimanfoo@rabernat@chrisroat@pep8speaks@joshmoore@shoyer@Carreau
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Add FSStore - #546

Merged
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec
Sep 14, 2020
Merged

Add FSStore#546
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec

Conversation

@martindurant

@martindurantmartindurant commented Mar 17, 2020

Copy link
Copy Markdown
Member

Fixes#540
Ref #373 (comment) (@rabernat )

Introduces a short Store implementation for generic fsspec url+options. Allows both lowercasing of keys and choice between '.' and '/'-based ("nested") keys.

For testing, have hijacked the TestNestedDirectoryStore tests just as an example - this is not how things will remain.

@alimanfoo

Copy link
Copy Markdown
Member

Thanks Martin, great to see this.

@rabernat

Copy link
Copy Markdown
Contributor

Thanks a lot @martindurant for getting this started! Let me know when you're ready for a review or some feedback.

It would be really nice to implement getsize and rename as well.

@martindurant

Copy link
Copy Markdown
MemberAuthor

getsize is already there ( https://github.com/zarr-developers/zarr-python/pull/546/files#diff-31d15042dbeedbf2942ace2ad4b9b2e2R1022 )
Do you have the signature for rename?

@chrisroat

Copy link
Copy Markdown

@martindurant I was just reloading the underlying issue into my brain's cache this morning, and excited to see this. What is the overall plan, with regard to how this would interact with things like N5Store and NestedDirectoryStore? How can I help?

@martindurant

Copy link
Copy Markdown
MemberAuthor

The idea is, that nested and consolidated, at least, would be options to this store, no need for extra classes. I don't know enough about the N5 case to know if that is as simple.

As far as I know, the little code that is here "works", but needs a good deal of testing and doubtless some massage to get totally right. At the moment, the PR just hijacks some existing nested tests, which all then pass locally, and almost all on CI.

@chrisroat

Copy link
Copy Markdown

If this is to fix 540, then I would like to see how I could help update N5Store to use this. The N5Store is a NestedDirectoryStore (also a MutableMapping like FSStore) which lays out the data according the N5 spec.

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality. It's possible that N5Store could take a FSStore in it's c'tor, but that may not be the cleanest way to combine the N5, FS, and NestedDirectory functionality.

Is FSStore a possible replacement for a DirectoryStore?

Regarding the comment above about the rename method, here is the DirectoryStore
implementation.

@martindurant

Copy link
Copy Markdown
MemberAuthor

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality

In this POC, FSStore is tested as if it were a nested store, but it can do both, depending on the arguments passed. That's what I meant bu not needing to have separate classes.

here is the DirectoryStore implementation.

OK, so simple enough

@alimanfoo

Copy link
Copy Markdown
Member

Just to say FWIW I think the best way forward for N5Store is to modify it so it becomes a transformation layer over another store, as suggested here and in zarr-developers/n5py#9. That way N5Store can be used with any type of underlying storage. I.e., work on N5Store can be orthogonal to this PR.

@chrisroat

Copy link
Copy Markdown

@martindurant I did a quick swap on N5Store to have it inherit from FSStore (and use the FSStore listdir within N5Store.listdir), and it does seem to correctly read/write a single array to GCS. I didn't test other functionality, like groups or anything related to listdir explicitly.

@alimanfoo OK. I'll pick up the discussion about the correct interaction of N5Store and FSStore on 540.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Right, there will be problems - so far I only ran the nestedstore tests, and see that they pass. You must of course pass key_separator='/' to the init, which for a subclass would be

 super().__init__(key_separator='/', other-kwargs)

I haven't got very far yet, but the code here should show that it isn't too complicated.

Comment threadzarr/storage.py Outdated
Martin Durant added 3 commits April 16, 2020 09:46
This demonstrates the purpose of this PR!
Now allows, for exmple `zarr.open('http://localhost:8000')`,
although there is no way to pass on further args with this method
yet.
@pep8speaks

pep8speaks commented Apr 16, 2020

Copy link
Copy Markdown

Hello @martindurant! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:

There are currently no PEP 8 issues detected in this Pull Request. Cheers! 🍻

Comment last updated at 2020-09-11 14:08:37 UTC

Comment threadzarr/storage.py Outdated

def __init__(self, url, normalize_keys=True, key_separator='.',
**storage_options):
mode='r', **storage_options):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change, the TestNestedDirectoryStore failure becomes:

_____________________________________________ TestNestedDirectoryStore.test_chunk_nesting _____________________________________________
self = <zarr.tests.test_storage.TestNestedDirectoryStore testMethod=test_chunk_nesting>
def test_chunk_nesting(self):
store = self.create_store()
# any path where last segment looks like a chunk key gets special handling
> store['0.0'] = b'xxx'
zarr/tests/test_storage.py:773:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <zarr.storage.FSStore object at 0x7fb7501c2c18>, key = '0.0', value = b'xxx'
def __setitem__(self, key, value):
if self.mode == 'r':
> raise PermissionError
E PermissionError
zarr/storage.py:974: PermissionError

We realized recently that the default mode for a regular DirectoryStore is w meaning that directories are automatically created:

In [18]: z=zarr.open('this/file/does/not/exist')
In [19]: list(z.groups()), list(z.arrays())
Out[19]: ([], [])

I was debating suggesting a change to "r" on a separate issue, but we'll probably need to keep it in mind for this PR as well.

If I force a "w" mode:

@@ -764,7 +764,7 @@ class TestNestedDirectoryStore(TestDirectoryStore, unittest.TestCase):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = NestedDirectoryStore(path, normalize_keys=normalize_keys,
- key_separator='/', auto_mkdir=True)
+ key_separator='/', auto_mkdir=True, mode="w")
return store

then I again see the previous error:

zarr/tests/test_storage.py:230:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
zarr/storage.py:970: in __getitem__
return self.map[key]
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/mapping.py:76: in __getitem__
result = self.fs.cat(k)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:587: in cat
return self.open(path, "rb").read()
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:775: in open
**kwargs
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:108: in _open
return LocalFileOpener(path, mode, fs=self, **kwargs)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:175: in __init__
self._open()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <fsspec.implementations.local.LocalFileOpener object at 0x7faa10221908>
def _open(self):
if self.f is None or self.f.closed:
if self.autocommit or "w" not in self.mode:
> self.f = open(self.path, mode=self.mode)
E IsADirectoryError: [Errno 21] Is a directory: '/var/folders/z5/txc_jj6x5l5cm81r56ck1n9c0000gn/T/tmpwebfv41i/c'
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:180: IsADirectoryError

which seems related, but I'm unclear how to get started with it, since it seems to be saying that fsspec expects one of the intermediate keys to be a file rather than a directory. I'll keep poking around but suggestions welcome.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably should have started with the non-nested stores for testing...
Exactly when directory trees should be removed and when files should be removed to make way for directories, I tries to reverse-engineer from the existing tests.

The actual error here, is that zarr expects a KeyError when trying to read "c", but fsspec as of fsspec/filesystem_spec#259 only converts FileNotFound into a KeyError. I suppose IsADirectory should be caught too, or the code should call fs.isfile()?

Comment threadzarr/tests/test_storage.py Outdated
@martindurant

Copy link
Copy Markdown
MemberAuthor

This appears to pass all tests with fsspec master, where I have substituted FSStore for DirectoryStore and NestedDirectoryStore in test_storage (not the final solution!).

Note that FSStore now directly implements consolidation, also in write mode (which is controversial, since it makes each metadata write take two remote calls, and introduces possible sync problems).

@martindurant

Copy link
Copy Markdown
MemberAuthor

Would it be useful to have fsspec master in this POC to show it will pass?

@rabernat

Copy link
Copy Markdown
Contributor

I appreciate all the work on this from @martindurant. At the same time, I do find it confusing to hear things like "FSStore now directly implements consolidation". We should have a clear separation between a storage layer and the zarr layer. It makes me nervous to see more features seeping into the storage layer.

@martindurant

Copy link
Copy Markdown
MemberAuthor

We should have a clear separation between a storage layer and the zarr layer

In my mind, fsspec is the storage layer, and everything in zarr.storage can be specific/specialised (such as the original conslolidated). You could use the previous consolidated with FSStore if you wanted, but this new version seems to me to remove duplication, as well as adding write-mode.

On of the issues I am trying to solve is "which store do I subclass": anyone coming to zarr and wanting to implement something new now would have to accommodate regular keys, nested keys or consolidated, not any combination.

Comment threadzarr/storage.py Outdated
self.fs = self.map.fs # for direct operations
self.mode = mode
self.exceptions = exceptions
# TODO: should warn if consolidated and write mode?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO ?

Comment threadzarr/storage.py Outdated
self.consolidated = consolidated
self.metadata_key = metadata_key
if consolidated:
self.meta = json.loads(self.map.get(metadata_key, b"{}").decode())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a json_loads utils function used in other place of this file:

def json_loads(s):
"""Read JSON in a consistent way."""
return json.loads(ensure_text(s, 'ascii'))

Do you want to use it ?

@alimanfoo

Copy link
Copy Markdown
Member

Dropping in comments from today's discussion on the community call. I have no objection to this PR, would be great to see it go in. Two points that come up are:

  • Should this store support consolidated metadata internally? Or can it let that be handled in the layer above? I would have a mild preference to let this be handled in the layer above, but happy to go either way.

  • Should this store support a chunk key separator argument? Or can it let that be handled in a layer above via composable stores? Ultimately composable stores would seem like a good idea, but given they don't exist yet it could be useful for @joshmoore and others to have FSStore support this for now.

@martindurant happy for you to make the call on how to round this one off.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Has anyone any idea why numcodecs failed to build during the docs stage here ?

@joshmoore

Copy link
Copy Markdown
Member

Has anyone any idea why numcodecs failed to build during the docs stage here ?

Due to the now yanked https://pypi.org/project/numcodecs/0.7.0/ as we try to get the wheels deployed. (Sorry 'bout that)

@martindurant

Copy link
Copy Markdown
MemberAuthor

Should the problem go away then?

Also, I have coverage failure even though "169 of 169 new or added lines in 5 files covered. (100.0%)" (because fsspec is not tested on py35?). That's annoying...

@martindurant

Copy link
Copy Markdown
MemberAuthor

(never mind on the latter, coverage just updated itself)

@joshmoore

joshmoore commented Sep 10, 2020

Copy link
Copy Markdown
Member

Should the problem go away then?

It should already be gone! Oddly travis is still finding it, perhaps because of some caching??

https://files.pythonhosted.org/packages/a1/b2/9c4fc0e4bc10a59442ced40dafc474f31bdc49699479da2e8912714e88af/numcodecs-0.7.0.tar.gz (3.0MB) ...

All the more reason to get 0.7.1 out ASAP.

@martindurant

Copy link
Copy Markdown
MemberAuthor

@joshmoore : is that S3 test OK for you?

@martindurant

Copy link
Copy Markdown
MemberAuthor

(I plan to merge this as is, unless there are further comments)

@joshmoorejoshmoore left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests pass, thanks @martindurant. I also tried adding a ```@pytest.mark.skipif(have_fsspec is False, reason="needs fsspec")
class TestNestedFSStore2(TestFSStore):

def create_store(self, normalize_keys=False):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = FSStore(path, normalize_keys=normalize_keys,
key_separator='/', auto_mkdir=True)
return store
locally which equally passed. :+1:

@martindurant
martindurant merged commit bb6b905 into zarr-developers:masterSep 14, 2020
@martindurant
martindurant deleted the fsspec branch September 14, 2020 13:41
@CarreauCarreau added this to the v2.5 milestone Sep 14, 2020
@shoyer

Copy link
Copy Markdown
Contributor

Quick question -- is there a good reason why FSStore defaults to normalize_keys=True, unlike the other Zarr stores? We were pretty surprised to find different behavior using Zarr directly and GCSFS.

@joshmoore

Copy link
Copy Markdown
Member

I had the same question. I've need to hard-code the argument in all of my uses. cc: @martindurant

@martindurant

Copy link
Copy Markdown
MemberAuthor

I certainly don't mind the default changing, if that is the consensus. I would like to claim there was a good reason for the current , but ...

@joshmoore

Copy link
Copy Markdown
Member

Bubbling this up into a new issue: #739

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

N5Store support of cloud buckets

8 participants

@martindurant@alimanfoo@rabernat@chrisroat@pep8speaks@joshmoore@shoyer@Carreau
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add FSStore - #546

Merged
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec
Sep 14, 2020
Merged

Add FSStore#546
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec

Conversation

@martindurant

@martindurantmartindurant commented Mar 17, 2020

Copy link
Copy Markdown
Member

Fixes#540
Ref #373 (comment) (@rabernat )

Introduces a short Store implementation for generic fsspec url+options. Allows both lowercasing of keys and choice between '.' and '/'-based ("nested") keys.

For testing, have hijacked the TestNestedDirectoryStore tests just as an example - this is not how things will remain.

@alimanfoo

Copy link
Copy Markdown
Member

Thanks Martin, great to see this.

@rabernat

Copy link
Copy Markdown
Contributor

Thanks a lot @martindurant for getting this started! Let me know when you're ready for a review or some feedback.

It would be really nice to implement getsize and rename as well.

@martindurant

Copy link
Copy Markdown
MemberAuthor

getsize is already there ( https://github.com/zarr-developers/zarr-python/pull/546/files#diff-31d15042dbeedbf2942ace2ad4b9b2e2R1022 )
Do you have the signature for rename?

@chrisroat

Copy link
Copy Markdown

@martindurant I was just reloading the underlying issue into my brain's cache this morning, and excited to see this. What is the overall plan, with regard to how this would interact with things like N5Store and NestedDirectoryStore? How can I help?

@martindurant

Copy link
Copy Markdown
MemberAuthor

The idea is, that nested and consolidated, at least, would be options to this store, no need for extra classes. I don't know enough about the N5 case to know if that is as simple.

As far as I know, the little code that is here "works", but needs a good deal of testing and doubtless some massage to get totally right. At the moment, the PR just hijacks some existing nested tests, which all then pass locally, and almost all on CI.

@chrisroat

Copy link
Copy Markdown

If this is to fix 540, then I would like to see how I could help update N5Store to use this. The N5Store is a NestedDirectoryStore (also a MutableMapping like FSStore) which lays out the data according the N5 spec.

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality. It's possible that N5Store could take a FSStore in it's c'tor, but that may not be the cleanest way to combine the N5, FS, and NestedDirectory functionality.

Is FSStore a possible replacement for a DirectoryStore?

Regarding the comment above about the rename method, here is the DirectoryStore
implementation.

@martindurant

Copy link
Copy Markdown
MemberAuthor

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality

In this POC, FSStore is tested as if it were a nested store, but it can do both, depending on the arguments passed. That's what I meant bu not needing to have separate classes.

here is the DirectoryStore implementation.

OK, so simple enough

@alimanfoo

Copy link
Copy Markdown
Member

Just to say FWIW I think the best way forward for N5Store is to modify it so it becomes a transformation layer over another store, as suggested here and in zarr-developers/n5py#9. That way N5Store can be used with any type of underlying storage. I.e., work on N5Store can be orthogonal to this PR.

@chrisroat

Copy link
Copy Markdown

@martindurant I did a quick swap on N5Store to have it inherit from FSStore (and use the FSStore listdir within N5Store.listdir), and it does seem to correctly read/write a single array to GCS. I didn't test other functionality, like groups or anything related to listdir explicitly.

@alimanfoo OK. I'll pick up the discussion about the correct interaction of N5Store and FSStore on 540.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Right, there will be problems - so far I only ran the nestedstore tests, and see that they pass. You must of course pass key_separator='/' to the init, which for a subclass would be

 super().__init__(key_separator='/', other-kwargs)

I haven't got very far yet, but the code here should show that it isn't too complicated.

Comment threadzarr/storage.py Outdated
Martin Durant added 3 commits April 16, 2020 09:46
This demonstrates the purpose of this PR!
Now allows, for exmple `zarr.open('http://localhost:8000')`,
although there is no way to pass on further args with this method
yet.
@pep8speaks

pep8speaks commented Apr 16, 2020

Copy link
Copy Markdown

Hello @martindurant! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:

There are currently no PEP 8 issues detected in this Pull Request. Cheers! 🍻

Comment last updated at 2020-09-11 14:08:37 UTC

Comment threadzarr/storage.py Outdated

def __init__(self, url, normalize_keys=True, key_separator='.',
**storage_options):
mode='r', **storage_options):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change, the TestNestedDirectoryStore failure becomes:

_____________________________________________ TestNestedDirectoryStore.test_chunk_nesting _____________________________________________
self = <zarr.tests.test_storage.TestNestedDirectoryStore testMethod=test_chunk_nesting>
def test_chunk_nesting(self):
store = self.create_store()
# any path where last segment looks like a chunk key gets special handling
> store['0.0'] = b'xxx'
zarr/tests/test_storage.py:773:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <zarr.storage.FSStore object at 0x7fb7501c2c18>, key = '0.0', value = b'xxx'
def __setitem__(self, key, value):
if self.mode == 'r':
> raise PermissionError
E PermissionError
zarr/storage.py:974: PermissionError

We realized recently that the default mode for a regular DirectoryStore is w meaning that directories are automatically created:

In [18]: z=zarr.open('this/file/does/not/exist')
In [19]: list(z.groups()), list(z.arrays())
Out[19]: ([], [])

I was debating suggesting a change to "r" on a separate issue, but we'll probably need to keep it in mind for this PR as well.

If I force a "w" mode:

@@ -764,7 +764,7 @@ class TestNestedDirectoryStore(TestDirectoryStore, unittest.TestCase):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = NestedDirectoryStore(path, normalize_keys=normalize_keys,
- key_separator='/', auto_mkdir=True)
+ key_separator='/', auto_mkdir=True, mode="w")
return store

then I again see the previous error:

zarr/tests/test_storage.py:230:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
zarr/storage.py:970: in __getitem__
return self.map[key]
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/mapping.py:76: in __getitem__
result = self.fs.cat(k)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:587: in cat
return self.open(path, "rb").read()
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:775: in open
**kwargs
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:108: in _open
return LocalFileOpener(path, mode, fs=self, **kwargs)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:175: in __init__
self._open()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <fsspec.implementations.local.LocalFileOpener object at 0x7faa10221908>
def _open(self):
if self.f is None or self.f.closed:
if self.autocommit or "w" not in self.mode:
> self.f = open(self.path, mode=self.mode)
E IsADirectoryError: [Errno 21] Is a directory: '/var/folders/z5/txc_jj6x5l5cm81r56ck1n9c0000gn/T/tmpwebfv41i/c'
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:180: IsADirectoryError

which seems related, but I'm unclear how to get started with it, since it seems to be saying that fsspec expects one of the intermediate keys to be a file rather than a directory. I'll keep poking around but suggestions welcome.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably should have started with the non-nested stores for testing...
Exactly when directory trees should be removed and when files should be removed to make way for directories, I tries to reverse-engineer from the existing tests.

The actual error here, is that zarr expects a KeyError when trying to read "c", but fsspec as of fsspec/filesystem_spec#259 only converts FileNotFound into a KeyError. I suppose IsADirectory should be caught too, or the code should call fs.isfile()?

Comment threadzarr/tests/test_storage.py Outdated
@martindurant

Copy link
Copy Markdown
MemberAuthor

This appears to pass all tests with fsspec master, where I have substituted FSStore for DirectoryStore and NestedDirectoryStore in test_storage (not the final solution!).

Note that FSStore now directly implements consolidation, also in write mode (which is controversial, since it makes each metadata write take two remote calls, and introduces possible sync problems).

@martindurant

Copy link
Copy Markdown
MemberAuthor

Would it be useful to have fsspec master in this POC to show it will pass?

@rabernat

Copy link
Copy Markdown
Contributor

I appreciate all the work on this from @martindurant. At the same time, I do find it confusing to hear things like "FSStore now directly implements consolidation". We should have a clear separation between a storage layer and the zarr layer. It makes me nervous to see more features seeping into the storage layer.

@martindurant

Copy link
Copy Markdown
MemberAuthor

We should have a clear separation between a storage layer and the zarr layer

In my mind, fsspec is the storage layer, and everything in zarr.storage can be specific/specialised (such as the original conslolidated). You could use the previous consolidated with FSStore if you wanted, but this new version seems to me to remove duplication, as well as adding write-mode.

On of the issues I am trying to solve is "which store do I subclass": anyone coming to zarr and wanting to implement something new now would have to accommodate regular keys, nested keys or consolidated, not any combination.

Comment threadzarr/storage.py Outdated
self.fs = self.map.fs # for direct operations
self.mode = mode
self.exceptions = exceptions
# TODO: should warn if consolidated and write mode?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO ?

Comment threadzarr/storage.py Outdated
self.consolidated = consolidated
self.metadata_key = metadata_key
if consolidated:
self.meta = json.loads(self.map.get(metadata_key, b"{}").decode())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a json_loads utils function used in other place of this file:

def json_loads(s):
"""Read JSON in a consistent way."""
return json.loads(ensure_text(s, 'ascii'))

Do you want to use it ?

@alimanfoo

Copy link
Copy Markdown
Member

Dropping in comments from today's discussion on the community call. I have no objection to this PR, would be great to see it go in. Two points that come up are:

  • Should this store support consolidated metadata internally? Or can it let that be handled in the layer above? I would have a mild preference to let this be handled in the layer above, but happy to go either way.

  • Should this store support a chunk key separator argument? Or can it let that be handled in a layer above via composable stores? Ultimately composable stores would seem like a good idea, but given they don't exist yet it could be useful for @joshmoore and others to have FSStore support this for now.

@martindurant happy for you to make the call on how to round this one off.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Has anyone any idea why numcodecs failed to build during the docs stage here ?

@joshmoore

Copy link
Copy Markdown
Member

Has anyone any idea why numcodecs failed to build during the docs stage here ?

Due to the now yanked https://pypi.org/project/numcodecs/0.7.0/ as we try to get the wheels deployed. (Sorry 'bout that)

@martindurant

Copy link
Copy Markdown
MemberAuthor

Should the problem go away then?

Also, I have coverage failure even though "169 of 169 new or added lines in 5 files covered. (100.0%)" (because fsspec is not tested on py35?). That's annoying...

@martindurant

Copy link
Copy Markdown
MemberAuthor

(never mind on the latter, coverage just updated itself)

@joshmoore

joshmoore commented Sep 10, 2020

Copy link
Copy Markdown
Member

Should the problem go away then?

It should already be gone! Oddly travis is still finding it, perhaps because of some caching??

https://files.pythonhosted.org/packages/a1/b2/9c4fc0e4bc10a59442ced40dafc474f31bdc49699479da2e8912714e88af/numcodecs-0.7.0.tar.gz (3.0MB) ...

All the more reason to get 0.7.1 out ASAP.

@martindurant

Copy link
Copy Markdown
MemberAuthor

@joshmoore : is that S3 test OK for you?

@martindurant

Copy link
Copy Markdown
MemberAuthor

(I plan to merge this as is, unless there are further comments)

@joshmoorejoshmoore left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests pass, thanks @martindurant. I also tried adding a ```@pytest.mark.skipif(have_fsspec is False, reason="needs fsspec")
class TestNestedFSStore2(TestFSStore):

def create_store(self, normalize_keys=False):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = FSStore(path, normalize_keys=normalize_keys,
key_separator='/', auto_mkdir=True)
return store
locally which equally passed. :+1:

@martindurant
martindurant merged commit bb6b905 into zarr-developers:masterSep 14, 2020
@martindurant
martindurant deleted the fsspec branch September 14, 2020 13:41
@CarreauCarreau added this to the v2.5 milestone Sep 14, 2020
@shoyer

Copy link
Copy Markdown
Contributor

Quick question -- is there a good reason why FSStore defaults to normalize_keys=True, unlike the other Zarr stores? We were pretty surprised to find different behavior using Zarr directly and GCSFS.

@joshmoore

Copy link
Copy Markdown
Member

I had the same question. I've need to hard-code the argument in all of my uses. cc: @martindurant

@martindurant

Copy link
Copy Markdown
MemberAuthor

I certainly don't mind the default changing, if that is the consensus. I would like to claim there was a good reason for the current , but ...

@joshmoore

Copy link
Copy Markdown
Member

Bubbling this up into a new issue: #739

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

N5Store support of cloud buckets

8 participants

@martindurant@alimanfoo@rabernat@chrisroat@pep8speaks@joshmoore@shoyer@Carreau
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add FSStore - #546

Merged
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec
Sep 14, 2020
Merged

Add FSStore#546
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec

Conversation

@martindurant

@martindurantmartindurant commented Mar 17, 2020

Copy link
Copy Markdown
Member

Fixes#540
Ref #373 (comment) (@rabernat )

Introduces a short Store implementation for generic fsspec url+options. Allows both lowercasing of keys and choice between '.' and '/'-based ("nested") keys.

For testing, have hijacked the TestNestedDirectoryStore tests just as an example - this is not how things will remain.

@alimanfoo

Copy link
Copy Markdown
Member

Thanks Martin, great to see this.

@rabernat

Copy link
Copy Markdown
Contributor

Thanks a lot @martindurant for getting this started! Let me know when you're ready for a review or some feedback.

It would be really nice to implement getsize and rename as well.

@martindurant

Copy link
Copy Markdown
MemberAuthor

getsize is already there ( https://github.com/zarr-developers/zarr-python/pull/546/files#diff-31d15042dbeedbf2942ace2ad4b9b2e2R1022 )
Do you have the signature for rename?

@chrisroat

Copy link
Copy Markdown

@martindurant I was just reloading the underlying issue into my brain's cache this morning, and excited to see this. What is the overall plan, with regard to how this would interact with things like N5Store and NestedDirectoryStore? How can I help?

@martindurant

Copy link
Copy Markdown
MemberAuthor

The idea is, that nested and consolidated, at least, would be options to this store, no need for extra classes. I don't know enough about the N5 case to know if that is as simple.

As far as I know, the little code that is here "works", but needs a good deal of testing and doubtless some massage to get totally right. At the moment, the PR just hijacks some existing nested tests, which all then pass locally, and almost all on CI.

@chrisroat

Copy link
Copy Markdown

If this is to fix 540, then I would like to see how I could help update N5Store to use this. The N5Store is a NestedDirectoryStore (also a MutableMapping like FSStore) which lays out the data according the N5 spec.

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality. It's possible that N5Store could take a FSStore in it's c'tor, but that may not be the cleanest way to combine the N5, FS, and NestedDirectory functionality.

Is FSStore a possible replacement for a DirectoryStore?

Regarding the comment above about the rename method, here is the DirectoryStore
implementation.

@martindurant

Copy link
Copy Markdown
MemberAuthor

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality

In this POC, FSStore is tested as if it were a nested store, but it can do both, depending on the arguments passed. That's what I meant bu not needing to have separate classes.

here is the DirectoryStore implementation.

OK, so simple enough

@alimanfoo

Copy link
Copy Markdown
Member

Just to say FWIW I think the best way forward for N5Store is to modify it so it becomes a transformation layer over another store, as suggested here and in zarr-developers/n5py#9. That way N5Store can be used with any type of underlying storage. I.e., work on N5Store can be orthogonal to this PR.

@chrisroat

Copy link
Copy Markdown

@martindurant I did a quick swap on N5Store to have it inherit from FSStore (and use the FSStore listdir within N5Store.listdir), and it does seem to correctly read/write a single array to GCS. I didn't test other functionality, like groups or anything related to listdir explicitly.

@alimanfoo OK. I'll pick up the discussion about the correct interaction of N5Store and FSStore on 540.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Right, there will be problems - so far I only ran the nestedstore tests, and see that they pass. You must of course pass key_separator='/' to the init, which for a subclass would be

 super().__init__(key_separator='/', other-kwargs)

I haven't got very far yet, but the code here should show that it isn't too complicated.

Comment threadzarr/storage.py Outdated
Martin Durant added 3 commits April 16, 2020 09:46
This demonstrates the purpose of this PR!
Now allows, for exmple `zarr.open('http://localhost:8000')`,
although there is no way to pass on further args with this method
yet.
@pep8speaks

pep8speaks commented Apr 16, 2020

Copy link
Copy Markdown

Hello @martindurant! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:

There are currently no PEP 8 issues detected in this Pull Request. Cheers! 🍻

Comment last updated at 2020-09-11 14:08:37 UTC

Comment threadzarr/storage.py Outdated

def __init__(self, url, normalize_keys=True, key_separator='.',
**storage_options):
mode='r', **storage_options):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change, the TestNestedDirectoryStore failure becomes:

_____________________________________________ TestNestedDirectoryStore.test_chunk_nesting _____________________________________________
self = <zarr.tests.test_storage.TestNestedDirectoryStore testMethod=test_chunk_nesting>
def test_chunk_nesting(self):
store = self.create_store()
# any path where last segment looks like a chunk key gets special handling
> store['0.0'] = b'xxx'
zarr/tests/test_storage.py:773:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <zarr.storage.FSStore object at 0x7fb7501c2c18>, key = '0.0', value = b'xxx'
def __setitem__(self, key, value):
if self.mode == 'r':
> raise PermissionError
E PermissionError
zarr/storage.py:974: PermissionError

We realized recently that the default mode for a regular DirectoryStore is w meaning that directories are automatically created:

In [18]: z=zarr.open('this/file/does/not/exist')
In [19]: list(z.groups()), list(z.arrays())
Out[19]: ([], [])

I was debating suggesting a change to "r" on a separate issue, but we'll probably need to keep it in mind for this PR as well.

If I force a "w" mode:

@@ -764,7 +764,7 @@ class TestNestedDirectoryStore(TestDirectoryStore, unittest.TestCase):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = NestedDirectoryStore(path, normalize_keys=normalize_keys,
- key_separator='/', auto_mkdir=True)
+ key_separator='/', auto_mkdir=True, mode="w")
return store

then I again see the previous error:

zarr/tests/test_storage.py:230:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
zarr/storage.py:970: in __getitem__
return self.map[key]
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/mapping.py:76: in __getitem__
result = self.fs.cat(k)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:587: in cat
return self.open(path, "rb").read()
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:775: in open
**kwargs
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:108: in _open
return LocalFileOpener(path, mode, fs=self, **kwargs)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:175: in __init__
self._open()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <fsspec.implementations.local.LocalFileOpener object at 0x7faa10221908>
def _open(self):
if self.f is None or self.f.closed:
if self.autocommit or "w" not in self.mode:
> self.f = open(self.path, mode=self.mode)
E IsADirectoryError: [Errno 21] Is a directory: '/var/folders/z5/txc_jj6x5l5cm81r56ck1n9c0000gn/T/tmpwebfv41i/c'
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:180: IsADirectoryError

which seems related, but I'm unclear how to get started with it, since it seems to be saying that fsspec expects one of the intermediate keys to be a file rather than a directory. I'll keep poking around but suggestions welcome.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably should have started with the non-nested stores for testing...
Exactly when directory trees should be removed and when files should be removed to make way for directories, I tries to reverse-engineer from the existing tests.

The actual error here, is that zarr expects a KeyError when trying to read "c", but fsspec as of fsspec/filesystem_spec#259 only converts FileNotFound into a KeyError. I suppose IsADirectory should be caught too, or the code should call fs.isfile()?

Comment threadzarr/tests/test_storage.py Outdated
@martindurant

Copy link
Copy Markdown
MemberAuthor

This appears to pass all tests with fsspec master, where I have substituted FSStore for DirectoryStore and NestedDirectoryStore in test_storage (not the final solution!).

Note that FSStore now directly implements consolidation, also in write mode (which is controversial, since it makes each metadata write take two remote calls, and introduces possible sync problems).

@martindurant

Copy link
Copy Markdown
MemberAuthor

Would it be useful to have fsspec master in this POC to show it will pass?

@rabernat

Copy link
Copy Markdown
Contributor

I appreciate all the work on this from @martindurant. At the same time, I do find it confusing to hear things like "FSStore now directly implements consolidation". We should have a clear separation between a storage layer and the zarr layer. It makes me nervous to see more features seeping into the storage layer.

@martindurant

Copy link
Copy Markdown
MemberAuthor

We should have a clear separation between a storage layer and the zarr layer

In my mind, fsspec is the storage layer, and everything in zarr.storage can be specific/specialised (such as the original conslolidated). You could use the previous consolidated with FSStore if you wanted, but this new version seems to me to remove duplication, as well as adding write-mode.

On of the issues I am trying to solve is "which store do I subclass": anyone coming to zarr and wanting to implement something new now would have to accommodate regular keys, nested keys or consolidated, not any combination.

Comment threadzarr/storage.py Outdated
self.fs = self.map.fs # for direct operations
self.mode = mode
self.exceptions = exceptions
# TODO: should warn if consolidated and write mode?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO ?

Comment threadzarr/storage.py Outdated
self.consolidated = consolidated
self.metadata_key = metadata_key
if consolidated:
self.meta = json.loads(self.map.get(metadata_key, b"{}").decode())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a json_loads utils function used in other place of this file:

def json_loads(s):
"""Read JSON in a consistent way."""
return json.loads(ensure_text(s, 'ascii'))

Do you want to use it ?

@alimanfoo

Copy link
Copy Markdown
Member

Dropping in comments from today's discussion on the community call. I have no objection to this PR, would be great to see it go in. Two points that come up are:

  • Should this store support consolidated metadata internally? Or can it let that be handled in the layer above? I would have a mild preference to let this be handled in the layer above, but happy to go either way.

  • Should this store support a chunk key separator argument? Or can it let that be handled in a layer above via composable stores? Ultimately composable stores would seem like a good idea, but given they don't exist yet it could be useful for @joshmoore and others to have FSStore support this for now.

@martindurant happy for you to make the call on how to round this one off.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Has anyone any idea why numcodecs failed to build during the docs stage here ?

@joshmoore

Copy link
Copy Markdown
Member

Has anyone any idea why numcodecs failed to build during the docs stage here ?

Due to the now yanked https://pypi.org/project/numcodecs/0.7.0/ as we try to get the wheels deployed. (Sorry 'bout that)

@martindurant

Copy link
Copy Markdown
MemberAuthor

Should the problem go away then?

Also, I have coverage failure even though "169 of 169 new or added lines in 5 files covered. (100.0%)" (because fsspec is not tested on py35?). That's annoying...

@martindurant

Copy link
Copy Markdown
MemberAuthor

(never mind on the latter, coverage just updated itself)

@joshmoore

joshmoore commented Sep 10, 2020

Copy link
Copy Markdown
Member

Should the problem go away then?

It should already be gone! Oddly travis is still finding it, perhaps because of some caching??

https://files.pythonhosted.org/packages/a1/b2/9c4fc0e4bc10a59442ced40dafc474f31bdc49699479da2e8912714e88af/numcodecs-0.7.0.tar.gz (3.0MB) ...

All the more reason to get 0.7.1 out ASAP.

@martindurant

Copy link
Copy Markdown
MemberAuthor

@joshmoore : is that S3 test OK for you?

@martindurant

Copy link
Copy Markdown
MemberAuthor

(I plan to merge this as is, unless there are further comments)

@joshmoorejoshmoore left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests pass, thanks @martindurant. I also tried adding a ```@pytest.mark.skipif(have_fsspec is False, reason="needs fsspec")
class TestNestedFSStore2(TestFSStore):

def create_store(self, normalize_keys=False):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = FSStore(path, normalize_keys=normalize_keys,
key_separator='/', auto_mkdir=True)
return store
locally which equally passed. :+1:

@martindurant
martindurant merged commit bb6b905 into zarr-developers:masterSep 14, 2020
@martindurant
martindurant deleted the fsspec branch September 14, 2020 13:41
@CarreauCarreau added this to the v2.5 milestone Sep 14, 2020
@shoyer

Copy link
Copy Markdown
Contributor

Quick question -- is there a good reason why FSStore defaults to normalize_keys=True, unlike the other Zarr stores? We were pretty surprised to find different behavior using Zarr directly and GCSFS.

@joshmoore

Copy link
Copy Markdown
Member

I had the same question. I've need to hard-code the argument in all of my uses. cc: @martindurant

@martindurant

Copy link
Copy Markdown
MemberAuthor

I certainly don't mind the default changing, if that is the consensus. I would like to claim there was a good reason for the current , but ...

@joshmoore

Copy link
Copy Markdown
Member

Bubbling this up into a new issue: #739

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

N5Store support of cloud buckets

8 participants

@martindurant@alimanfoo@rabernat@chrisroat@pep8speaks@joshmoore@shoyer@Carreau
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Add FSStore - #546

Merged
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec
Sep 14, 2020
Merged

Add FSStore#546
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec

Conversation

@martindurant

@martindurantmartindurant commented Mar 17, 2020

Copy link
Copy Markdown
Member

Fixes#540
Ref #373 (comment) (@rabernat )

Introduces a short Store implementation for generic fsspec url+options. Allows both lowercasing of keys and choice between '.' and '/'-based ("nested") keys.

For testing, have hijacked the TestNestedDirectoryStore tests just as an example - this is not how things will remain.

@alimanfoo

Copy link
Copy Markdown
Member

Thanks Martin, great to see this.

@rabernat

Copy link
Copy Markdown
Contributor

Thanks a lot @martindurant for getting this started! Let me know when you're ready for a review or some feedback.

It would be really nice to implement getsize and rename as well.

@martindurant

Copy link
Copy Markdown
MemberAuthor

getsize is already there ( https://github.com/zarr-developers/zarr-python/pull/546/files#diff-31d15042dbeedbf2942ace2ad4b9b2e2R1022 )
Do you have the signature for rename?

@chrisroat

Copy link
Copy Markdown

@martindurant I was just reloading the underlying issue into my brain's cache this morning, and excited to see this. What is the overall plan, with regard to how this would interact with things like N5Store and NestedDirectoryStore? How can I help?

@martindurant

Copy link
Copy Markdown
MemberAuthor

The idea is, that nested and consolidated, at least, would be options to this store, no need for extra classes. I don't know enough about the N5 case to know if that is as simple.

As far as I know, the little code that is here "works", but needs a good deal of testing and doubtless some massage to get totally right. At the moment, the PR just hijacks some existing nested tests, which all then pass locally, and almost all on CI.

@chrisroat

Copy link
Copy Markdown

If this is to fix 540, then I would like to see how I could help update N5Store to use this. The N5Store is a NestedDirectoryStore (also a MutableMapping like FSStore) which lays out the data according the N5 spec.

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality. It's possible that N5Store could take a FSStore in it's c'tor, but that may not be the cleanest way to combine the N5, FS, and NestedDirectory functionality.

Is FSStore a possible replacement for a DirectoryStore?

Regarding the comment above about the rename method, here is the DirectoryStore
implementation.

@martindurant

Copy link
Copy Markdown
MemberAuthor

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality

In this POC, FSStore is tested as if it were a nested store, but it can do both, depending on the arguments passed. That's what I meant bu not needing to have separate classes.

here is the DirectoryStore implementation.

OK, so simple enough

@alimanfoo

Copy link
Copy Markdown
Member

Just to say FWIW I think the best way forward for N5Store is to modify it so it becomes a transformation layer over another store, as suggested here and in zarr-developers/n5py#9. That way N5Store can be used with any type of underlying storage. I.e., work on N5Store can be orthogonal to this PR.

@chrisroat

Copy link
Copy Markdown

@martindurant I did a quick swap on N5Store to have it inherit from FSStore (and use the FSStore listdir within N5Store.listdir), and it does seem to correctly read/write a single array to GCS. I didn't test other functionality, like groups or anything related to listdir explicitly.

@alimanfoo OK. I'll pick up the discussion about the correct interaction of N5Store and FSStore on 540.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Right, there will be problems - so far I only ran the nestedstore tests, and see that they pass. You must of course pass key_separator='/' to the init, which for a subclass would be

 super().__init__(key_separator='/', other-kwargs)

I haven't got very far yet, but the code here should show that it isn't too complicated.

Comment threadzarr/storage.py Outdated
Martin Durant added 3 commits April 16, 2020 09:46
This demonstrates the purpose of this PR!
Now allows, for exmple `zarr.open('http://localhost:8000')`,
although there is no way to pass on further args with this method
yet.
@pep8speaks

pep8speaks commented Apr 16, 2020

Copy link
Copy Markdown

Hello @martindurant! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:

There are currently no PEP 8 issues detected in this Pull Request. Cheers! 🍻

Comment last updated at 2020-09-11 14:08:37 UTC

Comment threadzarr/storage.py Outdated

def __init__(self, url, normalize_keys=True, key_separator='.',
**storage_options):
mode='r', **storage_options):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change, the TestNestedDirectoryStore failure becomes:

_____________________________________________ TestNestedDirectoryStore.test_chunk_nesting _____________________________________________
self = <zarr.tests.test_storage.TestNestedDirectoryStore testMethod=test_chunk_nesting>
def test_chunk_nesting(self):
store = self.create_store()
# any path where last segment looks like a chunk key gets special handling
> store['0.0'] = b'xxx'
zarr/tests/test_storage.py:773:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <zarr.storage.FSStore object at 0x7fb7501c2c18>, key = '0.0', value = b'xxx'
def __setitem__(self, key, value):
if self.mode == 'r':
> raise PermissionError
E PermissionError
zarr/storage.py:974: PermissionError

We realized recently that the default mode for a regular DirectoryStore is w meaning that directories are automatically created:

In [18]: z=zarr.open('this/file/does/not/exist')
In [19]: list(z.groups()), list(z.arrays())
Out[19]: ([], [])

I was debating suggesting a change to "r" on a separate issue, but we'll probably need to keep it in mind for this PR as well.

If I force a "w" mode:

@@ -764,7 +764,7 @@ class TestNestedDirectoryStore(TestDirectoryStore, unittest.TestCase):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = NestedDirectoryStore(path, normalize_keys=normalize_keys,
- key_separator='/', auto_mkdir=True)
+ key_separator='/', auto_mkdir=True, mode="w")
return store

then I again see the previous error:

zarr/tests/test_storage.py:230:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
zarr/storage.py:970: in __getitem__
return self.map[key]
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/mapping.py:76: in __getitem__
result = self.fs.cat(k)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:587: in cat
return self.open(path, "rb").read()
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:775: in open
**kwargs
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:108: in _open
return LocalFileOpener(path, mode, fs=self, **kwargs)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:175: in __init__
self._open()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <fsspec.implementations.local.LocalFileOpener object at 0x7faa10221908>
def _open(self):
if self.f is None or self.f.closed:
if self.autocommit or "w" not in self.mode:
> self.f = open(self.path, mode=self.mode)
E IsADirectoryError: [Errno 21] Is a directory: '/var/folders/z5/txc_jj6x5l5cm81r56ck1n9c0000gn/T/tmpwebfv41i/c'
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:180: IsADirectoryError

which seems related, but I'm unclear how to get started with it, since it seems to be saying that fsspec expects one of the intermediate keys to be a file rather than a directory. I'll keep poking around but suggestions welcome.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably should have started with the non-nested stores for testing...
Exactly when directory trees should be removed and when files should be removed to make way for directories, I tries to reverse-engineer from the existing tests.

The actual error here, is that zarr expects a KeyError when trying to read "c", but fsspec as of fsspec/filesystem_spec#259 only converts FileNotFound into a KeyError. I suppose IsADirectory should be caught too, or the code should call fs.isfile()?

Comment threadzarr/tests/test_storage.py Outdated
@martindurant

Copy link
Copy Markdown
MemberAuthor

This appears to pass all tests with fsspec master, where I have substituted FSStore for DirectoryStore and NestedDirectoryStore in test_storage (not the final solution!).

Note that FSStore now directly implements consolidation, also in write mode (which is controversial, since it makes each metadata write take two remote calls, and introduces possible sync problems).

@martindurant

Copy link
Copy Markdown
MemberAuthor

Would it be useful to have fsspec master in this POC to show it will pass?

@rabernat

Copy link
Copy Markdown
Contributor

I appreciate all the work on this from @martindurant. At the same time, I do find it confusing to hear things like "FSStore now directly implements consolidation". We should have a clear separation between a storage layer and the zarr layer. It makes me nervous to see more features seeping into the storage layer.

@martindurant

Copy link
Copy Markdown
MemberAuthor

We should have a clear separation between a storage layer and the zarr layer

In my mind, fsspec is the storage layer, and everything in zarr.storage can be specific/specialised (such as the original conslolidated). You could use the previous consolidated with FSStore if you wanted, but this new version seems to me to remove duplication, as well as adding write-mode.

On of the issues I am trying to solve is "which store do I subclass": anyone coming to zarr and wanting to implement something new now would have to accommodate regular keys, nested keys or consolidated, not any combination.

Comment threadzarr/storage.py Outdated
self.fs = self.map.fs # for direct operations
self.mode = mode
self.exceptions = exceptions
# TODO: should warn if consolidated and write mode?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO ?

Comment threadzarr/storage.py Outdated
self.consolidated = consolidated
self.metadata_key = metadata_key
if consolidated:
self.meta = json.loads(self.map.get(metadata_key, b"{}").decode())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a json_loads utils function used in other place of this file:

def json_loads(s):
"""Read JSON in a consistent way."""
return json.loads(ensure_text(s, 'ascii'))

Do you want to use it ?

@alimanfoo

Copy link
Copy Markdown
Member

Dropping in comments from today's discussion on the community call. I have no objection to this PR, would be great to see it go in. Two points that come up are:

  • Should this store support consolidated metadata internally? Or can it let that be handled in the layer above? I would have a mild preference to let this be handled in the layer above, but happy to go either way.

  • Should this store support a chunk key separator argument? Or can it let that be handled in a layer above via composable stores? Ultimately composable stores would seem like a good idea, but given they don't exist yet it could be useful for @joshmoore and others to have FSStore support this for now.

@martindurant happy for you to make the call on how to round this one off.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Has anyone any idea why numcodecs failed to build during the docs stage here ?

@joshmoore

Copy link
Copy Markdown
Member

Has anyone any idea why numcodecs failed to build during the docs stage here ?

Due to the now yanked https://pypi.org/project/numcodecs/0.7.0/ as we try to get the wheels deployed. (Sorry 'bout that)

@martindurant

Copy link
Copy Markdown
MemberAuthor

Should the problem go away then?

Also, I have coverage failure even though "169 of 169 new or added lines in 5 files covered. (100.0%)" (because fsspec is not tested on py35?). That's annoying...

@martindurant

Copy link
Copy Markdown
MemberAuthor

(never mind on the latter, coverage just updated itself)

@joshmoore

joshmoore commented Sep 10, 2020

Copy link
Copy Markdown
Member

Should the problem go away then?

It should already be gone! Oddly travis is still finding it, perhaps because of some caching??

https://files.pythonhosted.org/packages/a1/b2/9c4fc0e4bc10a59442ced40dafc474f31bdc49699479da2e8912714e88af/numcodecs-0.7.0.tar.gz (3.0MB) ...

All the more reason to get 0.7.1 out ASAP.

@martindurant

Copy link
Copy Markdown
MemberAuthor

@joshmoore : is that S3 test OK for you?

@martindurant

Copy link
Copy Markdown
MemberAuthor

(I plan to merge this as is, unless there are further comments)

@joshmoorejoshmoore left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests pass, thanks @martindurant. I also tried adding a ```@pytest.mark.skipif(have_fsspec is False, reason="needs fsspec")
class TestNestedFSStore2(TestFSStore):

def create_store(self, normalize_keys=False):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = FSStore(path, normalize_keys=normalize_keys,
key_separator='/', auto_mkdir=True)
return store
locally which equally passed. :+1:

@martindurant
martindurant merged commit bb6b905 into zarr-developers:masterSep 14, 2020
@martindurant
martindurant deleted the fsspec branch September 14, 2020 13:41
@CarreauCarreau added this to the v2.5 milestone Sep 14, 2020
@shoyer

Copy link
Copy Markdown
Contributor

Quick question -- is there a good reason why FSStore defaults to normalize_keys=True, unlike the other Zarr stores? We were pretty surprised to find different behavior using Zarr directly and GCSFS.

@joshmoore

Copy link
Copy Markdown
Member

I had the same question. I've need to hard-code the argument in all of my uses. cc: @martindurant

@martindurant

Copy link
Copy Markdown
MemberAuthor

I certainly don't mind the default changing, if that is the consensus. I would like to claim there was a good reason for the current , but ...

@joshmoore

Copy link
Copy Markdown
Member

Bubbling this up into a new issue: #739

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

N5Store support of cloud buckets

8 participants

@martindurant@alimanfoo@rabernat@chrisroat@pep8speaks@joshmoore@shoyer@Carreau
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add FSStore - #546

Merged
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec
Sep 14, 2020
Merged

Add FSStore#546
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec

Conversation

@martindurant

@martindurantmartindurant commented Mar 17, 2020

Copy link
Copy Markdown
Member

Fixes#540
Ref #373 (comment) (@rabernat )

Introduces a short Store implementation for generic fsspec url+options. Allows both lowercasing of keys and choice between '.' and '/'-based ("nested") keys.

For testing, have hijacked the TestNestedDirectoryStore tests just as an example - this is not how things will remain.

@alimanfoo

Copy link
Copy Markdown
Member

Thanks Martin, great to see this.

@rabernat

Copy link
Copy Markdown
Contributor

Thanks a lot @martindurant for getting this started! Let me know when you're ready for a review or some feedback.

It would be really nice to implement getsize and rename as well.

@martindurant

Copy link
Copy Markdown
MemberAuthor

getsize is already there ( https://github.com/zarr-developers/zarr-python/pull/546/files#diff-31d15042dbeedbf2942ace2ad4b9b2e2R1022 )
Do you have the signature for rename?

@chrisroat

Copy link
Copy Markdown

@martindurant I was just reloading the underlying issue into my brain's cache this morning, and excited to see this. What is the overall plan, with regard to how this would interact with things like N5Store and NestedDirectoryStore? How can I help?

@martindurant

Copy link
Copy Markdown
MemberAuthor

The idea is, that nested and consolidated, at least, would be options to this store, no need for extra classes. I don't know enough about the N5 case to know if that is as simple.

As far as I know, the little code that is here "works", but needs a good deal of testing and doubtless some massage to get totally right. At the moment, the PR just hijacks some existing nested tests, which all then pass locally, and almost all on CI.

@chrisroat

Copy link
Copy Markdown

If this is to fix 540, then I would like to see how I could help update N5Store to use this. The N5Store is a NestedDirectoryStore (also a MutableMapping like FSStore) which lays out the data according the N5 spec.

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality. It's possible that N5Store could take a FSStore in it's c'tor, but that may not be the cleanest way to combine the N5, FS, and NestedDirectory functionality.

Is FSStore a possible replacement for a DirectoryStore?

Regarding the comment above about the rename method, here is the DirectoryStore
implementation.

@martindurant

Copy link
Copy Markdown
MemberAuthor

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality

In this POC, FSStore is tested as if it were a nested store, but it can do both, depending on the arguments passed. That's what I meant bu not needing to have separate classes.

here is the DirectoryStore implementation.

OK, so simple enough

@alimanfoo

Copy link
Copy Markdown
Member

Just to say FWIW I think the best way forward for N5Store is to modify it so it becomes a transformation layer over another store, as suggested here and in zarr-developers/n5py#9. That way N5Store can be used with any type of underlying storage. I.e., work on N5Store can be orthogonal to this PR.

@chrisroat

Copy link
Copy Markdown

@martindurant I did a quick swap on N5Store to have it inherit from FSStore (and use the FSStore listdir within N5Store.listdir), and it does seem to correctly read/write a single array to GCS. I didn't test other functionality, like groups or anything related to listdir explicitly.

@alimanfoo OK. I'll pick up the discussion about the correct interaction of N5Store and FSStore on 540.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Right, there will be problems - so far I only ran the nestedstore tests, and see that they pass. You must of course pass key_separator='/' to the init, which for a subclass would be

 super().__init__(key_separator='/', other-kwargs)

I haven't got very far yet, but the code here should show that it isn't too complicated.

Comment threadzarr/storage.py Outdated
Martin Durant added 3 commits April 16, 2020 09:46
This demonstrates the purpose of this PR!
Now allows, for exmple `zarr.open('http://localhost:8000')`,
although there is no way to pass on further args with this method
yet.
@pep8speaks

pep8speaks commented Apr 16, 2020

Copy link
Copy Markdown

Hello @martindurant! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:

There are currently no PEP 8 issues detected in this Pull Request. Cheers! 🍻

Comment last updated at 2020-09-11 14:08:37 UTC

Comment threadzarr/storage.py Outdated

def __init__(self, url, normalize_keys=True, key_separator='.',
**storage_options):
mode='r', **storage_options):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change, the TestNestedDirectoryStore failure becomes:

_____________________________________________ TestNestedDirectoryStore.test_chunk_nesting _____________________________________________
self = <zarr.tests.test_storage.TestNestedDirectoryStore testMethod=test_chunk_nesting>
def test_chunk_nesting(self):
store = self.create_store()
# any path where last segment looks like a chunk key gets special handling
> store['0.0'] = b'xxx'
zarr/tests/test_storage.py:773:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <zarr.storage.FSStore object at 0x7fb7501c2c18>, key = '0.0', value = b'xxx'
def __setitem__(self, key, value):
if self.mode == 'r':
> raise PermissionError
E PermissionError
zarr/storage.py:974: PermissionError

We realized recently that the default mode for a regular DirectoryStore is w meaning that directories are automatically created:

In [18]: z=zarr.open('this/file/does/not/exist')
In [19]: list(z.groups()), list(z.arrays())
Out[19]: ([], [])

I was debating suggesting a change to "r" on a separate issue, but we'll probably need to keep it in mind for this PR as well.

If I force a "w" mode:

@@ -764,7 +764,7 @@ class TestNestedDirectoryStore(TestDirectoryStore, unittest.TestCase):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = NestedDirectoryStore(path, normalize_keys=normalize_keys,
- key_separator='/', auto_mkdir=True)
+ key_separator='/', auto_mkdir=True, mode="w")
return store

then I again see the previous error:

zarr/tests/test_storage.py:230:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
zarr/storage.py:970: in __getitem__
return self.map[key]
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/mapping.py:76: in __getitem__
result = self.fs.cat(k)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:587: in cat
return self.open(path, "rb").read()
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:775: in open
**kwargs
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:108: in _open
return LocalFileOpener(path, mode, fs=self, **kwargs)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:175: in __init__
self._open()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <fsspec.implementations.local.LocalFileOpener object at 0x7faa10221908>
def _open(self):
if self.f is None or self.f.closed:
if self.autocommit or "w" not in self.mode:
> self.f = open(self.path, mode=self.mode)
E IsADirectoryError: [Errno 21] Is a directory: '/var/folders/z5/txc_jj6x5l5cm81r56ck1n9c0000gn/T/tmpwebfv41i/c'
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:180: IsADirectoryError

which seems related, but I'm unclear how to get started with it, since it seems to be saying that fsspec expects one of the intermediate keys to be a file rather than a directory. I'll keep poking around but suggestions welcome.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably should have started with the non-nested stores for testing...
Exactly when directory trees should be removed and when files should be removed to make way for directories, I tries to reverse-engineer from the existing tests.

The actual error here, is that zarr expects a KeyError when trying to read "c", but fsspec as of fsspec/filesystem_spec#259 only converts FileNotFound into a KeyError. I suppose IsADirectory should be caught too, or the code should call fs.isfile()?

Comment threadzarr/tests/test_storage.py Outdated
@martindurant

Copy link
Copy Markdown
MemberAuthor

This appears to pass all tests with fsspec master, where I have substituted FSStore for DirectoryStore and NestedDirectoryStore in test_storage (not the final solution!).

Note that FSStore now directly implements consolidation, also in write mode (which is controversial, since it makes each metadata write take two remote calls, and introduces possible sync problems).

@martindurant

Copy link
Copy Markdown
MemberAuthor

Would it be useful to have fsspec master in this POC to show it will pass?

@rabernat

Copy link
Copy Markdown
Contributor

I appreciate all the work on this from @martindurant. At the same time, I do find it confusing to hear things like "FSStore now directly implements consolidation". We should have a clear separation between a storage layer and the zarr layer. It makes me nervous to see more features seeping into the storage layer.

@martindurant

Copy link
Copy Markdown
MemberAuthor

We should have a clear separation between a storage layer and the zarr layer

In my mind, fsspec is the storage layer, and everything in zarr.storage can be specific/specialised (such as the original conslolidated). You could use the previous consolidated with FSStore if you wanted, but this new version seems to me to remove duplication, as well as adding write-mode.

On of the issues I am trying to solve is "which store do I subclass": anyone coming to zarr and wanting to implement something new now would have to accommodate regular keys, nested keys or consolidated, not any combination.

Comment threadzarr/storage.py Outdated
self.fs = self.map.fs # for direct operations
self.mode = mode
self.exceptions = exceptions
# TODO: should warn if consolidated and write mode?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO ?

Comment threadzarr/storage.py Outdated
self.consolidated = consolidated
self.metadata_key = metadata_key
if consolidated:
self.meta = json.loads(self.map.get(metadata_key, b"{}").decode())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a json_loads utils function used in other place of this file:

def json_loads(s):
"""Read JSON in a consistent way."""
return json.loads(ensure_text(s, 'ascii'))

Do you want to use it ?

@alimanfoo

Copy link
Copy Markdown
Member

Dropping in comments from today's discussion on the community call. I have no objection to this PR, would be great to see it go in. Two points that come up are:

  • Should this store support consolidated metadata internally? Or can it let that be handled in the layer above? I would have a mild preference to let this be handled in the layer above, but happy to go either way.

  • Should this store support a chunk key separator argument? Or can it let that be handled in a layer above via composable stores? Ultimately composable stores would seem like a good idea, but given they don't exist yet it could be useful for @joshmoore and others to have FSStore support this for now.

@martindurant happy for you to make the call on how to round this one off.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Has anyone any idea why numcodecs failed to build during the docs stage here ?

@joshmoore

Copy link
Copy Markdown
Member

Has anyone any idea why numcodecs failed to build during the docs stage here ?

Due to the now yanked https://pypi.org/project/numcodecs/0.7.0/ as we try to get the wheels deployed. (Sorry 'bout that)

@martindurant

Copy link
Copy Markdown
MemberAuthor

Should the problem go away then?

Also, I have coverage failure even though "169 of 169 new or added lines in 5 files covered. (100.0%)" (because fsspec is not tested on py35?). That's annoying...

@martindurant

Copy link
Copy Markdown
MemberAuthor

(never mind on the latter, coverage just updated itself)

@joshmoore

joshmoore commented Sep 10, 2020

Copy link
Copy Markdown
Member

Should the problem go away then?

It should already be gone! Oddly travis is still finding it, perhaps because of some caching??

https://files.pythonhosted.org/packages/a1/b2/9c4fc0e4bc10a59442ced40dafc474f31bdc49699479da2e8912714e88af/numcodecs-0.7.0.tar.gz (3.0MB) ...

All the more reason to get 0.7.1 out ASAP.

@martindurant

Copy link
Copy Markdown
MemberAuthor

@joshmoore : is that S3 test OK for you?

@martindurant

Copy link
Copy Markdown
MemberAuthor

(I plan to merge this as is, unless there are further comments)

@joshmoorejoshmoore left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests pass, thanks @martindurant. I also tried adding a ```@pytest.mark.skipif(have_fsspec is False, reason="needs fsspec")
class TestNestedFSStore2(TestFSStore):

def create_store(self, normalize_keys=False):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = FSStore(path, normalize_keys=normalize_keys,
key_separator='/', auto_mkdir=True)
return store
locally which equally passed. :+1:

@martindurant
martindurant merged commit bb6b905 into zarr-developers:masterSep 14, 2020
@martindurant
martindurant deleted the fsspec branch September 14, 2020 13:41
@CarreauCarreau added this to the v2.5 milestone Sep 14, 2020
@shoyer

Copy link
Copy Markdown
Contributor

Quick question -- is there a good reason why FSStore defaults to normalize_keys=True, unlike the other Zarr stores? We were pretty surprised to find different behavior using Zarr directly and GCSFS.

@joshmoore

Copy link
Copy Markdown
Member

I had the same question. I've need to hard-code the argument in all of my uses. cc: @martindurant

@martindurant

Copy link
Copy Markdown
MemberAuthor

I certainly don't mind the default changing, if that is the consensus. I would like to claim there was a good reason for the current , but ...

@joshmoore

Copy link
Copy Markdown
Member

Bubbling this up into a new issue: #739

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

N5Store support of cloud buckets

8 participants

@martindurant@alimanfoo@rabernat@chrisroat@pep8speaks@joshmoore@shoyer@Carreau
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add FSStore - #546

Merged
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec
Sep 14, 2020
Merged

Add FSStore#546
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec

Conversation

@martindurant

@martindurantmartindurant commented Mar 17, 2020

Copy link
Copy Markdown
Member

Fixes#540
Ref #373 (comment) (@rabernat )

Introduces a short Store implementation for generic fsspec url+options. Allows both lowercasing of keys and choice between '.' and '/'-based ("nested") keys.

For testing, have hijacked the TestNestedDirectoryStore tests just as an example - this is not how things will remain.

@alimanfoo

Copy link
Copy Markdown
Member

Thanks Martin, great to see this.

@rabernat

Copy link
Copy Markdown
Contributor

Thanks a lot @martindurant for getting this started! Let me know when you're ready for a review or some feedback.

It would be really nice to implement getsize and rename as well.

@martindurant

Copy link
Copy Markdown
MemberAuthor

getsize is already there ( https://github.com/zarr-developers/zarr-python/pull/546/files#diff-31d15042dbeedbf2942ace2ad4b9b2e2R1022 )
Do you have the signature for rename?

@chrisroat

Copy link
Copy Markdown

@martindurant I was just reloading the underlying issue into my brain's cache this morning, and excited to see this. What is the overall plan, with regard to how this would interact with things like N5Store and NestedDirectoryStore? How can I help?

@martindurant

Copy link
Copy Markdown
MemberAuthor

The idea is, that nested and consolidated, at least, would be options to this store, no need for extra classes. I don't know enough about the N5 case to know if that is as simple.

As far as I know, the little code that is here "works", but needs a good deal of testing and doubtless some massage to get totally right. At the moment, the PR just hijacks some existing nested tests, which all then pass locally, and almost all on CI.

@chrisroat

Copy link
Copy Markdown

If this is to fix 540, then I would like to see how I could help update N5Store to use this. The N5Store is a NestedDirectoryStore (also a MutableMapping like FSStore) which lays out the data according the N5 spec.

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality. It's possible that N5Store could take a FSStore in it's c'tor, but that may not be the cleanest way to combine the N5, FS, and NestedDirectory functionality.

Is FSStore a possible replacement for a DirectoryStore?

Regarding the comment above about the rename method, here is the DirectoryStore
implementation.

@martindurant

Copy link
Copy Markdown
MemberAuthor

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality

In this POC, FSStore is tested as if it were a nested store, but it can do both, depending on the arguments passed. That's what I meant bu not needing to have separate classes.

here is the DirectoryStore implementation.

OK, so simple enough

@alimanfoo

Copy link
Copy Markdown
Member

Just to say FWIW I think the best way forward for N5Store is to modify it so it becomes a transformation layer over another store, as suggested here and in zarr-developers/n5py#9. That way N5Store can be used with any type of underlying storage. I.e., work on N5Store can be orthogonal to this PR.

@chrisroat

Copy link
Copy Markdown

@martindurant I did a quick swap on N5Store to have it inherit from FSStore (and use the FSStore listdir within N5Store.listdir), and it does seem to correctly read/write a single array to GCS. I didn't test other functionality, like groups or anything related to listdir explicitly.

@alimanfoo OK. I'll pick up the discussion about the correct interaction of N5Store and FSStore on 540.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Right, there will be problems - so far I only ran the nestedstore tests, and see that they pass. You must of course pass key_separator='/' to the init, which for a subclass would be

 super().__init__(key_separator='/', other-kwargs)

I haven't got very far yet, but the code here should show that it isn't too complicated.

Comment threadzarr/storage.py Outdated
Martin Durant added 3 commits April 16, 2020 09:46
This demonstrates the purpose of this PR!
Now allows, for exmple `zarr.open('http://localhost:8000')`,
although there is no way to pass on further args with this method
yet.
@pep8speaks

pep8speaks commented Apr 16, 2020

Copy link
Copy Markdown

Hello @martindurant! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:

There are currently no PEP 8 issues detected in this Pull Request. Cheers! 🍻

Comment last updated at 2020-09-11 14:08:37 UTC

Comment threadzarr/storage.py Outdated

def __init__(self, url, normalize_keys=True, key_separator='.',
**storage_options):
mode='r', **storage_options):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change, the TestNestedDirectoryStore failure becomes:

_____________________________________________ TestNestedDirectoryStore.test_chunk_nesting _____________________________________________
self = <zarr.tests.test_storage.TestNestedDirectoryStore testMethod=test_chunk_nesting>
def test_chunk_nesting(self):
store = self.create_store()
# any path where last segment looks like a chunk key gets special handling
> store['0.0'] = b'xxx'
zarr/tests/test_storage.py:773:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <zarr.storage.FSStore object at 0x7fb7501c2c18>, key = '0.0', value = b'xxx'
def __setitem__(self, key, value):
if self.mode == 'r':
> raise PermissionError
E PermissionError
zarr/storage.py:974: PermissionError

We realized recently that the default mode for a regular DirectoryStore is w meaning that directories are automatically created:

In [18]: z=zarr.open('this/file/does/not/exist')
In [19]: list(z.groups()), list(z.arrays())
Out[19]: ([], [])

I was debating suggesting a change to "r" on a separate issue, but we'll probably need to keep it in mind for this PR as well.

If I force a "w" mode:

@@ -764,7 +764,7 @@ class TestNestedDirectoryStore(TestDirectoryStore, unittest.TestCase):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = NestedDirectoryStore(path, normalize_keys=normalize_keys,
- key_separator='/', auto_mkdir=True)
+ key_separator='/', auto_mkdir=True, mode="w")
return store

then I again see the previous error:

zarr/tests/test_storage.py:230:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
zarr/storage.py:970: in __getitem__
return self.map[key]
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/mapping.py:76: in __getitem__
result = self.fs.cat(k)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:587: in cat
return self.open(path, "rb").read()
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:775: in open
**kwargs
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:108: in _open
return LocalFileOpener(path, mode, fs=self, **kwargs)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:175: in __init__
self._open()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <fsspec.implementations.local.LocalFileOpener object at 0x7faa10221908>
def _open(self):
if self.f is None or self.f.closed:
if self.autocommit or "w" not in self.mode:
> self.f = open(self.path, mode=self.mode)
E IsADirectoryError: [Errno 21] Is a directory: '/var/folders/z5/txc_jj6x5l5cm81r56ck1n9c0000gn/T/tmpwebfv41i/c'
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:180: IsADirectoryError

which seems related, but I'm unclear how to get started with it, since it seems to be saying that fsspec expects one of the intermediate keys to be a file rather than a directory. I'll keep poking around but suggestions welcome.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably should have started with the non-nested stores for testing...
Exactly when directory trees should be removed and when files should be removed to make way for directories, I tries to reverse-engineer from the existing tests.

The actual error here, is that zarr expects a KeyError when trying to read "c", but fsspec as of fsspec/filesystem_spec#259 only converts FileNotFound into a KeyError. I suppose IsADirectory should be caught too, or the code should call fs.isfile()?

Comment threadzarr/tests/test_storage.py Outdated
@martindurant

Copy link
Copy Markdown
MemberAuthor

This appears to pass all tests with fsspec master, where I have substituted FSStore for DirectoryStore and NestedDirectoryStore in test_storage (not the final solution!).

Note that FSStore now directly implements consolidation, also in write mode (which is controversial, since it makes each metadata write take two remote calls, and introduces possible sync problems).

@martindurant

Copy link
Copy Markdown
MemberAuthor

Would it be useful to have fsspec master in this POC to show it will pass?

@rabernat

Copy link
Copy Markdown
Contributor

I appreciate all the work on this from @martindurant. At the same time, I do find it confusing to hear things like "FSStore now directly implements consolidation". We should have a clear separation between a storage layer and the zarr layer. It makes me nervous to see more features seeping into the storage layer.

@martindurant

Copy link
Copy Markdown
MemberAuthor

We should have a clear separation between a storage layer and the zarr layer

In my mind, fsspec is the storage layer, and everything in zarr.storage can be specific/specialised (such as the original conslolidated). You could use the previous consolidated with FSStore if you wanted, but this new version seems to me to remove duplication, as well as adding write-mode.

On of the issues I am trying to solve is "which store do I subclass": anyone coming to zarr and wanting to implement something new now would have to accommodate regular keys, nested keys or consolidated, not any combination.

Comment threadzarr/storage.py Outdated
self.fs = self.map.fs # for direct operations
self.mode = mode
self.exceptions = exceptions
# TODO: should warn if consolidated and write mode?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO ?

Comment threadzarr/storage.py Outdated
self.consolidated = consolidated
self.metadata_key = metadata_key
if consolidated:
self.meta = json.loads(self.map.get(metadata_key, b"{}").decode())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a json_loads utils function used in other place of this file:

def json_loads(s):
"""Read JSON in a consistent way."""
return json.loads(ensure_text(s, 'ascii'))

Do you want to use it ?

@alimanfoo

Copy link
Copy Markdown
Member

Dropping in comments from today's discussion on the community call. I have no objection to this PR, would be great to see it go in. Two points that come up are:

  • Should this store support consolidated metadata internally? Or can it let that be handled in the layer above? I would have a mild preference to let this be handled in the layer above, but happy to go either way.

  • Should this store support a chunk key separator argument? Or can it let that be handled in a layer above via composable stores? Ultimately composable stores would seem like a good idea, but given they don't exist yet it could be useful for @joshmoore and others to have FSStore support this for now.

@martindurant happy for you to make the call on how to round this one off.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Has anyone any idea why numcodecs failed to build during the docs stage here ?

@joshmoore

Copy link
Copy Markdown
Member

Has anyone any idea why numcodecs failed to build during the docs stage here ?

Due to the now yanked https://pypi.org/project/numcodecs/0.7.0/ as we try to get the wheels deployed. (Sorry 'bout that)

@martindurant

Copy link
Copy Markdown
MemberAuthor

Should the problem go away then?

Also, I have coverage failure even though "169 of 169 new or added lines in 5 files covered. (100.0%)" (because fsspec is not tested on py35?). That's annoying...

@martindurant

Copy link
Copy Markdown
MemberAuthor

(never mind on the latter, coverage just updated itself)

@joshmoore

joshmoore commented Sep 10, 2020

Copy link
Copy Markdown
Member

Should the problem go away then?

It should already be gone! Oddly travis is still finding it, perhaps because of some caching??

https://files.pythonhosted.org/packages/a1/b2/9c4fc0e4bc10a59442ced40dafc474f31bdc49699479da2e8912714e88af/numcodecs-0.7.0.tar.gz (3.0MB) ...

All the more reason to get 0.7.1 out ASAP.

@martindurant

Copy link
Copy Markdown
MemberAuthor

@joshmoore : is that S3 test OK for you?

@martindurant

Copy link
Copy Markdown
MemberAuthor

(I plan to merge this as is, unless there are further comments)

@joshmoorejoshmoore left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests pass, thanks @martindurant. I also tried adding a ```@pytest.mark.skipif(have_fsspec is False, reason="needs fsspec")
class TestNestedFSStore2(TestFSStore):

def create_store(self, normalize_keys=False):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = FSStore(path, normalize_keys=normalize_keys,
key_separator='/', auto_mkdir=True)
return store
locally which equally passed. :+1:

@martindurant
martindurant merged commit bb6b905 into zarr-developers:masterSep 14, 2020
@martindurant
martindurant deleted the fsspec branch September 14, 2020 13:41
@CarreauCarreau added this to the v2.5 milestone Sep 14, 2020
@shoyer

Copy link
Copy Markdown
Contributor

Quick question -- is there a good reason why FSStore defaults to normalize_keys=True, unlike the other Zarr stores? We were pretty surprised to find different behavior using Zarr directly and GCSFS.

@joshmoore

Copy link
Copy Markdown
Member

I had the same question. I've need to hard-code the argument in all of my uses. cc: @martindurant

@martindurant

Copy link
Copy Markdown
MemberAuthor

I certainly don't mind the default changing, if that is the consensus. I would like to claim there was a good reason for the current , but ...

@joshmoore

Copy link
Copy Markdown
Member

Bubbling this up into a new issue: #739

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

N5Store support of cloud buckets

8 participants

@martindurant@alimanfoo@rabernat@chrisroat@pep8speaks@joshmoore@shoyer@Carreau
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Add FSStore - #546

Merged
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec
Sep 14, 2020
Merged

Add FSStore#546
martindurant merged 23 commits into
zarr-developers:masterfrom
martindurant:fsspec

Conversation

@martindurant

@martindurantmartindurant commented Mar 17, 2020

Copy link
Copy Markdown
Member

Fixes#540
Ref #373 (comment) (@rabernat )

Introduces a short Store implementation for generic fsspec url+options. Allows both lowercasing of keys and choice between '.' and '/'-based ("nested") keys.

For testing, have hijacked the TestNestedDirectoryStore tests just as an example - this is not how things will remain.

@alimanfoo

Copy link
Copy Markdown
Member

Thanks Martin, great to see this.

@rabernat

Copy link
Copy Markdown
Contributor

Thanks a lot @martindurant for getting this started! Let me know when you're ready for a review or some feedback.

It would be really nice to implement getsize and rename as well.

@martindurant

Copy link
Copy Markdown
MemberAuthor

getsize is already there ( https://github.com/zarr-developers/zarr-python/pull/546/files#diff-31d15042dbeedbf2942ace2ad4b9b2e2R1022 )
Do you have the signature for rename?

@chrisroat

Copy link
Copy Markdown

@martindurant I was just reloading the underlying issue into my brain's cache this morning, and excited to see this. What is the overall plan, with regard to how this would interact with things like N5Store and NestedDirectoryStore? How can I help?

@martindurant

Copy link
Copy Markdown
MemberAuthor

The idea is, that nested and consolidated, at least, would be options to this store, no need for extra classes. I don't know enough about the N5 case to know if that is as simple.

As far as I know, the little code that is here "works", but needs a good deal of testing and doubtless some massage to get totally right. At the moment, the PR just hijacks some existing nested tests, which all then pass locally, and almost all on CI.

@chrisroat

Copy link
Copy Markdown

If this is to fix 540, then I would like to see how I could help update N5Store to use this. The N5Store is a NestedDirectoryStore (also a MutableMapping like FSStore) which lays out the data according the N5 spec.

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality. It's possible that N5Store could take a FSStore in it's c'tor, but that may not be the cleanest way to combine the N5, FS, and NestedDirectory functionality.

Is FSStore a possible replacement for a DirectoryStore?

Regarding the comment above about the rename method, here is the DirectoryStore
implementation.

@martindurant

Copy link
Copy Markdown
MemberAuthor

I do not think N5Store can just inherit from FSStore, since it will lose the Directory/NestedDirectory functionality

In this POC, FSStore is tested as if it were a nested store, but it can do both, depending on the arguments passed. That's what I meant bu not needing to have separate classes.

here is the DirectoryStore implementation.

OK, so simple enough

@alimanfoo

Copy link
Copy Markdown
Member

Just to say FWIW I think the best way forward for N5Store is to modify it so it becomes a transformation layer over another store, as suggested here and in zarr-developers/n5py#9. That way N5Store can be used with any type of underlying storage. I.e., work on N5Store can be orthogonal to this PR.

@chrisroat

Copy link
Copy Markdown

@martindurant I did a quick swap on N5Store to have it inherit from FSStore (and use the FSStore listdir within N5Store.listdir), and it does seem to correctly read/write a single array to GCS. I didn't test other functionality, like groups or anything related to listdir explicitly.

@alimanfoo OK. I'll pick up the discussion about the correct interaction of N5Store and FSStore on 540.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Right, there will be problems - so far I only ran the nestedstore tests, and see that they pass. You must of course pass key_separator='/' to the init, which for a subclass would be

 super().__init__(key_separator='/', other-kwargs)

I haven't got very far yet, but the code here should show that it isn't too complicated.

Comment threadzarr/storage.py Outdated
Martin Durant added 3 commits April 16, 2020 09:46
This demonstrates the purpose of this PR!
Now allows, for exmple `zarr.open('http://localhost:8000')`,
although there is no way to pass on further args with this method
yet.
@pep8speaks

pep8speaks commented Apr 16, 2020

Copy link
Copy Markdown

Hello @martindurant! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:

There are currently no PEP 8 issues detected in this Pull Request. Cheers! 🍻

Comment last updated at 2020-09-11 14:08:37 UTC

Comment threadzarr/storage.py Outdated

def __init__(self, url, normalize_keys=True, key_separator='.',
**storage_options):
mode='r', **storage_options):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change, the TestNestedDirectoryStore failure becomes:

_____________________________________________ TestNestedDirectoryStore.test_chunk_nesting _____________________________________________
self = <zarr.tests.test_storage.TestNestedDirectoryStore testMethod=test_chunk_nesting>
def test_chunk_nesting(self):
store = self.create_store()
# any path where last segment looks like a chunk key gets special handling
> store['0.0'] = b'xxx'
zarr/tests/test_storage.py:773:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <zarr.storage.FSStore object at 0x7fb7501c2c18>, key = '0.0', value = b'xxx'
def __setitem__(self, key, value):
if self.mode == 'r':
> raise PermissionError
E PermissionError
zarr/storage.py:974: PermissionError

We realized recently that the default mode for a regular DirectoryStore is w meaning that directories are automatically created:

In [18]: z=zarr.open('this/file/does/not/exist')
In [19]: list(z.groups()), list(z.arrays())
Out[19]: ([], [])

I was debating suggesting a change to "r" on a separate issue, but we'll probably need to keep it in mind for this PR as well.

If I force a "w" mode:

@@ -764,7 +764,7 @@ class TestNestedDirectoryStore(TestDirectoryStore, unittest.TestCase):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = NestedDirectoryStore(path, normalize_keys=normalize_keys,
- key_separator='/', auto_mkdir=True)
+ key_separator='/', auto_mkdir=True, mode="w")
return store

then I again see the previous error:

zarr/tests/test_storage.py:230:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
zarr/storage.py:970: in __getitem__
return self.map[key]
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/mapping.py:76: in __getitem__
result = self.fs.cat(k)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:587: in cat
return self.open(path, "rb").read()
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/spec.py:775: in open
**kwargs
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:108: in _open
return LocalFileOpener(path, mode, fs=self, **kwargs)
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:175: in __init__
self._open()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <fsspec.implementations.local.LocalFileOpener object at 0x7faa10221908>
def _open(self):
if self.f is None or self.f.closed:
if self.autocommit or "w" not in self.mode:
> self.f = open(self.path, mode=self.mode)
E IsADirectoryError: [Errno 21] Is a directory: '/var/folders/z5/txc_jj6x5l5cm81r56ck1n9c0000gn/T/tmpwebfv41i/c'
/opt/anaconda/envs/py36/lib/python3.6/site-packages/fsspec/implementations/local.py:180: IsADirectoryError

which seems related, but I'm unclear how to get started with it, since it seems to be saying that fsspec expects one of the intermediate keys to be a file rather than a directory. I'll keep poking around but suggestions welcome.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably should have started with the non-nested stores for testing...
Exactly when directory trees should be removed and when files should be removed to make way for directories, I tries to reverse-engineer from the existing tests.

The actual error here, is that zarr expects a KeyError when trying to read "c", but fsspec as of fsspec/filesystem_spec#259 only converts FileNotFound into a KeyError. I suppose IsADirectory should be caught too, or the code should call fs.isfile()?

Comment threadzarr/tests/test_storage.py Outdated
@martindurant

Copy link
Copy Markdown
MemberAuthor

This appears to pass all tests with fsspec master, where I have substituted FSStore for DirectoryStore and NestedDirectoryStore in test_storage (not the final solution!).

Note that FSStore now directly implements consolidation, also in write mode (which is controversial, since it makes each metadata write take two remote calls, and introduces possible sync problems).

@martindurant

Copy link
Copy Markdown
MemberAuthor

Would it be useful to have fsspec master in this POC to show it will pass?

@rabernat

Copy link
Copy Markdown
Contributor

I appreciate all the work on this from @martindurant. At the same time, I do find it confusing to hear things like "FSStore now directly implements consolidation". We should have a clear separation between a storage layer and the zarr layer. It makes me nervous to see more features seeping into the storage layer.

@martindurant

Copy link
Copy Markdown
MemberAuthor

We should have a clear separation between a storage layer and the zarr layer

In my mind, fsspec is the storage layer, and everything in zarr.storage can be specific/specialised (such as the original conslolidated). You could use the previous consolidated with FSStore if you wanted, but this new version seems to me to remove duplication, as well as adding write-mode.

On of the issues I am trying to solve is "which store do I subclass": anyone coming to zarr and wanting to implement something new now would have to accommodate regular keys, nested keys or consolidated, not any combination.

Comment threadzarr/storage.py Outdated
self.fs = self.map.fs # for direct operations
self.mode = mode
self.exceptions = exceptions
# TODO: should warn if consolidated and write mode?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO ?

Comment threadzarr/storage.py Outdated
self.consolidated = consolidated
self.metadata_key = metadata_key
if consolidated:
self.meta = json.loads(self.map.get(metadata_key, b"{}").decode())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a json_loads utils function used in other place of this file:

def json_loads(s):
"""Read JSON in a consistent way."""
return json.loads(ensure_text(s, 'ascii'))

Do you want to use it ?

@alimanfoo

Copy link
Copy Markdown
Member

Dropping in comments from today's discussion on the community call. I have no objection to this PR, would be great to see it go in. Two points that come up are:

  • Should this store support consolidated metadata internally? Or can it let that be handled in the layer above? I would have a mild preference to let this be handled in the layer above, but happy to go either way.

  • Should this store support a chunk key separator argument? Or can it let that be handled in a layer above via composable stores? Ultimately composable stores would seem like a good idea, but given they don't exist yet it could be useful for @joshmoore and others to have FSStore support this for now.

@martindurant happy for you to make the call on how to round this one off.

@martindurant

Copy link
Copy Markdown
MemberAuthor

Has anyone any idea why numcodecs failed to build during the docs stage here ?

@joshmoore

Copy link
Copy Markdown
Member

Has anyone any idea why numcodecs failed to build during the docs stage here ?

Due to the now yanked https://pypi.org/project/numcodecs/0.7.0/ as we try to get the wheels deployed. (Sorry 'bout that)

@martindurant

Copy link
Copy Markdown
MemberAuthor

Should the problem go away then?

Also, I have coverage failure even though "169 of 169 new or added lines in 5 files covered. (100.0%)" (because fsspec is not tested on py35?). That's annoying...

@martindurant

Copy link
Copy Markdown
MemberAuthor

(never mind on the latter, coverage just updated itself)

@joshmoore

joshmoore commented Sep 10, 2020

Copy link
Copy Markdown
Member

Should the problem go away then?

It should already be gone! Oddly travis is still finding it, perhaps because of some caching??

https://files.pythonhosted.org/packages/a1/b2/9c4fc0e4bc10a59442ced40dafc474f31bdc49699479da2e8912714e88af/numcodecs-0.7.0.tar.gz (3.0MB) ...

All the more reason to get 0.7.1 out ASAP.

@martindurant

Copy link
Copy Markdown
MemberAuthor

@joshmoore : is that S3 test OK for you?

@martindurant

Copy link
Copy Markdown
MemberAuthor

(I plan to merge this as is, unless there are further comments)

@joshmoorejoshmoore left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests pass, thanks @martindurant. I also tried adding a ```@pytest.mark.skipif(have_fsspec is False, reason="needs fsspec")
class TestNestedFSStore2(TestFSStore):

def create_store(self, normalize_keys=False):
path = tempfile.mkdtemp()
atexit.register(atexit_rmtree, path)
store = FSStore(path, normalize_keys=normalize_keys,
key_separator='/', auto_mkdir=True)
return store
locally which equally passed. :+1:

@martindurant
martindurant merged commit bb6b905 into zarr-developers:masterSep 14, 2020
@martindurant
martindurant deleted the fsspec branch September 14, 2020 13:41
@CarreauCarreau added this to the v2.5 milestone Sep 14, 2020
@shoyer

Copy link
Copy Markdown
Contributor

Quick question -- is there a good reason why FSStore defaults to normalize_keys=True, unlike the other Zarr stores? We were pretty surprised to find different behavior using Zarr directly and GCSFS.

@joshmoore

Copy link
Copy Markdown
Member

I had the same question. I've need to hard-code the argument in all of my uses. cc: @martindurant

@martindurant

Copy link
Copy Markdown
MemberAuthor

I certainly don't mind the default changing, if that is the consensus. I would like to claim there was a good reason for the current , but ...

@joshmoore

Copy link
Copy Markdown
Member

Bubbling this up into a new issue: #739

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

N5Store support of cloud buckets

8 participants

@martindurant@alimanfoo@rabernat@chrisroat@pep8speaks@joshmoore@shoyer@Carreau