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 +48 -0
- gesel/_download_database_file.py +79 -0
- gesel/_download_database_ranges.py +254 -0
- gesel/_download_gene_file.py +79 -0
- gesel/_fetch_all_collections.py +79 -0
- gesel/_fetch_all_genes.py +70 -0
- gesel/_fetch_all_sets.py +81 -0
- gesel/_fetch_genes_for_all_sets.py +56 -0
- gesel/_fetch_genes_for_some_sets.py +94 -0
- gesel/_fetch_sets_for_all_genes.py +55 -0
- gesel/_fetch_sets_for_some_genes.py +136 -0
- gesel/_fetch_some_collections.py +158 -0
- gesel/_fetch_some_sets.py +149 -0
- gesel/_find_overlapping_sets.py +129 -0
- gesel/_map_genes_by_name.py +95 -0
- gesel/_new_config.py +183 -0
- gesel/_search_genes.py +65 -0
- gesel/_search_set_text.py +210 -0
- gesel/_utils.py +98 -0
- gesel-0.1.0.dist-info/METADATA +229 -0
- gesel-0.1.0.dist-info/RECORD +24 -0
- gesel-0.1.0.dist-info/WHEEL +5 -0
- gesel-0.1.0.dist-info/licenses/LICENSE.txt +21 -0
- gesel-0.1.0.dist-info/top_level.txt +1 -0
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)
|
gesel/_fetch_all_sets.py
ADDED
|
@@ -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
|