genome-spy-python 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. genome_spy/__init__.py +199 -0
  2. genome_spy/_chart_authoring.py +231 -0
  3. genome_spy/_conditions.py +72 -0
  4. genome_spy/_embed.py +87 -0
  5. genome_spy/_expressions.py +271 -0
  6. genome_spy/_parameters.py +267 -0
  7. genome_spy/_render.py +207 -0
  8. genome_spy/_utils.py +75 -0
  9. genome_spy/_widget.py +262 -0
  10. genome_spy/api.py +198 -0
  11. genome_spy/arrow.py +155 -0
  12. genome_spy/channels.py +193 -0
  13. genome_spy/chart.py +1240 -0
  14. genome_spy/data.py +56 -0
  15. genome_spy/data_transformers.py +267 -0
  16. genome_spy/datasets/__init__.py +189 -0
  17. genome_spy/datasets/_airway.py +219 -0
  18. genome_spy/datasets/_annotations.py +37 -0
  19. genome_spy/datasets/_gistic.py +43 -0
  20. genome_spy/datasets/_grammar.py +66 -0
  21. genome_spy/datasets/_hapmap.py +180 -0
  22. genome_spy/datasets/_mutation.py +289 -0
  23. genome_spy/datasets/_oncoprint.py +523 -0
  24. genome_spy/datasets/data/airway_metadata.csv +9 -0
  25. genome_spy/datasets/data/airway_scaledcounts.csv +38695 -0
  26. genome_spy/datasets/data/brca.maf.gz +0 -0
  27. genome_spy/datasets/data/hapmap_gwas.csv +14413 -0
  28. genome_spy/datasets/data/mutation_impact_reference.json +27 -0
  29. genome_spy/datasets/data/oncoprint_dataset3.json +266 -0
  30. genome_spy/datasets/data/p53_sequence_comparison.json.gz +0 -0
  31. genome_spy/datasets/data/pik3ca_mutations.json +1 -0
  32. genome_spy/datasets/data/pik3ca_tcga_brca_lollipop.json +38 -0
  33. genome_spy/datasets/data/refseq_gene_bodies.csv.gz +0 -0
  34. genome_spy/datasets/data/tal1_alphagenome_reference.json.gz +0 -0
  35. genome_spy/datasets/data/tcga.tsv +146 -0
  36. genome_spy/datasets/data/tcga_laml.maf.gz +0 -0
  37. genome_spy/datasets/data/tcga_laml_annot.tsv +201 -0
  38. genome_spy/datasets/data/tcga_laml_combined_oncoplot.json.gz +0 -0
  39. genome_spy/datasets/data/tcga_ov_gistic_lesions.tsv.gz +0 -0
  40. genome_spy/datasets/data/tcga_ov_gistic_scores.tsv.gz +0 -0
  41. genome_spy/helpers.py +185 -0
  42. genome_spy/jupyter.py +5 -0
  43. genome_spy/py.typed +0 -0
  44. genome_spy/schema/__init__.py +784 -0
  45. genome_spy/schema/_kwds.py +1394 -0
  46. genome_spy/schema/_typing.py +186 -0
  47. genome_spy/schema/capabilities.json +593 -0
  48. genome_spy/schema/channels.py +8943 -0
  49. genome_spy/schema/composition.py +1064 -0
  50. genome_spy/schema/core.py +51821 -0
  51. genome_spy/schema/ergonomics.py +2056 -0
  52. genome_spy/schema/expressions.py +476 -0
  53. genome_spy/schema/genome-spy-schema.json +33657 -0
  54. genome_spy/schema/lazy.py +326 -0
  55. genome_spy/schema/mixins.py +11684 -0
  56. genome_spy/schemapi.py +264 -0
  57. genome_spy/static/widget.js +345 -0
  58. genome_spy_python-0.1.0.dist-info/METADATA +185 -0
  59. genome_spy_python-0.1.0.dist-info/RECORD +64 -0
  60. genome_spy_python-0.1.0.dist-info/WHEEL +4 -0
  61. genome_spy_python-0.1.0.dist-info/licenses/LICENSE +21 -0
  62. genome_spy_python-0.1.0.dist-info/licenses/LICENSES/ALTAIR-BSD-3-Clause.txt +27 -0
  63. genome_spy_python-0.1.0.dist-info/licenses/LICENSES/GALLERY-DATA-MIT.txt +22 -0
  64. genome_spy_python-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +42 -0
