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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 40 additions & 27 deletions src/zarr/core/group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@
from zarr.store.common import ensure_no_existing_node

if TYPE_CHECKING:
from collections.abc import AsyncGenerator, Iterable, Iterator
from collections.abc import AsyncGenerator, Generator, Iterable, Iterator
from typing import Any

from zarr.abc.codec import Codec
Expand DownExpand Up@@ -678,29 +678,31 @@ async def contains(self, member: str) -> bool:
else:
return True

# todo: decide if this method should be separate from `groups`
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, value in self.members():
async def groups(self) -> AsyncGenerator[tuple[str, AsyncGroup], None]:
async for name, value in self.members():
if isinstance(value, AsyncGroup):
yield key
yield name, value

# todo: decide if this method should be separate from `group_keys`
async def groups(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, value in self.members():
if isinstance(value, AsyncGroup):
yield value
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.groups():
yield key

# todo: decide if this method should be separate from `arrays`
async def array_keys(self) -> AsyncGenerator[str, None]:
async def group_values(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, group in self.groups():
yield group

async def arrays(self) -> AsyncGenerator[tuple[str, AsyncArray], None]:
async for key, value in self.members():
if isinstance(value, AsyncArray):
yield key
yield key, value

# todo: decide if this method should be separate from `array_keys`
async def arrays(self) -> AsyncGenerator[AsyncArray, None]:
async for _, value in self.members():
if isinstance(value, AsyncArray):
yield value
async def array_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.arrays():
yield key

async def array_values(self) -> AsyncGenerator[AsyncArray, None]:
async for _, array in self.arrays():
yield array

async def tree(self, expand: bool = False, level: int | None = None) -> Any:
raise NotImplementedError
Expand DownExpand Up@@ -861,18 +863,29 @@ def members(self, max_depth: int | None = 0) -> tuple[tuple[str, Array | Group],
def __contains__(self, member: str) -> bool:
return self._sync(self._async_group.contains(member))

def group_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.group_keys()))
def groups(self) -> Generator[tuple[str, Group], None]:
for name, async_group in self._sync_iter(self._async_group.groups()):
yield name, Group(async_group)

def group_keys(self) -> Generator[str, None]:
for name, _ in self.groups():
yield name

def group_values(self) -> Generator[Group, None]:
for _, group in self.groups():
yield group

def groups(self) -> tuple[Group, ...]:
# TODO: in v2 this was a generator that return key: Group
return tuple(Group(obj) for obj in self._sync_iter(self._async_group.groups()))
def arrays(self) -> Generator[tuple[str, Array], None]:
for name, async_array in self._sync_iter(self._async_group.arrays()):
yield name, Array(async_array)

def array_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.array_keys()))
def array_keys(self) -> Generator[str, None]:
for name, _ in self.arrays():
yield name

def arrays(self) -> tuple[Array, ...]:
return tuple(Array(obj) for obj in self._sync_iter(self._async_group.arrays()))
def array_values(self) -> Generator[Array, None]:
for _, array in self.arrays():
yield array

def tree(self, expand: bool = False, level: int | None = None) -> Any:
return self._sync(self._async_group.tree(expand=expand, level=level))
Expand Down
44 changes: 19 additions & 25 deletions tests/v3/test_group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -301,34 +301,28 @@ def test_group_contains(store: Store, zarr_format: ZarrFormat) -> None:
assert "foo" in group


