recount3 1.0.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.
recount3/__init__.py ADDED
@@ -0,0 +1,185 @@
1
+ # Copyright (c) 2026, Alexander A. Alsalihi, Robert M. Flight,
2
+ # Hunter N.B. Moseley. All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # * Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # * Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+ # * All advertising materials mentioning features or use of this software must
13
+ # display the following acknowledgement: This product includes software
14
+ # developed by the copyright holder.
15
+ # * Neither the name of the copyright holder nor the names of its contributors
16
+ # may be used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ # * If the source code is used in a published work, then proper citation of the
19
+ # source code must be included with the published work.
20
+ #
21
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND
22
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
25
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ #
32
+ """Typed Python API for the recount3 RNA-seq data repository.
33
+
34
+ `recount3 <https://rna.recount.bio/docs/>`_ is a large-scale uniformly
35
+ processed RNA-seq resource covering tens of thousands of human and mouse
36
+ samples across multiple data sources (SRA, GTEx, TCGA). This package
37
+ provides a small, typed Python API for discovering, downloading, and loading
38
+ recount3 data files.
39
+
40
+ The public surface is intentionally flat: core classes and helper functions
41
+ are re-exported here for discovery and convenience.
42
+
43
+ Typical usage example: high-level (recommended for most cases)::
44
+
45
+ from recount3 import create_rse
46
+
47
+ rse = create_rse(
48
+ project="SRP009615",
49
+ organism="human",
50
+ annotation_label="gencode_v26",
51
+ )
52
+
53
+ Typical usage example: lower-level (multi-project or custom workflows)::
54
+
55
+ from recount3 import R3ResourceBundle
56
+
57
+ bundle = R3ResourceBundle.discover(
58
+ organism="human",
59
+ data_source="sra",
60
+ project=["SRP009615", "SRP012682"],
61
+ )
62
+ counts = bundle.filter(
63
+ resource_type="count_files_gene_or_exon",
64
+ genomic_unit="gene",
65
+ ).stack_count_matrices()
66
+
67
+ Note:
68
+ Several features require optional dependencies:
69
+
70
+ * BiocPy (``biocframe``, ``summarizedexperiment``, ``genomicranges``):
71
+ required for :func:`create_rse`,
72
+ :meth:`~recount3.bundle.R3ResourceBundle.to_ranged_summarized_experiment`,
73
+ and the compute utilities in :mod:`recount3.se`.
74
+ Install with ``pip install biocframe summarizedexperiment genomicranges``.
75
+ * pyBigWig: required for BigWig file access via
76
+ :class:`~recount3._bigwig.BigWigFile`. Install with
77
+ ``pip install pyBigWig``.
78
+ """
79
+
80
+ from __future__ import annotations
81
+
82
+ from recount3._bigwig import BigWigFile
83
+ from recount3._descriptions import (
84
+ R3Annotations,
85
+ R3BigWig,
86
+ R3DataSourceMetadata,
87
+ R3DataSources,
88
+ R3GeneOrExonCounts,
89
+ R3JunctionCounts,
90
+ R3ProjectMetadata,
91
+ R3ResourceDescription,
92
+ )
93
+ from recount3.bundle import R3ResourceBundle
94
+ from recount3.config import (
95
+ Config,
96
+ default_config,
97
+ recount3_cache,
98
+ recount3_cache_files,
99
+ recount3_cache_rm,
100
+ )
101
+ from recount3.errors import (
102
+ CompatibilityError,
103
+ ConfigurationError,
104
+ DownloadError,
105
+ LoadError,
106
+ Recount3Error,
107
+ )
108
+ from recount3.resource import R3Resource
109
+ from recount3.se import (
110
+ build_ranged_summarized_experiment,
111
+ build_summarized_experiment,
112
+ create_rse,
113
+ )
114
+ from recount3.search import (
115
+ annotation_ext,
116
+ annotation_options,
117
+ create_sample_project_lists,
118
+ samples_for_project,
119
+ search_annotations,
120
+ search_bigwig_files,
121
+ search_count_files_gene_or_exon,
122
+ search_count_files_junctions,
123
+ search_data_source_metadata,
124
+ search_data_sources,
125
+ search_metadata_files,
126
+ search_project_all,
127
+ )
128
+ from recount3.types import (
129
+ CacheMode,
130
+ CompatibilityMode,
131
+ FieldSpec,
132
+ StringOrIterable,
133
+ )
134
+ from recount3.version import __version__
135
+
136
+ __all__ = [
137
+ "__version__",
138
+ # Config
139
+ "Config",
140
+ "default_config",
141
+ "recount3_cache",
142
+ "recount3_cache_files",
143
+ "recount3_cache_rm",
144
+ # Errors
145
+ "Recount3Error",
146
+ "ConfigurationError",
147
+ "DownloadError",
148
+ "LoadError",
149
+ "CompatibilityError",
150
+ # Types
151
+ "CacheMode",
152
+ "CompatibilityMode",
153
+ "FieldSpec",
154
+ "StringOrIterable",
155
+ # Descriptions
156
+ "R3ResourceDescription",
157
+ "R3Annotations",
158
+ "R3GeneOrExonCounts",
159
+ "R3JunctionCounts",
160
+ "R3ProjectMetadata",
161
+ "R3BigWig",
162
+ "R3DataSources",
163
+ "R3DataSourceMetadata",
164
+ # Core
165
+ "R3Resource",
166
+ "BigWigFile",
167
+ "R3ResourceBundle",
168
+ # Builders
169
+ "build_ranged_summarized_experiment",
170
+ "build_summarized_experiment",
171
+ "create_rse",
172
+ # Search
173
+ "samples_for_project",
174
+ "search_project_all",
175
+ "search_annotations",
176
+ "search_count_files_gene_or_exon",
177
+ "search_count_files_junctions",
178
+ "search_metadata_files",
179
+ "search_bigwig_files",
180
+ "search_data_sources",
181
+ "search_data_source_metadata",
182
+ "create_sample_project_lists",
183
+ "annotation_ext",
184
+ "annotation_options",
185
+ ]
recount3/__main__.py ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env python3
2
+ # Copyright (c) 2026, Alexander A. Alsalihi, Robert M. Flight,
3
+ # Hunter N.B. Moseley. All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # * Redistributions of source code must retain the above copyright notice, this
9
+ # list of conditions and the following disclaimer.
10
+ # * Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ # * All advertising materials mentioning features or use of this software must
14
+ # display the following acknowledgement: This product includes software
15
+ # developed by the copyright holder.
16
+ # * Neither the name of the copyright holder nor the names of its contributors
17
+ # may be used to endorse or promote products derived from this software without
18
+ # specific prior written permission.
19
+ # * If the source code is used in a published work, then proper citation of the
20
+ # source code must be included with the published work.
21
+ #
22
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND
23
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
26
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
+ #
33
+ # pylint: disable=invalid-name
34
+ """Module entry point.
35
+
36
+ Delegates to :mod:`recount3.cli`. Invoke via ``python -m recount3 --help``.
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ from recount3.cli import main
42
+
43
+ if __name__ == "__main__":
44
+ main()
recount3/_bigwig.py ADDED
@@ -0,0 +1,256 @@
1
+ # Copyright (c) 2026, Alexander A. Alsalihi, Robert M. Flight,
2
+ # Hunter N.B. Moseley. All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # * Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # * Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+ # * All advertising materials mentioning features or use of this software must
13
+ # display the following acknowledgement: This product includes software
14
+ # developed by the copyright holder.
15
+ # * Neither the name of the copyright holder nor the names of its contributors
16
+ # may be used to endorse or promote products derived from this software without
17
+ # specific prior written permission.
18
+ # * If the source code is used in a published work, then proper citation of the
19
+ # source code must be included with the published work.
20
+ #
21
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND
22
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
25
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ #
32
+ """BigWig file access via a small wrapper around ``pyBigWig``.
33
+
34
+ This is an internal module. For BigWig access via the public API, use
35
+ :meth:`~recount3.resource.R3Resource.load` on a BigWig
36
+ :class:`~recount3.resource.R3Resource`.
37
+
38
+ The optional dependency is imported through
39
+ :func:`~recount3._utils.get_pybigwig_module`.
40
+
41
+ Typical usage::
42
+
43
+ >>> from pathlib import Path
44
+ >>> from recount3._bigwig import BigWigFile
45
+ >>> with BigWigFile(Path("example.bw")) as bw:
46
+ ... lengths = bw.chroms()
47
+ ... mean = bw.stats("chr1", 0, 1000)[0]
48
+
49
+ Note:
50
+ Requires the optional ``pyBigWig`` package. Install with
51
+ ``pip install pyBigWig``. An :exc:`ImportError` is raised on first use
52
+ if the package is not available. ``pyBigWig`` can be difficult to
53
+ install on non-Linux systems.
54
+ """
55
+
56
+ from __future__ import annotations
57
+
58
+ import dataclasses
59
+ from pathlib import Path
60
+ from typing import Any, Mapping, TYPE_CHECKING, cast
61
+ from types import TracebackType
62
+
63
+ from recount3 import _utils
64
+
65
+ if TYPE_CHECKING: # pragma: no cover
66
+ import pyBigWig # type: ignore[import-not-found]
67
+
68
+
69
+ @dataclasses.dataclass(slots=True)
70
+ class BigWigFile:
71
+ """A lazily-opened BigWig reader with a small, typed API.
72
+
73
+ Instances are cheap to construct and do not open the file until the first
74
+ method call that requires a live ``pyBigWig`` handle. The handle is cached
75
+ for subsequent calls and can be explicitly released with :meth:`close`.
76
+
77
+ Attributes:
78
+ path: Filesystem path to a BigWig file (typically ``.bw``). The file must
79
+ exist when the handle is opened.
80
+ mode: File mode passed to ``pyBigWig.open``. Reading is the default
81
+ (``"r"``).
82
+ """
83
+
84
+ path: Path
85
+ mode: str = "r"
86
+
87
+ _bw: Any | None = dataclasses.field(default=None, init=False, repr=False)
88
+
89
+ def _ensure_open(self) -> pyBigWig.pyBigWig:
90
+ """Open the file if needed and return the live ``pyBigWig`` handle.
91
+
92
+ This method is the single place that opens the underlying file. It is
93
+ called internally by all public read methods.
94
+
95
+ Returns:
96
+ A live ``pyBigWig`` handle.
97
+
98
+ Raises:
99
+ FileNotFoundError: If ``path`` does not exist at open time.
100
+ ImportError: If the optional ``pyBigWig`` dependency is missing or
101
+ fails to import.
102
+ RuntimeError: If ``pyBigWig.open`` fails (it returns ``None``).
103
+ """
104
+ if self._bw is not None:
105
+ return cast("pyBigWig.pyBigWig", self._bw)
106
+
107
+ if not self.path.exists():
108
+ raise FileNotFoundError(str(self.path))
109
+
110
+ pybigwig = _utils.get_pybigwig_module()
111
+ bw = pybigwig.open(str(self.path), self.mode)
112
+ if bw is None: # pyBigWig returns None on failure
113
+ raise RuntimeError(f"Failed to open BigWig file: {self.path}")
114
+
115
+ self._bw = bw
116
+ return bw
117
+
118
+ def close(self) -> None:
119
+ """Close the underlying ``pyBigWig`` handle if it is open.
120
+
121
+ This method is idempotent.
122
+ """
123
+ if self._bw is None:
124
+ return
125
+ try:
126
+ self._bw.close()
127
+ finally:
128
+ self._bw = None
129
+
130
+ def __enter__(self) -> pyBigWig.pyBigWig:
131
+ """Enter a context manager and return the live ``pyBigWig`` handle."""
132
+ return self._ensure_open()
133
+
134
+ def __exit__(
135
+ self,
136
+ exc_type: type[BaseException] | None,
137
+ exc: BaseException | None,
138
+ tb: TracebackType | None,
139
+ ) -> None:
140
+ """Exit a context manager and close the file handle."""
141
+ del exc_type, exc, tb
142
+ self.close()
143
+
144
+ def is_open(self) -> bool:
145
+ """Return ``True`` if the underlying handle is currently open."""
146
+ return self._bw is not None
147
+
148
+ def chroms(
149
+ self, chrom: str | None = None
150
+ ) -> Mapping[str, int] | int | None:
151
+ """Return chromosome lengths, or a single chromosome length.
152
+
153
+ Args:
154
+ chrom: If provided, return only the length for this chromosome.
155
+
156
+ Returns:
157
+ If ``chrom`` is None, a mapping from chromosome name to length.
158
+ Otherwise, the length for the requested chromosome. ``None`` may be
159
+ returned if the chromosome is not present.
160
+ """
161
+ bw = self._ensure_open()
162
+ return bw.chroms(chrom) if chrom else bw.chroms()
163
+
164
+ def header(self) -> dict[str, Any]:
165
+ """Return the BigWig header metadata."""
166
+ bw = self._ensure_open()
167
+ return cast(dict[str, Any], bw.header())
168
+
169
+ def values(
170
+ self,
171
+ chrom: str,
172
+ start: int,
173
+ end: int,
174
+ *,
175
+ numpy: bool | None = None,
176
+ ) -> list[float] | Any:
177
+ """Return per-base values over a half-open interval ``[start, end)``.
178
+
179
+ Args:
180
+ chrom: Chromosome name.
181
+ start: 0-based start coordinate (inclusive).
182
+ end: 0-based end coordinate (exclusive).
183
+ numpy: Forwarded to ``pyBigWig.values``. When True, ``pyBigWig`` may
184
+ return a NumPy array depending on its configuration.
185
+
186
+ Returns:
187
+ Values returned by ``pyBigWig.values``. When ``numpy`` is not True, this is
188
+ typically a ``list[float]``. When ``numpy`` is True, ``pyBigWig`` may
189
+ return a NumPy array depending on its configuration.
190
+ """
191
+ bw = self._ensure_open()
192
+ return bw.values(chrom, int(start), int(end), numpy=numpy)
193
+
194
+ def stats(
195
+ self,
196
+ chrom: str,
197
+ start: int | None = None,
198
+ end: int | None = None,
199
+ *,
200
+ type: str = "mean", # pylint: disable=redefined-builtin
201
+ n_bins: int | None = None,
202
+ exact: bool | None = None,
203
+ ) -> list[float | None]:
204
+ """Return summary statistic(s) over an interval or whole chromosome.
205
+
206
+ Args:
207
+ chrom: Chromosome name.
208
+ start: 0-based start coordinate (inclusive). If omitted, stats are
209
+ computed over the whole chromosome.
210
+ end: 0-based end coordinate (exclusive). If omitted, stats are
211
+ computed over the whole chromosome.
212
+ type: Statistic name understood by ``pyBigWig.stats`` (for example,
213
+ ``"mean"``, ``"min"``, ``"max"``, ``"coverage"``).
214
+ n_bins: If provided, request binned stats via the ``nBins`` argument.
215
+ exact: If provided, forward to the ``exact`` argument.
216
+
217
+ Returns:
218
+ A list of statistic values as returned by ``pyBigWig.stats``. Values
219
+ may be ``None`` for missing data regions.
220
+ """
221
+ bw = self._ensure_open()
222
+ kwargs: dict[str, Any] = {"type": type}
223
+ if n_bins is not None:
224
+ kwargs["nBins"] = int(n_bins)
225
+ if exact is not None:
226
+ kwargs["exact"] = bool(exact)
227
+
228
+ if start is None or end is None:
229
+ return bw.stats(chrom, **kwargs)
230
+
231
+ return bw.stats(chrom, int(start), int(end), **kwargs)
232
+
233
+ def intervals(
234
+ self,
235
+ chrom: str,
236
+ start: int | None = None,
237
+ end: int | None = None,
238
+ ) -> list[tuple[int, int, float]] | None | Any:
239
+ """Return (start, end, value) intervals overlapping a region.
240
+
241
+ Args:
242
+ chrom: Chromosome name.
243
+ start: 0-based start coordinate (inclusive). If omitted, intervals
244
+ for the entire chromosome may be returned.
245
+ end: 0-based end coordinate (exclusive). If omitted, intervals for
246
+ the entire chromosome may be returned.
247
+
248
+ Returns:
249
+ Intervals returned by ``pyBigWig.intervals``. This is often a list of
250
+ ``(start, end, value)`` tuples. ``None`` may be returned when no intervals
251
+ overlap the requested region.
252
+ """
253
+ bw = self._ensure_open()
254
+ if start is None or end is None:
255
+ return bw.intervals(chrom)
256
+ return bw.intervals(chrom, int(start), int(end))