Skip to content
Open
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning][].

## [0.4.0] (Unreleased)

### Added

- MuData accessors. These are similar to and build on [AnnData accessors](https://anndata.scverse.org/page/accessors.html), but add an additional
level for modalities.

### Changed

- `update()` no longer automatically pulls obs/var columns from individual modalities by default. Set `mudata.set_options(pull_on_update=true)`
Expand Down
28 changes: 28 additions & 0 deletions docs/_templates/autosummary/class-accessor.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{{ fullname | escape | underline}}

{%if fullname == "mudata.acc.MuAcc" %}
{% set attributes = attributes | select("ne", "X") | select("ne", "layers") %}
{% endif %}

.. currentmodule:: {{ module }}

.. autoclass:: {{ objname }}
:show-inheritance:

{% block attributes %}
{%- for item in attributes %}
{%- if loop.first %}
.. rubric:: Attributes
{% endif %}
.. autoattribute:: {{ item }}
{%- endfor %}
{% endblock %}

{% block methods %}
{%- for item in methods if item != "__init__" and item not in inherited_members %}
{%- if loop.first %}
.. rubric:: Methods
{% endif %}
.. automethod:: {{ item }}
{%- endfor %}
{% endblock %}
35 changes: 35 additions & 0 deletions docs/accessors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Accessors and paths

```{eval-rst}
.. module:: mudata.acc
```

[](#mudata.acc) provides [accessors](inv:anndata:*:term#accessor) that create [references](inv:anndata:*:term#reference) to axis-aligned 1D and 2D arrays in [MuData](#mudata.MuData) objects.
See the corresponding [AnnData documentation](inv:anndata:*:doc#accessors).

:::{important}
This functionality requires AnnData 0.13 or newer.
:::

The central [accessor](inv:anndata:*:term#accessor) is [](#A).
```{eval-rst}
.. autodata:: A
```
See [](#MuAcc) and [AdAcc](#anndata.acc.AdAcc) for examples of how to use it to create [references](inv:anndata:*:term#reference) (i.e. [AdRefs](#anndata.acc.AdRef)).

```{eval-rst}
.. autosummary::
:toctree: generated
:template: class-accessor

MuAcc
MultiModAcc
ModAcc
ModMapAcc
ModMetaAcc
ModLayerAcc
ModGraphAcc
ModMultiAcc
ModMultiMapAcc
ModGraphMapAcc
```
8 changes: 8 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@
write_zarr
```

## Accessors
```{eval-rst}
.. toctree::
:hidden:

mudata.acc <accessors>
```

## Extensions
```{eval-rst}
.. autosummary::
Expand Down
7 changes: 5 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,11 @@
pygments_style = "default"
katex_prerender = shutil.which(katex.NODEJS_BINARY) is not None

nitpick_ignore = [
nitpick_ignore = (
# If building the documentation fails because of a missing link that is outside your control,
# you can add an exception to this list.
# ("py:class", "igraph.Graph"),
]
("py:obj", "typing.R"),
("py:obj", "anndata.acc.Axes"),
("py:class", "AdRef"),
)
17 changes: 17 additions & 0 deletions docs/extensions/skip_private_bases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Generic, get_origin

from sphinx.util.typing import ExtensionMetadata

if TYPE_CHECKING:
from sphinx.application import Sphinx


def skip_private_bases(app: Sphinx, name: str, obj: type, _unused, bases: list[type]) -> None:
bases[:] = [b for b in bases if b is not object if get_origin(b) is not Generic if not b.__name__.startswith("_")]


def setup(app: Sphinx) -> ExtensionMetadata:
app.connect("autodoc-process-bases", skip_private_bases)
return ExtensionMetadata(parallel_read_safe=True)
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ dev = [
"pre-commit",
"twine>=4.0.2",
]
test = [ "coverage>=7.10", "mudata[io]", "pytest" ]
test = [ "coverage>=7.10", "mudata[io]", "packaging", "pytest" ]
doc = [
"anndata>=0.13rc1",
"docutils>=0.8,!=0.18.*,!=0.19.*",
"ipykernel",
"ipython",
Expand Down Expand Up @@ -123,7 +124,7 @@ lint.ignore = [
"TID252", # allow relative imports
]
lint.per-file-ignores."*/__init__.py" = [ "F401" ]
lint.per-file-ignores."docs/*" = [ "I" ]
lint.per-file-ignores."docs/*" = [ "D", "I" ]
lint.per-file-ignores."docs/notebooks/*" = [ "D", "F403", "F405" ]
lint.per-file-ignores."tests/*" = [ "D" ]
lint.pydocstyle.convention = "numpy"
Expand Down
7 changes: 7 additions & 0 deletions src/mudata/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Multimodal datasets"""

from contextlib import suppress

from anndata import AnnData
from scverse_misc import ExtensionNamespace
from scverse_misc import make_register_namespace_decorator as _make_register_namespace_decorator
Expand Down Expand Up @@ -51,3 +53,8 @@
"register_mudata_namespace",
"ExtensionNamespace",
]

with suppress(ImportError):
from . import acc

__all__.append("acc")
37 changes: 35 additions & 2 deletions src/mudata/_core/mudata.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,8 +533,41 @@ def strings_to_categoricals(self, df: pd.DataFrame | None = None) -> pd.DataFram
def __getitem__(self, index) -> AnnData | MuData:
if isinstance(index, str):
return self._mod[index]
else:
return MuData(self, as_view=True, index=index)

with suppress(ImportError):
from anndata.acc import AdRef

if isinstance(index, AdRef):
try:
return index.acc.get(self, index.idx)
except KeyError as e:
if index.acc.dim in ("obs", "var"):
for modname, mod in self._mod.items():
if index in mod:
raise KeyError(
f"There is no key {index.idx} in MuData .{index.acc.dim} but there is one in {modname} .{index.acc.dim}. Consider running `pull_{index.acc.dim}()` to update global .{index.acc.dim}."
) from e
raise
return MuData(self, as_view=True, index=index)

def __contains__(self, key) -> bool:
if isinstance(key, str):
return key in self._mod
with suppress(ImportError):
from anndata.acc import AdRef, MapAcc, RefAcc

from ..acc import ModAcc, MultiModAcc, _ModalityMapAcc, _ModalityMixin

if isinstance(key, ModAcc | _ModalityMapAcc):
return key.isin(self)
elif isinstance(key, _ModalityMixin):
return key in self.mod[key.mod]
elif isinstance(key, MultiModAcc):
return bool(self.mod)
elif isinstance(key, AdRef | RefAcc | MapAcc):
return AnnData.__contains__(self, key)

raise TypeError(f"Unexpected key {key!r}.")

@property
def mod(self) -> Mapping[str, AnnData | MuData]:
Expand Down
Loading