def test_group_subgroups(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
def test_group_child_iterators(store: Store, zarr_format: ZarrFormat):

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I decided to combine these tests since the setup is the same. And the test benefits from having both groups and arrays in the Group being accessed, since the code needs to filter to just child Groups / Arrays.

group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subgroups_expected = tuple(group.create_group(k) for k in keys)
# create a sub-array as well
_ = group.create_array("array", shape=(10,))
subgroups_observed = group.groups()
assert set(group.group_keys()) == set(keys)
assert len(subgroups_observed) == len(subgroups_expected)
assert all(a in subgroups_observed for a in subgroups_expected)
expected_group_keys = ["g0", "g1"]
expected_group_values = [group.create_group(name=name) for name in expected_group_keys]
expected_groups = list(zip(expected_group_keys, expected_group_values, strict=False))

expected_group_values[0].create_group("subgroup")
expected_group_values[0].create_array("subarray", shape=(1,))

def test_group_subarrays(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subarrays_expected = tuple(group.create_array(k, shape=(10,)) for k in keys)
# create a sub-group as well
_ = group.create_group("group")
subarrays_observed = group.arrays()
assert set(group.array_keys()) == set(keys)
assert len(subarrays_observed) == len(subarrays_expected)
assert all(a in subarrays_observed for a in subarrays_expected)
expected_array_keys = ["a0", "a1"]
expected_array_values = [
group.create_array(name=name, shape=(1,)) for name in expected_array_keys
]
expected_arrays = list(zip(expected_array_keys, expected_array_values, strict=False))

assert sorted(group.groups(), key=lambda x: x[0]) == expected_groups
assert sorted(group.group_keys()) == expected_group_keys
assert sorted(group.group_values(), key=lambda x: x.name) == expected_group_values

assert sorted(group.arrays(), key=lambda x: x[0]) == expected_arrays
assert sorted(group.array_keys()) == expected_array_keys
assert sorted(group.array_values(), key=lambda x: x.name) == expected_array_values


def test_group_update_attributes(store: Store, zarr_format: ZarrFormat) -> None:
Expand Down
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 40 additions & 27 deletions src/zarr/core/group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@
from zarr.store.common import ensure_no_existing_node

if TYPE_CHECKING:
from collections.abc import AsyncGenerator, Iterable, Iterator
from collections.abc import AsyncGenerator, Generator, Iterable, Iterator
from typing import Any

from zarr.abc.codec import Codec
Expand DownExpand Up@@ -678,29 +678,31 @@ async def contains(self, member: str) -> bool:
else:
return True

# todo: decide if this method should be separate from `groups`
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, value in self.members():
async def groups(self) -> AsyncGenerator[tuple[str, AsyncGroup], None]:
async for name, value in self.members():
if isinstance(value, AsyncGroup):
yield key
yield name, value

# todo: decide if this method should be separate from `group_keys`
async def groups(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, value in self.members():
if isinstance(value, AsyncGroup):
yield value
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.groups():
yield key

# todo: decide if this method should be separate from `arrays`
async def array_keys(self) -> AsyncGenerator[str, None]:
async def group_values(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, group in self.groups():
yield group

async def arrays(self) -> AsyncGenerator[tuple[str, AsyncArray], None]:
async for key, value in self.members():
if isinstance(value, AsyncArray):
yield key
yield key, value

# todo: decide if this method should be separate from `array_keys`
async def arrays(self) -> AsyncGenerator[AsyncArray, None]:
async for _, value in self.members():
if isinstance(value, AsyncArray):
yield value
async def array_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.arrays():
yield key

async def array_values(self) -> AsyncGenerator[AsyncArray, None]:
async for _, array in self.arrays():
yield array

async def tree(self, expand: bool = False, level: int | None = None) -> Any:
raise NotImplementedError
Expand DownExpand Up@@ -861,18 +863,29 @@ def members(self, max_depth: int | None = 0) -> tuple[tuple[str, Array | Group],
def __contains__(self, member: str) -> bool:
return self._sync(self._async_group.contains(member))

def group_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.group_keys()))
def groups(self) -> Generator[tuple[str, Group], None]:
for name, async_group in self._sync_iter(self._async_group.groups()):
yield name, Group(async_group)

def group_keys(self) -> Generator[str, None]:
for name, _ in self.groups():
yield name

def group_values(self) -> Generator[Group, None]:
for _, group in self.groups():
yield group

def groups(self) -> tuple[Group, ...]:
# TODO: in v2 this was a generator that return key: Group
return tuple(Group(obj) for obj in self._sync_iter(self._async_group.groups()))
def arrays(self) -> Generator[tuple[str, Array], None]:
for name, async_array in self._sync_iter(self._async_group.arrays()):
yield name, Array(async_array)

def array_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.array_keys()))
def array_keys(self) -> Generator[str, None]:
for name, _ in self.arrays():
yield name

def arrays(self) -> tuple[Array, ...]:
return tuple(Array(obj) for obj in self._sync_iter(self._async_group.arrays()))
def array_values(self) -> Generator[Array, None]:
for _, array in self.arrays():
yield array

def tree(self, expand: bool = False, level: int | None = None) -> Any:
return self._sync(self._async_group.tree(expand=expand, level=level))
Expand Down
44 changes: 19 additions & 25 deletions tests/v3/test_group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -301,34 +301,28 @@ def test_group_contains(store: Store, zarr_format: ZarrFormat) -> None:
assert "foo" in group


def test_group_subgroups(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
def test_group_child_iterators(store: Store, zarr_format: ZarrFormat):

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I decided to combine these tests since the setup is the same. And the test benefits from having both groups and arrays in the Group being accessed, since the code needs to filter to just child Groups / Arrays.

group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subgroups_expected = tuple(group.create_group(k) for k in keys)
# create a sub-array as well
_ = group.create_array("array", shape=(10,))
subgroups_observed = group.groups()
assert set(group.group_keys()) == set(keys)
assert len(subgroups_observed) == len(subgroups_expected)
assert all(a in subgroups_observed for a in subgroups_expected)
expected_group_keys = ["g0", "g1"]
expected_group_values = [group.create_group(name=name) for name in expected_group_keys]
expected_groups = list(zip(expected_group_keys, expected_group_values, strict=False))

expected_group_values[0].create_group("subgroup")
expected_group_values[0].create_array("subarray", shape=(1,))

def test_group_subarrays(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subarrays_expected = tuple(group.create_array(k, shape=(10,)) for k in keys)
# create a sub-group as well
_ = group.create_group("group")
subarrays_observed = group.arrays()
assert set(group.array_keys()) == set(keys)
assert len(subarrays_observed) == len(subarrays_expected)
assert all(a in subarrays_observed for a in subarrays_expected)
expected_array_keys = ["a0", "a1"]
expected_array_values = [
group.create_array(name=name, shape=(1,)) for name in expected_array_keys
]
expected_arrays = list(zip(expected_array_keys, expected_array_values, strict=False))

assert sorted(group.groups(), key=lambda x: x[0]) == expected_groups
assert sorted(group.group_keys()) == expected_group_keys
assert sorted(group.group_values(), key=lambda x: x.name) == expected_group_values

assert sorted(group.arrays(), key=lambda x: x[0]) == expected_arrays
assert sorted(group.array_keys()) == expected_array_keys
assert sorted(group.array_values(), key=lambda x: x.name) == expected_array_values


def test_group_update_attributes(store: Store, zarr_format: ZarrFormat) -> None:
Expand Down
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 40 additions & 27 deletions src/zarr/core/group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@
from zarr.store.common import ensure_no_existing_node

if TYPE_CHECKING:
from collections.abc import AsyncGenerator, Iterable, Iterator
from collections.abc import AsyncGenerator, Generator, Iterable, Iterator
from typing import Any

from zarr.abc.codec import Codec
Expand DownExpand Up@@ -678,29 +678,31 @@ async def contains(self, member: str) -> bool:
else:
return True

# todo: decide if this method should be separate from `groups`
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, value in self.members():
async def groups(self) -> AsyncGenerator[tuple[str, AsyncGroup], None]:
async for name, value in self.members():
if isinstance(value, AsyncGroup):
yield key
yield name, value

# todo: decide if this method should be separate from `group_keys`
async def groups(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, value in self.members():
if isinstance(value, AsyncGroup):
yield value
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.groups():
yield key

# todo: decide if this method should be separate from `arrays`
async def array_keys(self) -> AsyncGenerator[str, None]:
async def group_values(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, group in self.groups():
yield group

async def arrays(self) -> AsyncGenerator[tuple[str, AsyncArray], None]:
async for key, value in self.members():
if isinstance(value, AsyncArray):
yield key
yield key, value

# todo: decide if this method should be separate from `array_keys`
async def arrays(self) -> AsyncGenerator[AsyncArray, None]:
async for _, value in self.members():
if isinstance(value, AsyncArray):
yield value
async def array_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.arrays():
yield key

async def array_values(self) -> AsyncGenerator[AsyncArray, None]:
async for _, array in self.arrays():
yield array

async def tree(self, expand: bool = False, level: int | None = None) -> Any:
raise NotImplementedError
Expand DownExpand Up@@ -861,18 +863,29 @@ def members(self, max_depth: int | None = 0) -> tuple[tuple[str, Array | Group],
def __contains__(self, member: str) -> bool:
return self._sync(self._async_group.contains(member))

def group_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.group_keys()))
def groups(self) -> Generator[tuple[str, Group], None]:
for name, async_group in self._sync_iter(self._async_group.groups()):
yield name, Group(async_group)

def group_keys(self) -> Generator[str, None]:
for name, _ in self.groups():
yield name

def group_values(self) -> Generator[Group, None]:
for _, group in self.groups():
yield group

def groups(self) -> tuple[Group, ...]:
# TODO: in v2 this was a generator that return key: Group
return tuple(Group(obj) for obj in self._sync_iter(self._async_group.groups()))
def arrays(self) -> Generator[tuple[str, Array], None]:
for name, async_array in self._sync_iter(self._async_group.arrays()):
yield name, Array(async_array)

def array_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.array_keys()))
def array_keys(self) -> Generator[str, None]:
for name, _ in self.arrays():
yield name

def arrays(self) -> tuple[Array, ...]:
return tuple(Array(obj) for obj in self._sync_iter(self._async_group.arrays()))
def array_values(self) -> Generator[Array, None]:
for _, array in self.arrays():
yield array

def tree(self, expand: bool = False, level: int | None = None) -> Any:
return self._sync(self._async_group.tree(expand=expand, level=level))
Expand Down
44 changes: 19 additions & 25 deletions tests/v3/test_group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -301,34 +301,28 @@ def test_group_contains(store: Store, zarr_format: ZarrFormat) -> None:
assert "foo" in group


def test_group_subgroups(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
def test_group_child_iterators(store: Store, zarr_format: ZarrFormat):

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I decided to combine these tests since the setup is the same. And the test benefits from having both groups and arrays in the Group being accessed, since the code needs to filter to just child Groups / Arrays.

group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subgroups_expected = tuple(group.create_group(k) for k in keys)
# create a sub-array as well
_ = group.create_array("array", shape=(10,))
subgroups_observed = group.groups()
assert set(group.group_keys()) == set(keys)
assert len(subgroups_observed) == len(subgroups_expected)
assert all(a in subgroups_observed for a in subgroups_expected)
expected_group_keys = ["g0", "g1"]
expected_group_values = [group.create_group(name=name) for name in expected_group_keys]
expected_groups = list(zip(expected_group_keys, expected_group_values, strict=False))

expected_group_values[0].create_group("subgroup")
expected_group_values[0].create_array("subarray", shape=(1,))

def test_group_subarrays(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subarrays_expected = tuple(group.create_array(k, shape=(10,)) for k in keys)
# create a sub-group as well
_ = group.create_group("group")
subarrays_observed = group.arrays()
assert set(group.array_keys()) == set(keys)
assert len(subarrays_observed) == len(subarrays_expected)
assert all(a in subarrays_observed for a in subarrays_expected)
expected_array_keys = ["a0", "a1"]
expected_array_values = [
group.create_array(name=name, shape=(1,)) for name in expected_array_keys
]
expected_arrays = list(zip(expected_array_keys, expected_array_values, strict=False))

assert sorted(group.groups(), key=lambda x: x[0]) == expected_groups
assert sorted(group.group_keys()) == expected_group_keys
assert sorted(group.group_values(), key=lambda x: x.name) == expected_group_values

assert sorted(group.arrays(), key=lambda x: x[0]) == expected_arrays
assert sorted(group.array_keys()) == expected_array_keys
assert sorted(group.array_values(), key=lambda x: x.name) == expected_array_values


def test_group_update_attributes(store: Store, zarr_format: ZarrFormat) -> None:
Expand Down
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 40 additions & 27 deletions src/zarr/core/group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@
from zarr.store.common import ensure_no_existing_node

if TYPE_CHECKING:
from collections.abc import AsyncGenerator, Iterable, Iterator
from collections.abc import AsyncGenerator, Generator, Iterable, Iterator
from typing import Any

from zarr.abc.codec import Codec
Expand DownExpand Up@@ -678,29 +678,31 @@ async def contains(self, member: str) -> bool:
else:
return True

# todo: decide if this method should be separate from `groups`
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, value in self.members():
async def groups(self) -> AsyncGenerator[tuple[str, AsyncGroup], None]:
async for name, value in self.members():
if isinstance(value, AsyncGroup):
yield key
yield name, value

# todo: decide if this method should be separate from `group_keys`
async def groups(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, value in self.members():
if isinstance(value, AsyncGroup):
yield value
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.groups():
yield key

# todo: decide if this method should be separate from `arrays`
async def array_keys(self) -> AsyncGenerator[str, None]:
async def group_values(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, group in self.groups():
yield group

async def arrays(self) -> AsyncGenerator[tuple[str, AsyncArray], None]:
async for key, value in self.members():
if isinstance(value, AsyncArray):
yield key
yield key, value

# todo: decide if this method should be separate from `array_keys`
async def arrays(self) -> AsyncGenerator[AsyncArray, None]:
async for _, value in self.members():
if isinstance(value, AsyncArray):
yield value
async def array_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.arrays():
yield key

async def array_values(self) -> AsyncGenerator[AsyncArray, None]:
async for _, array in self.arrays():
yield array

async def tree(self, expand: bool = False, level: int | None = None) -> Any:
raise NotImplementedError
Expand DownExpand Up@@ -861,18 +863,29 @@ def members(self, max_depth: int | None = 0) -> tuple[tuple[str, Array | Group],
def __contains__(self, member: str) -> bool:
return self._sync(self._async_group.contains(member))

def group_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.group_keys()))
def groups(self) -> Generator[tuple[str, Group], None]:
for name, async_group in self._sync_iter(self._async_group.groups()):
yield name, Group(async_group)

def group_keys(self) -> Generator[str, None]:
for name, _ in self.groups():
yield name

def group_values(self) -> Generator[Group, None]:
for _, group in self.groups():
yield group

def groups(self) -> tuple[Group, ...]:
# TODO: in v2 this was a generator that return key: Group
return tuple(Group(obj) for obj in self._sync_iter(self._async_group.groups()))
def arrays(self) -> Generator[tuple[str, Array], None]:
for name, async_array in self._sync_iter(self._async_group.arrays()):
yield name, Array(async_array)

def array_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.array_keys()))
def array_keys(self) -> Generator[str, None]:
for name, _ in self.arrays():
yield name

def arrays(self) -> tuple[Array, ...]:
return tuple(Array(obj) for obj in self._sync_iter(self._async_group.arrays()))
def array_values(self) -> Generator[Array, None]:
for _, array in self.arrays():
yield array

def tree(self, expand: bool = False, level: int | None = None) -> Any:
return self._sync(self._async_group.tree(expand=expand, level=level))
Expand Down
44 changes: 19 additions & 25 deletions tests/v3/test_group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -301,34 +301,28 @@ def test_group_contains(store: Store, zarr_format: ZarrFormat) -> None:
assert "foo" in group


def test_group_subgroups(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
def test_group_child_iterators(store: Store, zarr_format: ZarrFormat):

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I decided to combine these tests since the setup is the same. And the test benefits from having both groups and arrays in the Group being accessed, since the code needs to filter to just child Groups / Arrays.

group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subgroups_expected = tuple(group.create_group(k) for k in keys)
# create a sub-array as well
_ = group.create_array("array", shape=(10,))
subgroups_observed = group.groups()
assert set(group.group_keys()) == set(keys)
assert len(subgroups_observed) == len(subgroups_expected)
assert all(a in subgroups_observed for a in subgroups_expected)
expected_group_keys = ["g0", "g1"]
expected_group_values = [group.create_group(name=name) for name in expected_group_keys]
expected_groups = list(zip(expected_group_keys, expected_group_values, strict=False))

expected_group_values[0].create_group("subgroup")
expected_group_values[0].create_array("subarray", shape=(1,))

def test_group_subarrays(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subarrays_expected = tuple(group.create_array(k, shape=(10,)) for k in keys)
# create a sub-group as well
_ = group.create_group("group")
subarrays_observed = group.arrays()
assert set(group.array_keys()) == set(keys)
assert len(subarrays_observed) == len(subarrays_expected)
assert all(a in subarrays_observed for a in subarrays_expected)
expected_array_keys = ["a0", "a1"]
expected_array_values = [
group.create_array(name=name, shape=(1,)) for name in expected_array_keys
]
expected_arrays = list(zip(expected_array_keys, expected_array_values, strict=False))

assert sorted(group.groups(), key=lambda x: x[0]) == expected_groups
assert sorted(group.group_keys()) == expected_group_keys
assert sorted(group.group_values(), key=lambda x: x.name) == expected_group_values

assert sorted(group.arrays(), key=lambda x: x[0]) == expected_arrays
assert sorted(group.array_keys()) == expected_array_keys
assert sorted(group.array_values(), key=lambda x: x.name) == expected_array_values


def test_group_update_attributes(store: Store, zarr_format: ZarrFormat) -> None:
Expand Down
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 40 additions & 27 deletions src/zarr/core/group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@
from zarr.store.common import ensure_no_existing_node

if TYPE_CHECKING:
from collections.abc import AsyncGenerator, Iterable, Iterator
from collections.abc import AsyncGenerator, Generator, Iterable, Iterator
from typing import Any

from zarr.abc.codec import Codec
Expand DownExpand Up@@ -678,29 +678,31 @@ async def contains(self, member: str) -> bool:
else:
return True

# todo: decide if this method should be separate from `groups`
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, value in self.members():
async def groups(self) -> AsyncGenerator[tuple[str, AsyncGroup], None]:
async for name, value in self.members():
if isinstance(value, AsyncGroup):
yield key
yield name, value

# todo: decide if this method should be separate from `group_keys`
async def groups(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, value in self.members():
if isinstance(value, AsyncGroup):
yield value
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.groups():
yield key

# todo: decide if this method should be separate from `arrays`
async def array_keys(self) -> AsyncGenerator[str, None]:
async def group_values(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, group in self.groups():
yield group

async def arrays(self) -> AsyncGenerator[tuple[str, AsyncArray], None]:
async for key, value in self.members():
if isinstance(value, AsyncArray):
yield key
yield key, value

# todo: decide if this method should be separate from `array_keys`
async def arrays(self) -> AsyncGenerator[AsyncArray, None]:
async for _, value in self.members():
if isinstance(value, AsyncArray):
yield value
async def array_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.arrays():
yield key

async def array_values(self) -> AsyncGenerator[AsyncArray, None]:
async for _, array in self.arrays():
yield array

async def tree(self, expand: bool = False, level: int | None = None) -> Any:
raise NotImplementedError
Expand DownExpand Up@@ -861,18 +863,29 @@ def members(self, max_depth: int | None = 0) -> tuple[tuple[str, Array | Group],
def __contains__(self, member: str) -> bool:
return self._sync(self._async_group.contains(member))

def group_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.group_keys()))
def groups(self) -> Generator[tuple[str, Group], None]:
for name, async_group in self._sync_iter(self._async_group.groups()):
yield name, Group(async_group)

def group_keys(self) -> Generator[str, None]:
for name, _ in self.groups():
yield name

def group_values(self) -> Generator[Group, None]:
for _, group in self.groups():
yield group

def groups(self) -> tuple[Group, ...]:
# TODO: in v2 this was a generator that return key: Group
return tuple(Group(obj) for obj in self._sync_iter(self._async_group.groups()))
def arrays(self) -> Generator[tuple[str, Array], None]:
for name, async_array in self._sync_iter(self._async_group.arrays()):
yield name, Array(async_array)

def array_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.array_keys()))
def array_keys(self) -> Generator[str, None]:
for name, _ in self.arrays():
yield name

def arrays(self) -> tuple[Array, ...]:
return tuple(Array(obj) for obj in self._sync_iter(self._async_group.arrays()))
def array_values(self) -> Generator[Array, None]:
for _, array in self.arrays():
yield array

def tree(self, expand: bool = False, level: int | None = None) -> Any:
return self._sync(self._async_group.tree(expand=expand, level=level))
Expand Down
44 changes: 19 additions & 25 deletions tests/v3/test_group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -301,34 +301,28 @@ def test_group_contains(store: Store, zarr_format: ZarrFormat) -> None:
assert "foo" in group


def test_group_subgroups(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
def test_group_child_iterators(store: Store, zarr_format: ZarrFormat):

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I decided to combine these tests since the setup is the same. And the test benefits from having both groups and arrays in the Group being accessed, since the code needs to filter to just child Groups / Arrays.

group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subgroups_expected = tuple(group.create_group(k) for k in keys)
# create a sub-array as well
_ = group.create_array("array", shape=(10,))
subgroups_observed = group.groups()
assert set(group.group_keys()) == set(keys)
assert len(subgroups_observed) == len(subgroups_expected)
assert all(a in subgroups_observed for a in subgroups_expected)
expected_group_keys = ["g0", "g1"]
expected_group_values = [group.create_group(name=name) for name in expected_group_keys]
expected_groups = list(zip(expected_group_keys, expected_group_values, strict=False))

expected_group_values[0].create_group("subgroup")
expected_group_values[0].create_array("subarray", shape=(1,))

def test_group_subarrays(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subarrays_expected = tuple(group.create_array(k, shape=(10,)) for k in keys)
# create a sub-group as well
_ = group.create_group("group")
subarrays_observed = group.arrays()
assert set(group.array_keys()) == set(keys)
assert len(subarrays_observed) == len(subarrays_expected)
assert all(a in subarrays_observed for a in subarrays_expected)
expected_array_keys = ["a0", "a1"]
expected_array_values = [
group.create_array(name=name, shape=(1,)) for name in expected_array_keys
]
expected_arrays = list(zip(expected_array_keys, expected_array_values, strict=False))

assert sorted(group.groups(), key=lambda x: x[0]) == expected_groups
assert sorted(group.group_keys()) == expected_group_keys
assert sorted(group.group_values(), key=lambda x: x.name) == expected_group_values

assert sorted(group.arrays(), key=lambda x: x[0]) == expected_arrays
assert sorted(group.array_keys()) == expected_array_keys
assert sorted(group.array_values(), key=lambda x: x.name) == expected_array_values


def test_group_update_attributes(store: Store, zarr_format: ZarrFormat) -> None:
Expand Down
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 40 additions & 27 deletions src/zarr/core/group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@
from zarr.store.common import ensure_no_existing_node

if TYPE_CHECKING:
from collections.abc import AsyncGenerator, Iterable, Iterator
from collections.abc import AsyncGenerator, Generator, Iterable, Iterator
from typing import Any

from zarr.abc.codec import Codec
Expand DownExpand Up@@ -678,29 +678,31 @@ async def contains(self, member: str) -> bool:
else:
return True

# todo: decide if this method should be separate from `groups`
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, value in self.members():
async def groups(self) -> AsyncGenerator[tuple[str, AsyncGroup], None]:
async for name, value in self.members():
if isinstance(value, AsyncGroup):
yield key
yield name, value

# todo: decide if this method should be separate from `group_keys`
async def groups(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, value in self.members():
if isinstance(value, AsyncGroup):
yield value
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.groups():
yield key

# todo: decide if this method should be separate from `arrays`
async def array_keys(self) -> AsyncGenerator[str, None]:
async def group_values(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, group in self.groups():
yield group

async def arrays(self) -> AsyncGenerator[tuple[str, AsyncArray], None]:
async for key, value in self.members():
if isinstance(value, AsyncArray):
yield key
yield key, value

# todo: decide if this method should be separate from `array_keys`
async def arrays(self) -> AsyncGenerator[AsyncArray, None]:
async for _, value in self.members():
if isinstance(value, AsyncArray):
yield value
async def array_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.arrays():
yield key

async def array_values(self) -> AsyncGenerator[AsyncArray, None]:
async for _, array in self.arrays():
yield array

async def tree(self, expand: bool = False, level: int | None = None) -> Any:
raise NotImplementedError
Expand DownExpand Up@@ -861,18 +863,29 @@ def members(self, max_depth: int | None = 0) -> tuple[tuple[str, Array | Group],
def __contains__(self, member: str) -> bool:
return self._sync(self._async_group.contains(member))

def group_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.group_keys()))
def groups(self) -> Generator[tuple[str, Group], None]:
for name, async_group in self._sync_iter(self._async_group.groups()):
yield name, Group(async_group)

def group_keys(self) -> Generator[str, None]:
for name, _ in self.groups():
yield name

def group_values(self) -> Generator[Group, None]:
for _, group in self.groups():
yield group

def groups(self) -> tuple[Group, ...]:
# TODO: in v2 this was a generator that return key: Group
return tuple(Group(obj) for obj in self._sync_iter(self._async_group.groups()))
def arrays(self) -> Generator[tuple[str, Array], None]:
for name, async_array in self._sync_iter(self._async_group.arrays()):
yield name, Array(async_array)

def array_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.array_keys()))
def array_keys(self) -> Generator[str, None]:
for name, _ in self.arrays():
yield name

def arrays(self) -> tuple[Array, ...]:
return tuple(Array(obj) for obj in self._sync_iter(self._async_group.arrays()))
def array_values(self) -> Generator[Array, None]:
for _, array in self.arrays():
yield array

def tree(self, expand: bool = False, level: int | None = None) -> Any:
return self._sync(self._async_group.tree(expand=expand, level=level))
Expand Down
44 changes: 19 additions & 25 deletions tests/v3/test_group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -301,34 +301,28 @@ def test_group_contains(store: Store, zarr_format: ZarrFormat) -> None:
assert "foo" in group


def test_group_subgroups(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
def test_group_child_iterators(store: Store, zarr_format: ZarrFormat):

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I decided to combine these tests since the setup is the same. And the test benefits from having both groups and arrays in the Group being accessed, since the code needs to filter to just child Groups / Arrays.

group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subgroups_expected = tuple(group.create_group(k) for k in keys)
# create a sub-array as well
_ = group.create_array("array", shape=(10,))
subgroups_observed = group.groups()
assert set(group.group_keys()) == set(keys)
assert len(subgroups_observed) == len(subgroups_expected)
assert all(a in subgroups_observed for a in subgroups_expected)
expected_group_keys = ["g0", "g1"]
expected_group_values = [group.create_group(name=name) for name in expected_group_keys]
expected_groups = list(zip(expected_group_keys, expected_group_values, strict=False))

expected_group_values[0].create_group("subgroup")
expected_group_values[0].create_array("subarray", shape=(1,))

def test_group_subarrays(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subarrays_expected = tuple(group.create_array(k, shape=(10,)) for k in keys)
# create a sub-group as well
_ = group.create_group("group")
subarrays_observed = group.arrays()
assert set(group.array_keys()) == set(keys)
assert len(subarrays_observed) == len(subarrays_expected)
assert all(a in subarrays_observed for a in subarrays_expected)
expected_array_keys = ["a0", "a1"]
expected_array_values = [
group.create_array(name=name, shape=(1,)) for name in expected_array_keys
]
expected_arrays = list(zip(expected_array_keys, expected_array_values, strict=False))

assert sorted(group.groups(), key=lambda x: x[0]) == expected_groups
assert sorted(group.group_keys()) == expected_group_keys
assert sorted(group.group_values(), key=lambda x: x.name) == expected_group_values

assert sorted(group.arrays(), key=lambda x: x[0]) == expected_arrays
assert sorted(group.array_keys()) == expected_array_keys
assert sorted(group.array_values(), key=lambda x: x.name) == expected_array_values


def test_group_update_attributes(store: Store, zarr_format: ZarrFormat) -> None:
Expand Down
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 40 additions & 27 deletions src/zarr/core/group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@
from zarr.store.common import ensure_no_existing_node

if TYPE_CHECKING:
from collections.abc import AsyncGenerator, Iterable, Iterator
from collections.abc import AsyncGenerator, Generator, Iterable, Iterator
from typing import Any

from zarr.abc.codec import Codec
Expand DownExpand Up@@ -678,29 +678,31 @@ async def contains(self, member: str) -> bool:
else:
return True

# todo: decide if this method should be separate from `groups`
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, value in self.members():
async def groups(self) -> AsyncGenerator[tuple[str, AsyncGroup], None]:
async for name, value in self.members():
if isinstance(value, AsyncGroup):
yield key
yield name, value

# todo: decide if this method should be separate from `group_keys`
async def groups(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, value in self.members():
if isinstance(value, AsyncGroup):
yield value
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.groups():
yield key

# todo: decide if this method should be separate from `arrays`
async def array_keys(self) -> AsyncGenerator[str, None]:
async def group_values(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, group in self.groups():
yield group

async def arrays(self) -> AsyncGenerator[tuple[str, AsyncArray], None]:
async for key, value in self.members():
if isinstance(value, AsyncArray):
yield key
yield key, value

# todo: decide if this method should be separate from `array_keys`
async def arrays(self) -> AsyncGenerator[AsyncArray, None]:
async for _, value in self.members():
if isinstance(value, AsyncArray):
yield value
async def array_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.arrays():
yield key

async def array_values(self) -> AsyncGenerator[AsyncArray, None]:
async for _, array in self.arrays():
yield array

async def tree(self, expand: bool = False, level: int | None = None) -> Any:
raise NotImplementedError
Expand DownExpand Up@@ -861,18 +863,29 @@ def members(self, max_depth: int | None = 0) -> tuple[tuple[str, Array | Group],
def __contains__(self, member: str) -> bool:
return self._sync(self._async_group.contains(member))

def group_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.group_keys()))
def groups(self) -> Generator[tuple[str, Group], None]:
for name, async_group in self._sync_iter(self._async_group.groups()):
yield name, Group(async_group)

def group_keys(self) -> Generator[str, None]:
for name, _ in self.groups():
yield name

def group_values(self) -> Generator[Group, None]:
for _, group in self.groups():
yield group

def groups(self) -> tuple[Group, ...]:
# TODO: in v2 this was a generator that return key: Group
return tuple(Group(obj) for obj in self._sync_iter(self._async_group.groups()))
def arrays(self) -> Generator[tuple[str, Array], None]:
for name, async_array in self._sync_iter(self._async_group.arrays()):
yield name, Array(async_array)

def array_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.array_keys()))
def array_keys(self) -> Generator[str, None]:
for name, _ in self.arrays():
yield name

def arrays(self) -> tuple[Array, ...]:
return tuple(Array(obj) for obj in self._sync_iter(self._async_group.arrays()))
def array_values(self) -> Generator[Array, None]:
for _, array in self.arrays():
yield array

def tree(self, expand: bool = False, level: int | None = None) -> Any:
return self._sync(self._async_group.tree(expand=expand, level=level))
Expand Down
44 changes: 19 additions & 25 deletions tests/v3/test_group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -301,34 +301,28 @@ def test_group_contains(store: Store, zarr_format: ZarrFormat) -> None:
assert "foo" in group


def test_group_subgroups(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
def test_group_child_iterators(store: Store, zarr_format: ZarrFormat):

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I decided to combine these tests since the setup is the same. And the test benefits from having both groups and arrays in the Group being accessed, since the code needs to filter to just child Groups / Arrays.

group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subgroups_expected = tuple(group.create_group(k) for k in keys)
# create a sub-array as well
_ = group.create_array("array", shape=(10,))
subgroups_observed = group.groups()
assert set(group.group_keys()) == set(keys)
assert len(subgroups_observed) == len(subgroups_expected)
assert all(a in subgroups_observed for a in subgroups_expected)
expected_group_keys = ["g0", "g1"]
expected_group_values = [group.create_group(name=name) for name in expected_group_keys]
expected_groups = list(zip(expected_group_keys, expected_group_values, strict=False))

expected_group_values[0].create_group("subgroup")
expected_group_values[0].create_array("subarray", shape=(1,))

def test_group_subarrays(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subarrays_expected = tuple(group.create_array(k, shape=(10,)) for k in keys)
# create a sub-group as well
_ = group.create_group("group")
subarrays_observed = group.arrays()
assert set(group.array_keys()) == set(keys)
assert len(subarrays_observed) == len(subarrays_expected)
assert all(a in subarrays_observed for a in subarrays_expected)
expected_array_keys = ["a0", "a1"]
expected_array_values = [
group.create_array(name=name, shape=(1,)) for name in expected_array_keys
]
expected_arrays = list(zip(expected_array_keys, expected_array_values, strict=False))

assert sorted(group.groups(), key=lambda x: x[0]) == expected_groups
assert sorted(group.group_keys()) == expected_group_keys
assert sorted(group.group_values(), key=lambda x: x.name) == expected_group_values

assert sorted(group.arrays(), key=lambda x: x[0]) == expected_arrays
assert sorted(group.array_keys()) == expected_array_keys
assert sorted(group.array_values(), key=lambda x: x.name) == expected_array_values


def test_group_update_attributes(store: Store, zarr_format: ZarrFormat) -> None:
Expand Down
, '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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 40 additions & 27 deletions src/zarr/core/group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -33,7 +33,7 @@
from zarr.store.common import ensure_no_existing_node

if TYPE_CHECKING:
from collections.abc import AsyncGenerator, Iterable, Iterator
from collections.abc import AsyncGenerator, Generator, Iterable, Iterator
from typing import Any

from zarr.abc.codec import Codec
Expand DownExpand Up@@ -678,29 +678,31 @@ async def contains(self, member: str) -> bool:
else:
return True

# todo: decide if this method should be separate from `groups`
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, value in self.members():
async def groups(self) -> AsyncGenerator[tuple[str, AsyncGroup], None]:
async for name, value in self.members():
if isinstance(value, AsyncGroup):
yield key
yield name, value

# todo: decide if this method should be separate from `group_keys`
async def groups(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, value in self.members():
if isinstance(value, AsyncGroup):
yield value
async def group_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.groups():
yield key

# todo: decide if this method should be separate from `arrays`
async def array_keys(self) -> AsyncGenerator[str, None]:
async def group_values(self) -> AsyncGenerator[AsyncGroup, None]:
async for _, group in self.groups():
yield group

async def arrays(self) -> AsyncGenerator[tuple[str, AsyncArray], None]:
async for key, value in self.members():
if isinstance(value, AsyncArray):
yield key
yield key, value

# todo: decide if this method should be separate from `array_keys`
async def arrays(self) -> AsyncGenerator[AsyncArray, None]:
async for _, value in self.members():
if isinstance(value, AsyncArray):
yield value
async def array_keys(self) -> AsyncGenerator[str, None]:
async for key, _ in self.arrays():
yield key

async def array_values(self) -> AsyncGenerator[AsyncArray, None]:
async for _, array in self.arrays():
yield array

async def tree(self, expand: bool = False, level: int | None = None) -> Any:
raise NotImplementedError
Expand DownExpand Up@@ -861,18 +863,29 @@ def members(self, max_depth: int | None = 0) -> tuple[tuple[str, Array | Group],
def __contains__(self, member: str) -> bool:
return self._sync(self._async_group.contains(member))

def group_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.group_keys()))
def groups(self) -> Generator[tuple[str, Group], None]:
for name, async_group in self._sync_iter(self._async_group.groups()):
yield name, Group(async_group)

def group_keys(self) -> Generator[str, None]:
for name, _ in self.groups():
yield name

def group_values(self) -> Generator[Group, None]:
for _, group in self.groups():
yield group

def groups(self) -> tuple[Group, ...]:
# TODO: in v2 this was a generator that return key: Group
return tuple(Group(obj) for obj in self._sync_iter(self._async_group.groups()))
def arrays(self) -> Generator[tuple[str, Array], None]:
for name, async_array in self._sync_iter(self._async_group.arrays()):
yield name, Array(async_array)

def array_keys(self) -> tuple[str, ...]:
return tuple(self._sync_iter(self._async_group.array_keys()))
def array_keys(self) -> Generator[str, None]:
for name, _ in self.arrays():
yield name

def arrays(self) -> tuple[Array, ...]:
return tuple(Array(obj) for obj in self._sync_iter(self._async_group.arrays()))
def array_values(self) -> Generator[Array, None]:
for _, array in self.arrays():
yield array

def tree(self, expand: bool = False, level: int | None = None) -> Any:
return self._sync(self._async_group.tree(expand=expand, level=level))
Expand Down
44 changes: 19 additions & 25 deletions tests/v3/test_group.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -301,34 +301,28 @@ def test_group_contains(store: Store, zarr_format: ZarrFormat) -> None:
assert "foo" in group


def test_group_subgroups(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
def test_group_child_iterators(store: Store, zarr_format: ZarrFormat):

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I decided to combine these tests since the setup is the same. And the test benefits from having both groups and arrays in the Group being accessed, since the code needs to filter to just child Groups / Arrays.

group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subgroups_expected = tuple(group.create_group(k) for k in keys)
# create a sub-array as well
_ = group.create_array("array", shape=(10,))
subgroups_observed = group.groups()
assert set(group.group_keys()) == set(keys)
assert len(subgroups_observed) == len(subgroups_expected)
assert all(a in subgroups_observed for a in subgroups_expected)
expected_group_keys = ["g0", "g1"]
expected_group_values = [group.create_group(name=name) for name in expected_group_keys]
expected_groups = list(zip(expected_group_keys, expected_group_values, strict=False))

expected_group_values[0].create_group("subgroup")
expected_group_values[0].create_array("subarray", shape=(1,))

def test_group_subarrays(store: Store, zarr_format: ZarrFormat) -> None:
"""
Test the behavior of `Group` methods for accessing subgroups, namely `Group.group_keys` and `Group.groups`
"""
group = Group.create(store, zarr_format=zarr_format)
keys = ("foo", "bar")
subarrays_expected = tuple(group.create_array(k, shape=(10,)) for k in keys)
# create a sub-group as well
_ = group.create_group("group")
subarrays_observed = group.arrays()
assert set(group.array_keys()) == set(keys)
assert len(subarrays_observed) == len(subarrays_expected)
assert all(a in subarrays_observed for a in subarrays_expected)
expected_array_keys = ["a0", "a1"]
expected_array_values = [
group.create_array(name=name, shape=(1,)) for name in expected_array_keys
]
expected_arrays = list(zip(expected_array_keys, expected_array_values, strict=False))

assert sorted(group.groups(), key=lambda x: x[0]) == expected_groups
assert sorted(group.group_keys()) == expected_group_keys
assert sorted(group.group_values(), key=lambda x: x.name) == expected_group_values

assert sorted(group.arrays(), key=lambda x: x[0]) == expected_arrays
assert sorted(group.array_keys()) == expected_array_keys
assert sorted(group.array_values(), key=lambda x: x.name) == expected_array_values


def test_group_update_attributes(store: Store, zarr_format: ZarrFormat) -> None:
Expand Down