genome_spy/data.py ADDED
@@ -0,0 +1,56 @@
1
+ """Helpers for schema-backed GenomeSpy data sources."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from typing import Any, cast
7
+
8
+ from genome_spy.schema import Data, ExprRef, LazyDataParams, UrlTemplate
9
+ from genome_spy.schema.lazy import LazyDataMethodMixin
10
+
11
+ __all__ = ["Data", "LazyNamespace", "lazy"]
12
+
13
+ _LazyUrl = str | Sequence[str] | ExprRef | dict[str, Any] | UrlTemplate
14
+
15
+
16
+ class LazyNamespace(LazyDataMethodMixin):
17
+ """Convenience builders for GenomeSpy lazy data sources.
18
+
19
+ Description:
20
+ These helpers build schema-backed :class:`genome_spy.schema.Data`
21
+ objects with a populated ``lazy`` block so callers can stay within the
22
+ handwritten chart API instead of assembling nested dictionaries by hand.
23
+
24
+ Example:
25
+ >>> lazy.bigwig("https://example.test/signal.bw")
26
+ >>> lazy.gff3("https://example.test/genes.gff3.gz", windowSize=2_000_000)
27
+ """
28
+
29
+ def source(self, type: str, url: _LazyUrl, /, **kwargs: Any) -> Data:
30
+ """Create a lazy data source of an arbitrary GenomeSpy type.
31
+
32
+ Description:
33
+ This is the generic escape hatch for lazy data sources. Named
34
+ helpers such as :meth:`bigwig` and :meth:`gff3` cover each
35
+ schema-defined URL-backed source type.
36
+
37
+ Args:
38
+ type: GenomeSpy lazy source type such as ``"bigwig"`` or
39
+ ``"gff3"``.
40
+ url: Remote data URL.
41
+ **kwargs: Additional lazy-source parameters supported by GenomeSpy.
42
+
43
+ Returns:
44
+ A schema-backed :class:`genome_spy.schema.Data` object.
45
+
46
+ Raises:
47
+ No exceptions are raised directly here.
48
+
49
+ Example:
50
+ >>> lazy.source("bigwig", "https://example.test/signal.bw")
51
+ """
52
+
53
+ return Data(lazy=LazyDataParams(type=cast(Any, type), url=url, **kwargs))
54
+
55
+
56
+ lazy = LazyNamespace()
@@ -0,0 +1,267 @@
1
+ """Settings for preparing chart data during serialization."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import Iterator
7
+ from contextlib import AbstractContextManager
8
+ from hashlib import sha256
9
+ from typing import Any
10
+
11
+ from genome_spy.schema.core import _ROOT_SCHEMA
12
+ from genome_spy.schemapi import SchemaBase, Undefined, UndefinedType
13
+
14
+ __all__ = ["DataTransformerSettings", "data_transformers"]
15
+
16
+
17
+ class _RestoreSettings(AbstractContextManager["DataTransformerSettings"]):
18
+ def __init__(self, settings: DataTransformerSettings, previous: bool) -> None:
19
+ self.settings = settings
20
+ self.previous = previous
21
+
22
+ def __enter__(self) -> DataTransformerSettings:
23
+ return self.settings
24
+
25
+ def __exit__(self, *args: Any) -> None:
26
+ self.settings.consolidate_datasets = self.previous
27
+
28
+
29
+ class DataTransformerSettings:
30
+ """Control whether exported charts share repeated inline tables.
31
+
32
+ Description:
33
+ Consolidation is enabled by default. This settings object supports
34
+ dataset consolidation only, not a registry of transformer plugins.
35
+
36
+ Example:
37
+ >>> with data_transformers.enable(consolidate_datasets=False):
38
+ ... spec = chart.to_dict()
39
+ """
40
+
41
+ def __init__(self) -> None:
42
+ self._consolidate_datasets = True
43
+
44
+ @property
45
+ def consolidate_datasets(self) -> bool:
46
+ """Whether eligible inline tables become shared named datasets."""
47
+ return self._consolidate_datasets
48
+
49
+ @consolidate_datasets.setter
50
+ def consolidate_datasets(self, value: bool) -> None:
51
+ if not isinstance(value, bool):
52
+ raise TypeError("consolidate_datasets must be a boolean.")
53
+ self._consolidate_datasets = value
54
+
55
+ def enable(
56
+ self, *, consolidate_datasets: bool | UndefinedType = Undefined
57
+ ) -> AbstractContextManager[DataTransformerSettings]:
58
+ """Apply a setting, optionally restoring it after a ``with`` block.
59
+
60
+ Description:
61
+ The change takes effect immediately. When used as a context manager,
62
+ the previous value is restored even if the block raises an error.
63
+
64
+ Args:
65
+ consolidate_datasets: Whether to share eligible inline tables.
66
+ Omit to keep the current setting.
67
+
68
+ Returns:
69
+ A context manager that restores the previous setting.
70
+
71
+ Raises:
72
+ TypeError: If the setting is not a boolean.
73
+
74
+ Example:
75
+ >>> with data_transformers.enable(consolidate_datasets=False):
76
+ ... spec = chart.to_dict()
77
+ """
78
+ previous = self.consolidate_datasets
79
+ if not isinstance(consolidate_datasets, UndefinedType):
80
+ self.consolidate_datasets = consolidate_datasets
81
+ return _RestoreSettings(self, previous)
82
+
83
+
84
+ data_transformers = DataTransformerSettings()
85
+
86
+
87
+ def _schema_parts(
88
+ schema: dict[str, Any], seen: set[int] | None = None
89
+ ) -> Iterator[dict[str, Any]]:
90
+ """Resolve schema alternatives without descending into instance fields."""
91
+ if seen is None:
92
+ seen = set()
93
+ if id(schema) in seen:
94
+ return
95
+ seen.add(id(schema))
96
+ yield schema
97
+ ref = schema.get("$ref")
98
+ if isinstance(ref, str):
99
+ name = ref.removeprefix("#/definitions/")
100
+ target = _ROOT_SCHEMA.get("definitions", {}).get(name, {})
101
+ yield from _schema_parts(target, seen)
102
+ for keyword in ("anyOf", "oneOf", "allOf"):
103
+ for alternative in schema.get(keyword, []):
104
+ yield from _schema_parts(alternative, seen)
105
+
106
+
107
+ def _data_slots(
108
+ value: Any,
109
+ schema: dict[str, Any] = _ROOT_SCHEMA,
110
+ *,
111
+ owner: str | None = None,
112
+ scoped: bool = False,
113
+ root: bool = True,
114
+ include_templates: bool = False,
115
+ ) -> Iterator[tuple[str, dict[str, Any], str | None, bool]]:
116
+ """Visit only schema-declared data sources and dataset declarations.
117
+
118
+ Open mappings (including rows, metadata, and parameter values) are opaque.
119
+ Templates are separate serialization roots, not live view instances.
120
+ Schema references also discover sources inside new transform definitions.
121
+ """
122
+ if isinstance(value, SchemaBase):
123
+ value = value._kwds
124
+ if not isinstance(value, (dict, list, tuple)):
125
+ return
126
+ parts = list(_schema_parts(schema))
127
+ # Stop at a data source: its records are user data, not chart grammar.
128
+ source_refs = {"#/definitions/InlineData", "#/definitions/NamedData"}
129
+ if (
130
+ isinstance(value, dict)
131
+ and value.keys() & {"values", "name", "url", "lazy", "sequence"}
132
+ and any(p.get("$ref") in source_refs for p in parts)
133
+ ):
134
+ yield "data", value, owner, scoped
135
+ return
136
+ if isinstance(value, (list, tuple)):
137
+ items = [p["items"] for p in parts if isinstance(p.get("items"), dict)]
138
+ if items:
139
+ for item in value:
140
+ yield from _data_slots(
141
+ item,
142
+ {"anyOf": items},
143
+ owner=owner,
144
+ scoped=scoped,
145
+ root=False,
146
+ include_templates=include_templates,
147
+ )
148
+ return
149
+ # Merge property locations, not validation rules. The generated wrappers
150
+ # remain responsible for validating which union branch the value satisfies.
151
+ properties: dict[str, list[dict[str, Any]]] = {}
152
+ for part in parts:
153
+ for key, child_schema in part.get("properties", {}).items():
154
+ properties.setdefault(key, []).append(child_schema)
155
+ if not root and "datasets" in properties and "datasets" in value:
156
+ name = value.get("name")
157
+ owner = name if isinstance(name, str) and name else None
158
+ scoped = True
159
+ for key, child_schemas in properties.items():
160
+ if key not in value:
161
+ continue
162
+ if key == "templates" and isinstance(value[key], dict):
163
+ for template in value[key].values():
164
+ if isinstance(template, SchemaBase):
165
+ template = template._kwds
166
+ if isinstance(template, dict):
167
+ yield "template", template, None, True
168
+ if include_templates:
169
+ yield from _data_slots(template, include_templates=True)
170
+ elif key == "datasets":
171
+ if isinstance(value[key], dict):
172
+ yield "datasets", value[key], owner, scoped
173
+ else:
174
+ yield from _data_slots(
175
+ value[key],
176
+ {"anyOf": child_schemas},
177
+ owner=owner,
178
+ scoped=scoped,
179
+ root=False,
180
+ include_templates=include_templates,
181
+ )
182
+
183
+ # Typed maps (Record[str, T]) contain grammar; arbitrary maps do not.
184
+ additional = [
185
+ part["additionalProperties"]
186
+ for part in parts
187
+ if isinstance(part.get("additionalProperties"), dict)
188
+ ]
189
+ if additional:
190
+ for key in value.keys() - properties.keys():
191
+ yield from _data_slots(
192
+ value[key],
193
+ {"anyOf": additional},
194
+ owner=owner,
195
+ scoped=scoped,
196
+ root=False,
197
+ include_templates=include_templates,
198
+ )
199
+
200
+
201
+ def _dataset_name(canonical: str) -> str:
202
+ """Name normalized content consistently, independently of object identity."""
203
+ return "data-" + sha256(canonical.encode()).hexdigest()[:32]
204
+
205
+
206
+ class _DatasetConsolidation:
207
+ """Collect tables once per serialization, reserving authored names first."""
208
+
209
+ def __init__(self, authored: Any) -> None:
210
+ self.reserved: set[str] = set()
211
+ for kind, value, _, _ in _data_slots(authored, include_templates=True):
212
+ if kind == "datasets":
213
+ self.reserved.update(value)
214
+ elif kind == "data" and isinstance(value.get("name"), str):
215
+ self.reserved.add(value["name"])
216
+ self.names: dict[str, str] = {}
217
+ self.datasets: dict[str, Any] = {}
218
+
219
+ def source(self, source: Any) -> Any:
220
+ if not isinstance(source, dict) or set(source) - {"values", "description"}:
221
+ return source
222
+ rows = source.get("values")
223
+ # Non-array and format-bearing sources need their inline loader.
224
+ if not isinstance(rows, list) or not all(isinstance(row, dict) for row in rows):
225
+ return source
226
+ # Secondary inputs may arrive as raw schema values rather than through
227
+ # Chart's data normalizer. Hash the same JSON-safe rows in either path.
228
+ from genome_spy._chart_authoring import json_safe
229
+
230
+ rows = json_safe(rows)
231
+ canonical = json.dumps(
232
+ rows, sort_keys=True, separators=(",", ":"), allow_nan=False
233
+ )
234
+ name = self.names.get(canonical)
235
+ if name is None:
236
+ base = _dataset_name(canonical)
237
+ name = base
238
+ suffix = 1
239
+ while name in self.reserved:
240
+ name = f"{base}-{suffix}"
241
+ suffix += 1
242
+ self.reserved.add(name)
243
+ self.names[canonical] = name
244
+ self.datasets[name] = rows
245
+ return {key: value for key, value in source.items() if key != "values"} | {
246
+ "name": name
247
+ }
248
+
249
+ def finish(self, spec: dict[str, Any]) -> dict[str, Any]:
250
+ # Also cover raw/generated nested specs and secondary transform inputs.
251
+ for kind, source, _, _ in _data_slots(spec):
252
+ if kind == "template":
253
+ # Each imported instance owns these declarations at runtime.
254
+ _consolidate(source)
255
+ elif kind == "data":
256
+ replacement = self.source(source)
257
+ if replacement is not source:
258
+ source.clear()
259
+ source.update(replacement)
260
+ if self.datasets:
261
+ spec.setdefault("datasets", {}).update(self.datasets)
262
+ return spec
263
+
264
+
265
+ def _consolidate(spec: dict[str, Any]) -> dict[str, Any]:
266
+ """Consolidate an owned serialized spec without changing authored names."""
267
+ return _DatasetConsolidation(spec).finish(spec)
@@ -0,0 +1,189 @@
1
+ """Dataset helpers for packaged GenomeSpy example data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from importlib.abc import Traversable
7
+ from importlib.resources import files
8
+ from pathlib import Path
9
+ from typing import Any, Literal, cast, overload
10
+
11
+ __all__ = [
12
+ "DatasetNotFoundError",
13
+ "available_datasets",
14
+ "load_dataset",
15
+ ]
16
+
17
+ _DATA_DIR = files("genome_spy.datasets").joinpath("data")
18
+ _DATASETS = {
19
+ "airway_metadata": "airway_metadata.csv",
20
+ "airway_scaledcounts": "airway_scaledcounts.csv",
21
+ "brca_maf": "brca.maf.gz",
22
+ "hapmap_gwas": "hapmap_gwas.csv",
23
+ "mutation_impact_reference": "mutation_impact_reference.json",
24
+ "p53_sequence_comparison": "p53_sequence_comparison.json.gz",
25
+ "pik3ca_mutations": "pik3ca_mutations.json",
26
+ "pik3ca_tcga_brca_lollipop": "pik3ca_tcga_brca_lollipop.json",
27
+ "tal1_alphagenome_reference": "tal1_alphagenome_reference.json.gz",
28
+ "pyoncoprint_tcga": "tcga.tsv",
29
+ "refseq_gene_bodies": "refseq_gene_bodies.csv.gz",
30
+ "tcga_laml_annotations": "tcga_laml_annot.tsv",
31
+ "tcga_laml_combined_oncoplot": "tcga_laml_combined_oncoplot.json.gz",
32
+ "tcga_laml_maf": "tcga_laml.maf.gz",
33
+ "tcga_ov_gistic_lesions": "tcga_ov_gistic_lesions.tsv.gz",
34
+ "tcga_ov_gistic_scores": "tcga_ov_gistic_scores.tsv.gz",
35
+ "tcga_oncoprint": "oncoprint_dataset3.json",
36
+ }
37
+
38
+
39
+ class DatasetNotFoundError(ValueError):
40
+ """Dataset name lookup failed.
41
+
42
+ Description:
43
+ ``load_dataset`` and related helpers use this exception to report an
44
+ unknown dataset name together with the valid packaged choices.
45
+ """
46
+
47
+
48
+ def available_datasets() -> tuple[str, ...]:
49
+ """Packaged dataset names.
50
+
51
+ Description:
52
+ The returned names are the public identifiers accepted by
53
+ ``load_dataset``.
54
+
55
+ Returns:
56
+ Dataset names that ``load_dataset`` accepts.
57
+
58
+ Raises:
59
+ None.
60
+
61
+ Example:
62
+ >>> available_datasets()
63
+ ('airway_metadata', 'airway_scaledcounts', 'hapmap_gwas', ...)
64
+ """
65
+
66
+ return tuple(sorted(_DATASETS))
67
+
68
+
69
+ def _resource_for(name: str) -> Traversable:
70
+ try:
71
+ filename = _DATASETS[name]
72
+ except KeyError as exc:
73
+ known = ", ".join(available_datasets())
74
+ raise DatasetNotFoundError(
75
+ f"Unknown dataset {name!r}. Available datasets: {known}."
76
+ ) from exc
77
+ return _DATA_DIR.joinpath(filename)
78
+
79
+
80
+ def _load_table_bundle(name: str, tables: tuple[str, ...]) -> dict[str, Any]:
81
+ """Load selected prepared JSON tables as DataFrames, preserving metadata."""
82
+ import pandas as pd
83
+
84
+ data = load_dataset(name, as_format="json")
85
+ if not isinstance(data, dict):
86
+ raise ValueError(f"Dataset {name!r} must contain a mapping of tables.")
87
+ for table in tables:
88
+ data[table] = pd.DataFrame.from_records(data[table])
89
+ return data
90
+
91
+
92
+ def _load_dataframe(name: str) -> Any:
93
+ try:
94
+ import pandas as pd
95
+ except ImportError as exc:
96
+ raise ImportError(
97
+ f"Loading dataset {name!r} as a DataFrame requires pandas. "
98
+ "Install pandas or use as_format='text' or as_format='json'."
99
+ ) from exc
100
+
101
+ resource = _resource_for(name)
102
+ suffixes = Path(_DATASETS[name]).suffixes
103
+ if suffixes in ([".csv"], [".csv", ".gz"]):
104
+ return pd.read_csv(resource)
105
+ if suffixes in ([".tsv"], [".tsv", ".gz"]):
106
+ return pd.read_csv(resource, sep="\t")
107
+ if suffixes[-2:] == [".maf", ".gz"]:
108
+ return pd.read_csv(resource, sep="\t", compression="gzip")
109
+ raise ValueError(
110
+ f"Dataset {name!r} is not tabular and cannot be read as a DataFrame."
111
+ )
112
+
113
+
114
+ @overload
115
+ def load_dataset(name: str, *, as_format: Literal["auto"] = "auto") -> Any: ...
116
+
117
+
118
+ @overload
119
+ def load_dataset(name: str, *, as_format: Literal["dataframe"]) -> Any: ...
120
+
121
+
122
+ @overload
123
+ def load_dataset(
124
+ name: str, *, as_format: Literal["json"]
125
+ ) -> dict[str, Any] | list[Any]: ...
126
+
127
+
128
+ @overload
129
+ def load_dataset(name: str, *, as_format: Literal["text"]) -> str: ...
130
+
131
+
132
+ def load_dataset(
133
+ name: str,
134
+ *,
135
+ as_format: Literal["auto", "dataframe", "json", "text"] = "auto",
136
+ ) -> Any:
137
+ """Load a packaged example dataset by name.
138
+
139
+ Description:
140
+ The loader understands the small set of real datasets vendored with the
141
+ package for examples and tutorials. ``"auto"`` returns a pandas
142
+ ``DataFrame`` for CSV, TSV, and compressed MAF files and parsed Python
143
+ objects for JSON files.
144
+
145
+ Args:
146
+ name: Dataset name from ``available_datasets()``.
147
+ as_format: Output format to return.
148
+
149
+ Returns:
150
+ The loaded dataset in the requested format.
151
+
152
+ Raises:
153
+ DatasetNotFoundError: If the dataset name is unknown.
154
+ ImportError: If ``as_format="dataframe"`` is requested without pandas.
155
+ ValueError: If the requested format does not match the stored file type.
156
+
157
+ Example:
158
+ >>> load_dataset("hapmap_gwas").head()
159
+ >>> load_dataset("pik3ca_mutations", as_format="json")["domains"][0]
160
+ """
161
+
162
+ resource = _resource_for(name)
163
+ filename = _DATASETS[name]
164
+ suffix = Path(filename).suffix
165
+ suffixes = Path(filename).suffixes
166
+
167
+ if as_format == "text":
168
+ if suffix == ".gz":
169
+ import gzip
170
+
171
+ return gzip.decompress(resource.read_bytes()).decode("utf-8")
172
+ return resource.read_text(encoding="utf-8")
173
+ is_json = suffix == ".json" or suffixes[-2:] == [".json", ".gz"]
174
+ if as_format == "json":
175
+ if not is_json:
176
+ raise ValueError(f"Dataset {name!r} is stored as {suffix}, not JSON.")
177
+ text = load_dataset(name, as_format="text")
178
+ return cast(dict[str, Any] | list[Any], json.loads(text))
179
+ if as_format == "dataframe":
180
+ return _load_dataframe(name)
181
+ if is_json:
182
+ return json.loads(load_dataset(name, as_format="text"))
183
+ if suffix in {".csv", ".tsv"} or suffixes[-2:] in (
184
+ [".csv", ".gz"],
185
+ [".tsv", ".gz"],
186
+ [".maf", ".gz"],
187
+ ):
188
+ return _load_dataframe(name)
189
+ raise ValueError(f"Unsupported dataset format for {name!r}: {suffix}.")