gesel 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.
gesel/__init__.py ADDED
@@ -0,0 +1,48 @@
1
+ import sys
2
+
3
+ if sys.version_info[:2] >= (3, 8):
4
+ # TODO: Import directly (no need for conditional) when `python_requires = >= 3.8`
5
+ from importlib.metadata import PackageNotFoundError, version # pragma: no cover
6
+ else:
7
+ from importlib_metadata import PackageNotFoundError, version # pragma: no cover
8
+
9
+ try:
10
+ # Change here if project is renamed and does not equal the package name
11
+ dist_name = __name__
12
+ __version__ = version(dist_name)
13
+ except PackageNotFoundError: # pragma: no cover
14
+ __version__ = "unknown"
15
+ finally:
16
+ del version, PackageNotFoundError
17
+
18
+
19
+ from ._download_database_file import download_database_file, database_url
20
+ from ._download_database_ranges import download_database_ranges, download_multipart_ranges, range_concurrency
21
+ from ._download_gene_file import download_gene_file, gene_url
22
+
23
+ from ._fetch_all_collections import fetch_all_collections
24
+ from ._fetch_all_genes import fetch_all_genes
25
+ from ._fetch_all_sets import fetch_all_sets
26
+ from ._fetch_some_sets import fetch_some_sets, fetch_set_sizes
27
+ from ._fetch_some_collections import fetch_some_collections, fetch_collection_sizes
28
+
29
+ from ._fetch_genes_for_all_sets import fetch_genes_for_all_sets
30
+ from ._fetch_sets_for_all_genes import fetch_sets_for_all_genes
31
+ from ._fetch_genes_for_some_sets import fetch_genes_for_some_sets
32
+ from ._fetch_sets_for_some_genes import fetch_sets_for_some_genes, effective_number_of_genes
33
+ from ._find_overlapping_sets import find_overlapping_sets
34
+
35
+ from ._map_genes_by_name import map_genes_by_name
36
+ from ._search_genes import search_genes
37
+ from ._search_set_text import search_set_text
38
+
39
+ from ._new_config import new_config, flush_memory_cache
40
+ from ._utils import cache_directory
41
+
42
+
43
+ __all__ = []
44
+ _toignore = set(["sys", "biocutils", "biocframe"])
45
+ for _name in dir():
46
+ if _name.startswith("_") or _name in _toignore:
47
+ continue
48
+ __all__.append(_name)
@@ -0,0 +1,79 @@
1
+ from typing import Optional
2
+ from . import _utils as utils
3
+
4
+
5
+ def download_database_file(
6
+ name: str,
7
+ url: Optional[str] = None,
8
+ cache: Optional[str] = None,
9
+ overwrite: bool = False
10
+ ) -> str:
11
+ """
12
+ Download Gesel database files and store them in a cache on the local file system.
13
+
14
+ Args:
15
+ name:
16
+ Name of the file.
17
+ This usually has the species identifier as a prefix.
18
+
19
+ url:
20
+ Base URL to the Gesel database files.
21
+ If ``None``, it is set to :py:func:`~database_url`.
22
+
23
+ cache:
24
+ Path to a cache directory.
25
+ If ``None``, it is set to :py:func:`~cache_directory`.
26
+
27
+ overwrite:
28
+ Boolean indicating whether any cached file should be overwritten with a new download.
29
+
30
+ Returns:
31
+ Path to the downloaded file on the local file system.
32
+
33
+ Examples:
34
+ >>> import gesel
35
+ >>> gesel.download_database_file("9606_collections.tsv.gz")
36
+ """
37
+ if url is None:
38
+ url = database_url()
39
+ return utils._download_file(cache, url + "/" + name, overwrite)
40
+
41
+
42
+ _db_url = None
43
+
44
+
45
+ def database_url(url: Optional[str] = None) -> str:
46
+ """
47
+ Get or set the base URL to the Gesel database files, which is used in :py:func:`~download_database_file`.
48
+
49
+ Args:
50
+ url:
51
+ The new database URL.
52
+
53
+ Returns:
54
+ If ``url = None``, the URL to the Gesel database is returned.
55
+ The URL defaults to the `GitHub releases page <https://github.com/LTLA/gesel-feedstock>`_;
56
+ this can be altered by setting the ``GESEL_DATABASE_URL`` environment variable before the first call to this function.
57
+
58
+ If ``url`` is provided, this function sets the database URL to ``url``, and returns the previous value of the URL.
59
+
60
+ Examples:
61
+ >>> import gesel
62
+ >>> gesel.database_url()
63
+ >>> old = gesel.database_url("https://foo.bar")
64
+ >>> gesel.database_url(old)
65
+ >>> gesel.database_url()
66
+ """
67
+
68
+ global _db_url
69
+ if _db_url is None:
70
+ import os
71
+ if "GESEL_DATABASE_URL" in os.environ:
72
+ _db_url = os.environ["GESEL_DATABASE_URL"]
73
+ else:
74
+ _db_url = "https://github.com/LTLA/gesel-feedstock/releases/download/indices-v0.2.1"
75
+
76
+ previous = _db_url
77
+ if url is not None:
78
+ _db_url = url
79
+ return previous
@@ -0,0 +1,254 @@
1
+ from ._download_database_file import database_url
2
+ from . import _utils as utils
3
+ from typing import Optional
4
+ import requests
5
+
6
+
7
+ def download_database_ranges(
8
+ name: str,
9
+ start: list[int],
10
+ end: list[int],
11
+ url: Optional[str] = None,
12
+ multipart: bool = False,
13
+ concurrency: Optional[int] = None
14
+ ) -> list[str]:
15
+ """
16
+ Download any number of byte ranges from a Gesel database file.
17
+
18
+ Args:
19
+ name:
20
+ Name of the file.
21
+ This usually has the species identifier as a prefix.
22
+
23
+ start:
24
+ List of integers containing the zero-indexed closed start of each byte range to extract from the file.
25
+ This list may be empty.
26
+
27
+ end:
28
+ List of integers containing the zero-indexed open end of each byte range to extract from the file.
29
+ This should have the same length as ``start`` such that the ``i``-th range is defined as ``[start[i], end[i])``.
30
+ All ranges supplied in a single call to this function should be non-overlapping.
31
+
32
+ url:
33
+ Base URL to the Gesel database files.
34
+ If ``None``, it is set to :py:func:`~gesel.database_url`.
35
+
36
+ multipart:
37
+ Whether the server at ``url`` supports multi-part range requests.
38
+
39
+ concurrency:
40
+ Maximum number of concurrent range requests.
41
+ If ``None``, defaults to :py:func:`~gesel.range_concurrency`.
42
+ Only used if ``multipart = False``.
43
+
44
+ Returns:
45
+ List of byte strings containing the requested bytes for each range.
46
+ For ranges where ``end <= start``, an empty string is returned.
47
+
48
+ Examples:
49
+ >>> import gesel
50
+ >>> gesel.download_database_ranges("9606_set2gene.tsv", [0], [100])
51
+ >>> gesel.download_database_ranges("9606_set2gene.tsv", [10, 100, 1000], [20, 150, 1100])
52
+ """
53
+
54
+ if url is None:
55
+ url = database_url()
56
+ url = url + "/" + name
57
+
58
+ if multipart:
59
+ return download_multipart_ranges(url, start, end)
60
+
61
+ if concurrency is None:
62
+ concurrency = range_concurrency()
63
+ from concurrent.futures import ThreadPoolExecutor
64
+
65
+ with ThreadPoolExecutor(max_workers = concurrency) as executor:
66
+ collected = []
67
+ for i in range(len(start)):
68
+ curstart = start[i]
69
+ curend = end[i]
70
+ if curstart < curend:
71
+ collected.append((i, executor.submit(_make_range_request, url, curstart, curend)))
72
+
73
+ output = [b""] * len(start)
74
+ for i, val in collected:
75
+ output[i] = val.result()
76
+
77
+ return output
78
+
79
+
80
+ _range_concurrency = 10
81
+
82
+
83
+ def range_concurrency(concurrency: Optional[int] = None):
84
+ """
85
+ Set the default number of threads for concurrent range requests in :py:func:`~gesel.download_database_ranges`.
86
+
87
+ Args:
88
+ concurrency:
89
+ Number of threads.
90
+
91
+ Results:
92
+ If ``concurrency = None``, the number of threads is returned (default 10).
93
+
94
+ If ``concurrency`` is provided, it is used to set the number of threads, and the previous number of threads is returned.
95
+
96
+ Examples:
97
+ >>> import gesel
98
+ >>> gesel.range_concurrency()
99
+ >>> old = gesel.range_concurrency(5)
100
+ >>> print(old)
101
+ >>> gesel.range_concurrency(old)
102
+ """
103
+ global _range_concurrency
104
+ previous = _range_concurrency
105
+ if concurrency is not None:
106
+ _range_concurrency = concurrency
107
+ return previous
108
+
109
+
110
+ def _make_range_request(url, curstart, curend) -> bytes:
111
+ resp = requests.get(url, headers = { "Range": "bytes=" + str(curstart) + "-" + str(curend - 1) }) # byte ranges are closed intervals, not half-open.
112
+ if resp.status_code >= 300:
113
+ raise utils.format_error(resp)
114
+ return resp.content[:(curend - curstart)] # We use 'content' instead of 'text' to handle multi-byte characters correctly.
115
+
116
+
117
+ def download_multipart_ranges(
118
+ url: str,
119
+ start: list[int],
120
+ end: list[int],
121
+ _mock = None
122
+ ) -> list[str]:
123
+ """
124
+ Perform a multi-part range request on a Gesel database file.
125
+
126
+ Args:
127
+ url:
128
+ URL to a specific Gesel database file.
129
+
130
+ start:
131
+ List of integers containing the zero-indexed closed start of each byte range to extract from the file.
132
+ This list may be empty.
133
+
134
+ end:
135
+ List of integers containing the zero-indexed open end of each byte range to extract from the file.
136
+ This should have the same length as ``start`` such that the ``i``-th range is defined as ``[start[i], end[i])``.
137
+ All ranges supplied in a single call to this function should be non-overlapping.
138
+
139
+ _mock:
140
+ Internal use only.
141
+
142
+ Returns:
143
+ List of byte strings containing the requested bytes for each range.
144
+ For ranges where ``end <= start``, an empty string is returned.
145
+
146
+ Examples:
147
+ >>> import gesel
148
+ >>> url = gesel.database_url() + "/9606_set2gene.tsv"
149
+ >>> gesel.download_multipart_ranges(url, [0], [100])
150
+ >>> gesel.download_multipart_ranges(url, [10, 100, 1000], [10, 150, 900])
151
+ >>> # Note: as of writing, GitHub releases don't support multi-part range requests.
152
+ """
153
+
154
+ collected = []
155
+ for i in range(len(start)):
156
+ curstart = start[i]
157
+ curend = end[i]
158
+ if curend <= curstart:
159
+ continue
160
+ collected.append((curstart, curend, i))
161
+ collected.sort()
162
+
163
+ output = [b""] * len(start)
164
+ if len(collected) == 0:
165
+ return output
166
+
167
+ formatted = []
168
+ for curstart, curend, _ in collected:
169
+ formatted.append(str(curstart) + "-" + str(curend - 1)) # byte ranges are closed intervals, not half-open.
170
+
171
+ if _mock is None:
172
+ resp = requests.get(url, headers = { "Range": "bytes=" + ", ".join(formatted) })
173
+ else:
174
+ resp = _mock(url, "bytes=" + ", ".join(formatted))
175
+ if resp.status_code >= 300:
176
+ raise utils.format_error(resp)
177
+
178
+ if len(collected) == 1:
179
+ curstart, curend, curi = collected[0]
180
+ output[curi] = resp.content[:(curend - curstart)]
181
+ return output
182
+
183
+ ct = resp.headers["Content-Type"]
184
+ prefix = "multipart/byteranges; boundary="
185
+ if not ct.startswith(prefix):
186
+ raise RuntimeError("unexpected content type from multi-part range request")
187
+ boundary = ct[len(prefix):]
188
+
189
+ # Remember, we operate on the byte strings to correctly handle ranges for multi-byte characters.
190
+ parsed_start, parsed_content = _parse_multipart_response(resp.content, boundary.encode("utf-8"))
191
+ import bisect
192
+ for curstart, curend, curi in collected:
193
+ chosen = bisect.bisect_right(parsed_start, curstart) - 1
194
+ offset = parsed_start[chosen]
195
+ output[curi] = parsed_content[chosen][(curstart - offset):(curend - offset)]
196
+
197
+ return output
198
+
199
+
200
+ # WARNING: this part only works when the contents of body are fully string-able;
201
+ # otherwise, if it's binary data, we really should be working with them as raw data.
202
+ def _parse_multipart_response(body: bytes, boundary: bytes) -> tuple[list, list]:
203
+ position = 0
204
+ boundary = b"--" + boundary
205
+ starts = []
206
+ contents = []
207
+
208
+ while True:
209
+ if body[position:(position + len(boundary))] != boundary:
210
+ raise RuntimeError("missing boundary in multi-part range request")
211
+ position += len(boundary)
212
+
213
+ remaining = body[position:(position + 2)]
214
+ if remaining == b"--":
215
+ break
216
+ if remaining != b"\r\n":
217
+ raise RuntimeError("unexpected content range format in multi-part response")
218
+ position += 2
219
+
220
+ # Running through all headers.
221
+ headers = {}
222
+ while True:
223
+ crlf = body.find(b"\r\n", position)
224
+ if crlf == -1:
225
+ raise RuntimeError("failed to find CRLF to terminate headers in multi-part response")
226
+ curline = body[position:crlf]
227
+ position = crlf + 2
228
+ if curline == b"": # headers terminate at a CRLF empty line.
229
+ break
230
+ at_colon = curline.find(b":")
231
+ headers[curline[:at_colon].lower()] = curline[(at_colon+1):]
232
+
233
+ if b"content-range" not in headers:
234
+ raise RuntimeError("expected a 'content-range' header in each multi-part response")
235
+ crange = headers[b"content-range"]
236
+ to_bytes = crange.find(b"bytes ")
237
+ to_dash = crange.find(b"-")
238
+ to_slash = crange.find(b"/")
239
+ if to_bytes == -1 or to_dash == -1 or to_slash == -1 or to_dash >= to_slash:
240
+ raise RuntimeError("expected 'content-range' header to follow a 'bytes <start>-<end>/<size>' format")
241
+
242
+ first = int(crange[(to_bytes + 6):to_dash])
243
+ last = int(crange[(to_dash + 1):to_slash])
244
+ starts.append(first)
245
+ curlen = last - first + 1
246
+ contents.append(body[position:(position + curlen)])
247
+ position += curlen
248
+
249
+ remaining = body[position:(position + 2)]
250
+ if remaining != b"\r\n":
251
+ raise RuntimeError("expected a newline after each part of a multi-part response")
252
+ position += 2
253
+
254
+ return starts, contents
@@ -0,0 +1,79 @@
1
+ from typing import Optional
2
+ from . import _utils as utils
3
+
4
+
5
+ def download_gene_file(
6
+ name: str,
7
+ url: Optional[str] = None,
8
+ cache: Optional[str] = None,
9
+ overwrite: bool = False
10
+ ) -> str:
11
+ """
12
+ Download Gesel gene files and store them in a cache on the local file system.
13
+
14
+ Args:
15
+ name:
16
+ Name of the file.
17
+ This usually has the species identifier as a prefix.
18
+
19
+ url:
20
+ Base URL to the Gesel gene files.
21
+ If ``None``, it is set to :py:func:`~gene_url`.
22
+
23
+ cache:
24
+ Path to a cache directory.
25
+ If ``None``, it is set to :py:func:`~cache_directory`.
26
+
27
+ overwrite:
28
+ Boolean indicating whether any cached file should be overwritten with a new download.
29
+
30
+ Returns:
31
+ Path to the downloaded file on the local file system.
32
+
33
+ Examples:
34
+ >>> import gesel
35
+ >>> gesel.download_gene_file("9606_symbol.tsv.gz")
36
+ """
37
+ if url is None:
38
+ url = gene_url()
39
+ return utils._download_file(cache, url + "/" + name, overwrite)
40
+
41
+
42
+ _gene_url = None
43
+
44
+
45
+ def gene_url(url: Optional[str] = None) -> str:
46
+ """
47
+ Get or set the base URL to the Gesel gene files, which is used in :py:func:`~download_gene_file`.
48
+
49
+ Args:
50
+ url:
51
+ The new gene URL.
52
+
53
+ Returns:
54
+ If ``url = None``, the URL to the Gesel gene is returned.
55
+ The default gene URL is set to the `GitHub releases page <https://github.com/LTLA/gesel-feedstock>`_.
56
+ This can be altered by setting the ``GESEL_GENE_URL`` environment variable.
57
+
58
+ If ``url`` is provided, this function sets the gene URL to ``url``, and returns the previous value of the URL.
59
+
60
+ Examples:
61
+ >>> import gesel
62
+ >>> gesel.gene_url()
63
+ >>> old = gesel.gene_url("https://foo.bar")
64
+ >>> gesel.gene_url(old)
65
+ >>> gesel.gene_url()
66
+ """
67
+
68
+ global _gene_url
69
+ if _gene_url is None:
70
+ import os
71
+ if "GESEL_GENE_URL" in os.environ:
72
+ _gene_url = os.environ["GESEL_GENE_URL"]
73
+ else:
74
+ _gene_url = "https://github.com/LTLA/gesel-feedstock/releases/download/genes-v1.0.0"
75
+
76
+ previous = _gene_url
77
+ if url is not None:
78
+ _gene_url = url
79
+ return previous
@@ -0,0 +1,79 @@
1
+ from typing import Optional
2
+ import biocframe
3
+ from . import _utils as utils
4
+ from . import _new_config as cfg
5
+
6
+
7
+ def fetch_all_collections(species: str, config: Optional[dict] = None) -> biocframe.BiocFrame:
8
+ """
9
+ Fetch information about all gene set collections in the Gesel database.
10
+
11
+ Args:
12
+ species:
13
+ NCBI taxonomy ID of the species of interest.
14
+
15
+ config:
16
+ Configuration object, typically created by :py:func:`~gesel.new_config`.
17
+ If ``None``, the default configuration is used.
18
+
19
+ Returns:
20
+ A :py:class:`~biocframe.BiocFrame` where each row represents a collection.
21
+ This contains the following columns:
22
+
23
+ - ``title``, string containing the title of the collection.
24
+ - ``description``, string containing a description of the collection.
25
+ - ``maintainer``, string containing the identity of the collection's maintainer.
26
+ - ``source``, string containing the source of origin of the collection.
27
+ - ``start``, integer containing the set index of the first gene set in this collection.
28
+ The set index refers to a row in the data frame returned by :py:func:`~gesel.fetch_all_sets`.
29
+ - ``size``, integer specifying the number of gene sets in the collection.
30
+
31
+ If this function is called once, the data frame will be cached in memory and re-used in subsequent calls.
32
+ The cached information will also be used to speed up :py:func:`~gesel.fetch_some_collections`.
33
+
34
+ Examples:
35
+ >>> import gesel
36
+ >>> df = gesel.fetch_all_collections("9606")
37
+ >>> print(df)
38
+ """
39
+
40
+ config = cfg._get_config(config)
41
+ candidate = cfg._get_cache(config, "fetch_all_collections", species)
42
+ if candidate is not None:
43
+ return candidate
44
+
45
+ fname = species + "_collections.tsv.gz"
46
+ path = cfg._fetch_file(config, fname)
47
+
48
+ title = []
49
+ desc = []
50
+ maintainer = []
51
+ src = []
52
+ start = []
53
+ size = []
54
+ last = 0
55
+
56
+ import gzip
57
+ with gzip.open(path, "rb") as f:
58
+ for line in f:
59
+ details = line.decode("utf-8").split("\t")
60
+ title.append(details[0])
61
+ desc.append(details[1])
62
+ maintainer.append(details[3])
63
+ src.append(details[4])
64
+ start.append(last)
65
+ cursize = int(details[5])
66
+ size.append(cursize)
67
+ last += cursize
68
+
69
+ output = biocframe.BiocFrame({
70
+ "title": title,
71
+ "description": desc,
72
+ "maintainer": maintainer,
73
+ "source": src,
74
+ "start": start,
75
+ "size": size
76
+ })
77
+
78
+ cfg._set_cache(config, "fetch_all_collections", species, output)
79
+ return output
@@ -0,0 +1,70 @@
1
+ from . import _new_config as cfg
2
+ from typing import Optional
3
+ import biocframe
4
+
5
+
6
+ def fetch_all_genes(
7
+ species: str,
8
+ types: list = ["symbol", "entrez", "ensembl"],
9
+ config: Optional[dict] = None
10
+ ) -> biocframe.BiocFrame:
11
+ """
12
+ Fetch names and identifiers of various types for all genes.
13
+
14
+ Args:
15
+ species:
16
+ NCBI taxonomy ID of the species of interest.
17
+
18
+ types:
19
+ Types of gene names to return.
20
+ Typically one or more of ``symbol``, ``entrez``, and/or ``ensembl``.
21
+
22
+ config:
23
+ Configuration object, typically created by :py:func:`~gesel.new_config`.
24
+ If ``None``, the default configuration is used.
25
+
26
+ Returns:
27
+ A :py:class:`~biocframe.BiocFrame` where each row represents a gene.
28
+ Each column corresponds to one of the ``types`` and is a list of lists.
29
+ Each inner list in the column contains the names of the specified type for each gene.
30
+
31
+ Examples:
32
+ >>> import gesel
33
+ >>> df = gesel.fetch_all_genes("9606")
34
+ >>> print(df)
35
+ >>> print(df["symbol"][1:10])
36
+ """
37
+
38
+ config = cfg._get_config(config)
39
+ cached = cfg._get_cache(config, "fetch_all_genes", species)
40
+
41
+ modified = False
42
+ if cached is None:
43
+ cached = {}
44
+
45
+ output = {}
46
+ for t in types:
47
+ if t in cached:
48
+ output[t] = cached[t]
49
+ continue
50
+
51
+ path = cfg._fetch_gene(config, species + "_" + t + ".tsv.gz")
52
+ collected = []
53
+
54
+ import gzip
55
+ with gzip.open(path, "rb") as f:
56
+ for line in f:
57
+ line = line.rstrip()
58
+ if line == b"":
59
+ collected.append([])
60
+ else:
61
+ collected.append(line.decode("utf-8").split("\t"))
62
+
63
+ output[t] = collected
64
+ cached[t] = collected
65
+ modified = True
66
+
67
+ if modified:
68
+ cfg._set_cache(config, "fetch_all_genes", species, cached)
69
+
70
+ return biocframe.BiocFrame(output)
@@ -0,0 +1,81 @@
1
+ from typing import Optional
2
+ from . import _new_config as cfg
3
+ from ._fetch_all_collections import fetch_all_collections
4
+ import biocframe
5
+
6
+
7
+ def fetch_all_sets(species: str, config: Optional[dict] = None) -> biocframe.BiocFrame:
8
+ """
9
+ Fetch information about all gene sets in the Gesel database.
10
+
11
+ Args:
12
+ species:
13
+ NCBI taxonomy ID of the species of interest.
14
+
15
+ config:
16
+ Configuration object, typically created by :py:func:`~gesel.new_config`.
17
+ If ``None``, the default configuration is used.
18
+
19
+ Returns:
20
+ A :py:class:`~biocframe.BiocFrame` where each row represents a gene set.
21
+ This contains the following columns:
22
+
23
+ - ``name``, string containing the name of the gene set.
24
+ - ``description``, string containing a description of the gene set.
25
+ - ``size``: integer specifying the number of genes in this gene set.
26
+ - ``collection``: integer, the collection index of the collection that contains this gene set.
27
+ The collection index refers to a row of the data frame returned by :py:func:`~gesel.fetch_all_collections`.
28
+ - ``number``: integer, the position of the gene set inside the specified collection.
29
+ The set index of the current gene set is defined by adding ``number`` to the collection's ``start``.
30
+
31
+ If this function is called once, the data frame will be cached in memory and re-used in subsequent calls.
32
+ The cached information will also be used to speed up :py:func:`~gesel.fetch_some_sets`.
33
+
34
+ Examples:
35
+ >>> import gesel
36
+ >>> df = gesel.fetch_all_sets("9606")
37
+ >>> print(df)
38
+ """
39
+
40
+ config = cfg._get_config(config)
41
+ candidate = cfg._get_cache(config, "fetch_all_sets", species)
42
+ if candidate is not None:
43
+ return candidate
44
+
45
+ fname = species + "_sets.tsv.gz"
46
+ path = cfg._fetch_file(config, fname)
47
+
48
+ name = []
49
+ desc = []
50
+ size = []
51
+
52
+ import gzip
53
+ with gzip.open(path, "rb") as f:
54
+ for line in f:
55
+ details = line.rstrip().decode("utf-8").split("\t")
56
+ name.append(details[0])
57
+ desc.append(details[1])
58
+ size.append(int(details[2]))
59
+
60
+ info = fetch_all_collections(species, config=config)
61
+ collection, number = _compute_set_to_collection_indices(info["size"])
62
+
63
+ output = biocframe.BiocFrame({
64
+ "name": name,
65
+ "description": desc,
66
+ "size": size,
67
+ "collection": collection,
68
+ "number": number
69
+ })
70
+
71
+ cfg._set_cache(config, "fetch_all_sets", species, output)
72
+ return output
73
+
74
+
75
+ def _compute_set_to_collection_indices(collection_sizes: list) -> tuple:
76
+ collection = []
77
+ number = []
78
+ for cidx, csize in enumerate(collection_sizes):
79
+ collection += [cidx] * csize
80
+ number += list(range(csize))
81
+ return collection, number