pub-worm 0.3.17__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.
- pub_worm/__init__.py +1 -0
- pub_worm/biorxiv/__init__.py +0 -0
- pub_worm/biorxiv/biorxiv_api.py +215 -0
- pub_worm/ensembl/__init__.py +0 -0
- pub_worm/ensembl/ensembl_api.py +96 -0
- pub_worm/impact_factor/__init__.py +11 -0
- pub_worm/impact_factor/data/2022_JCR_IF.csv +12428 -0
- pub_worm/impact_factor/impact_factor_lookup.py +10 -0
- pub_worm/ncbi/__init__.py +1 -0
- pub_worm/ncbi/entreze_api.py +463 -0
- pub_worm/orcid/__init__.py +0 -0
- pub_worm/orcid/orcid_api.py +118 -0
- pub_worm/wormbase/__init__.py +9 -0
- pub_worm/wormbase/data/wormbase_field_gene.json +79 -0
- pub_worm/wormbase/data/wormbase_field_paper.json +8 -0
- pub_worm/wormbase/data/wormbase_widget_gene.json +55 -0
- pub_worm/wormbase/to_csv_helpers.py +35 -0
- pub_worm/wormbase/wormbase_api.py +274 -0
- pub_worm/wormbase/wormbase_util.py +241 -0
- pub_worm-0.3.17.dist-info/METADATA +103 -0
- pub_worm-0.3.17.dist-info/RECORD +22 -0
- pub_worm-0.3.17.dist-info/WHEEL +4 -0
pub_worm/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.17"
|
|
File without changes
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
from typing import Any, List, Optional, Union
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
from bs4 import BeautifulSoup
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Funder(BaseModel):
|
|
10
|
+
name: Optional[str] = ""
|
|
11
|
+
id: Optional[str] = ""
|
|
12
|
+
id_type: Optional[str] = ""
|
|
13
|
+
award: Optional[str] = ""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Paper(BaseModel):
|
|
17
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
18
|
+
|
|
19
|
+
title: str = ""
|
|
20
|
+
authors: str = ""
|
|
21
|
+
author_corresponding: Optional[str] = ""
|
|
22
|
+
author_corresponding_institution: Optional[str] = ""
|
|
23
|
+
doi: str = ""
|
|
24
|
+
date: str = ""
|
|
25
|
+
version: Union[int, str] = "1"
|
|
26
|
+
type: Optional[str] = ""
|
|
27
|
+
license: Optional[str] = ""
|
|
28
|
+
category: Optional[str] = ""
|
|
29
|
+
jatsxml: Optional[str] = ""
|
|
30
|
+
abstract: Optional[str] = ""
|
|
31
|
+
funder: Union[str, List[Funder]] = ""
|
|
32
|
+
published: Optional[str] = ""
|
|
33
|
+
server: Optional[str] = "bioRxiv"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Message(BaseModel):
|
|
37
|
+
status: str
|
|
38
|
+
category: Optional[str] = None
|
|
39
|
+
interval: Optional[str] = None
|
|
40
|
+
funder: Optional[str] = None
|
|
41
|
+
cursor: Optional[Union[int, str]] = 0
|
|
42
|
+
count: Optional[Union[int, str]] = 0
|
|
43
|
+
count_new_papers: Optional[Union[int, str]] = 0
|
|
44
|
+
total: Optional[Union[int, str]] = 0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class BioRxivResponse(BaseModel):
|
|
48
|
+
messages: List[Message] = Field(default_factory=list)
|
|
49
|
+
collection: List[Paper] = Field(default_factory=list)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def is_ok(self) -> bool:
|
|
53
|
+
return bool(self.messages and self.messages[0].status == "ok")
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def error_message(self) -> Optional[str]:
|
|
57
|
+
if not self.messages:
|
|
58
|
+
return "Empty response messages"
|
|
59
|
+
if self.messages[0].status != "ok":
|
|
60
|
+
return self.messages[0].status
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def total(self) -> int:
|
|
65
|
+
if self.messages and self.messages[0].total is not None:
|
|
66
|
+
try:
|
|
67
|
+
return int(self.messages[0].total)
|
|
68
|
+
except (ValueError, TypeError):
|
|
69
|
+
return 0
|
|
70
|
+
return 0
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def count(self) -> int:
|
|
74
|
+
if self.messages and self.messages[0].count is not None:
|
|
75
|
+
try:
|
|
76
|
+
return int(self.messages[0].count)
|
|
77
|
+
except (ValueError, TypeError):
|
|
78
|
+
return len(self.collection)
|
|
79
|
+
return len(self.collection)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def biorxiv_most_recent_30_posts() -> bytes:
|
|
83
|
+
"""Retrieve raw XML content for the most recent 30 posts on bioRxiv."""
|
|
84
|
+
url = "https://connect.biorxiv.org/biorxiv_xml.php?subject=all"
|
|
85
|
+
response = requests.get(url, timeout=20)
|
|
86
|
+
if response.status_code == 200:
|
|
87
|
+
return response.content
|
|
88
|
+
else:
|
|
89
|
+
raise RuntimeError(f"Failed to retrieve data from bioRxiv feed: {response.status_code}")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Backwards compatibility alias
|
|
93
|
+
biorxiv_most_recent_30__posts = biorxiv_most_recent_30_posts
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _contains_keywords(text: str, keywords: list[str]) -> bool:
|
|
97
|
+
"""Check if any keyword is contained in the text (case-insensitive)."""
|
|
98
|
+
text_lower = text.lower()
|
|
99
|
+
return any(keyword.lower() in text_lower for keyword in keywords)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def biorxiv_recent_posts_filtered(
|
|
103
|
+
keywords: list[str] = ["caenorhabditis", "elegans"],
|
|
104
|
+
) -> list[dict[str, str]]:
|
|
105
|
+
"""Retrieve recent posts from the bioRxiv RSS feed matching given keywords."""
|
|
106
|
+
xml_data = biorxiv_most_recent_30_posts()
|
|
107
|
+
soup = BeautifulSoup(xml_data, "xml")
|
|
108
|
+
articles: list[dict[str, str]] = []
|
|
109
|
+
|
|
110
|
+
for item in soup.find_all("item"):
|
|
111
|
+
title_tag = item.find("title")
|
|
112
|
+
description_tag = item.find("description")
|
|
113
|
+
date_tag = item.find("dc:date")
|
|
114
|
+
identifier_tag = item.find("dc:identifier")
|
|
115
|
+
|
|
116
|
+
title = title_tag.get_text(strip=True) if title_tag else ""
|
|
117
|
+
description = description_tag.get_text(strip=True) if description_tag else ""
|
|
118
|
+
dc_date = date_tag.get_text(strip=True) if date_tag else ""
|
|
119
|
+
dc_identifier = identifier_tag.get_text(strip=True) if identifier_tag else ""
|
|
120
|
+
|
|
121
|
+
if _contains_keywords(title, keywords) or _contains_keywords(description, keywords):
|
|
122
|
+
doi_url = f"https://doi.org/{dc_identifier}" if dc_identifier else ""
|
|
123
|
+
articles.append({
|
|
124
|
+
"title": title,
|
|
125
|
+
"date": dc_date,
|
|
126
|
+
"doi": doi_url,
|
|
127
|
+
"url": doi_url,
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
return articles
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def fetch_biorxiv_page(
|
|
134
|
+
start_date: str,
|
|
135
|
+
end_date: str,
|
|
136
|
+
cursor: int = 0,
|
|
137
|
+
session: Optional[requests.Session] = None,
|
|
138
|
+
timeout: int = 30,
|
|
139
|
+
) -> BioRxivResponse:
|
|
140
|
+
"""Fetch a single page (up to 30 items) from the bioRxiv REST API."""
|
|
141
|
+
url = f"https://api.biorxiv.org/details/biorxiv/{start_date}/{end_date}/{cursor}/json"
|
|
142
|
+
client = session if session is not None else requests
|
|
143
|
+
try:
|
|
144
|
+
response = client.get(url, timeout=timeout)
|
|
145
|
+
if response.status_code != 200:
|
|
146
|
+
return BioRxivResponse(
|
|
147
|
+
messages=[Message(status=f"HTTP {response.status_code}")],
|
|
148
|
+
collection=[],
|
|
149
|
+
)
|
|
150
|
+
data: dict[str, Any] = response.json()
|
|
151
|
+
return BioRxivResponse.model_validate(data)
|
|
152
|
+
except Exception as error:
|
|
153
|
+
return BioRxivResponse(
|
|
154
|
+
messages=[Message(status=f"Request error: {error}")],
|
|
155
|
+
collection=[],
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def biorxiv_search(
|
|
160
|
+
search_criteria: str = "caenorhabditis elegans",
|
|
161
|
+
days: int = 1,
|
|
162
|
+
verbose: bool = True,
|
|
163
|
+
) -> list[dict[str, str]]:
|
|
164
|
+
"""Search bioRxiv for papers published in the last `days` matching criteria via REST API."""
|
|
165
|
+
today = datetime.today()
|
|
166
|
+
start_date = (today - timedelta(days=days)).strftime("%Y-%m-%d")
|
|
167
|
+
end_date = today.strftime("%Y-%m-%d")
|
|
168
|
+
|
|
169
|
+
cursor = 0
|
|
170
|
+
results: list[dict[str, str]] = []
|
|
171
|
+
keywords = [kw.strip().lower() for kw in search_criteria.lower().split() if kw.strip()]
|
|
172
|
+
|
|
173
|
+
with requests.Session() as session:
|
|
174
|
+
while True:
|
|
175
|
+
response_obj = fetch_biorxiv_page(
|
|
176
|
+
start_date=start_date,
|
|
177
|
+
end_date=end_date,
|
|
178
|
+
cursor=cursor,
|
|
179
|
+
session=session,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if not response_obj.is_ok or not response_obj.collection:
|
|
183
|
+
if verbose and response_obj.error_message:
|
|
184
|
+
print(f"bioRxiv API: {response_obj.error_message}", flush=True)
|
|
185
|
+
break
|
|
186
|
+
|
|
187
|
+
for paper in response_obj.collection:
|
|
188
|
+
full_text = f"{paper.title} {paper.abstract}".lower()
|
|
189
|
+
if any(kw in full_text for kw in keywords):
|
|
190
|
+
doi_url = f"https://doi.org/{paper.doi}" if paper.doi else ""
|
|
191
|
+
results.append({
|
|
192
|
+
"title": paper.title,
|
|
193
|
+
"url": doi_url,
|
|
194
|
+
"doi": paper.doi,
|
|
195
|
+
"date": paper.date,
|
|
196
|
+
"category": paper.category or "",
|
|
197
|
+
"authors": paper.authors,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
total = response_obj.total
|
|
201
|
+
count = response_obj.count
|
|
202
|
+
if count <= 0:
|
|
203
|
+
break
|
|
204
|
+
|
|
205
|
+
cursor += count
|
|
206
|
+
if verbose:
|
|
207
|
+
print(
|
|
208
|
+
f"Fetched {cursor}/{total} papers ({len(results)} matching)...",
|
|
209
|
+
flush=True,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if cursor >= total:
|
|
213
|
+
break
|
|
214
|
+
|
|
215
|
+
return results
|
|
File without changes
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import aiofiles
|
|
4
|
+
import aiohttp
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_sequence_region(start_position, end_position, chromosome, species = "caenorhabditis_elegans"):
|
|
9
|
+
sequence = ""
|
|
10
|
+
try:
|
|
11
|
+
# Construct the URL for the Ensembl API
|
|
12
|
+
url = f"https://rest.ensembl.org/sequence/region/{species}/{chromosome}:{start_position}..{end_position}?content-type=text/plain"
|
|
13
|
+
|
|
14
|
+
# Send the GET request
|
|
15
|
+
response = requests.get(url, timeout=10)
|
|
16
|
+
|
|
17
|
+
# Check if the request was successful
|
|
18
|
+
if response.status_code == 200:
|
|
19
|
+
sequence = response.text
|
|
20
|
+
else:
|
|
21
|
+
print(f"Failed to retrieve sequence. Status code: {response.status_code}")
|
|
22
|
+
|
|
23
|
+
except requests.exceptions.Timeout:
|
|
24
|
+
print("The request timed out. Please try again later.")
|
|
25
|
+
except requests.exceptions.RequestException as e:
|
|
26
|
+
print(f"An error occurred: {e}")
|
|
27
|
+
return sequence
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def async_get_sequence_region(start_position, end_position, chromosome, species="caenorhabditis_elegans", max_retries=3):
|
|
31
|
+
sequence = ""
|
|
32
|
+
url = f"https://rest.ensembl.org/sequence/region/{species}/{chromosome}:{start_position}..{end_position}?content-type=text/plain"
|
|
33
|
+
|
|
34
|
+
retries = 0
|
|
35
|
+
|
|
36
|
+
while retries < max_retries:
|
|
37
|
+
try:
|
|
38
|
+
async with aiohttp.ClientSession() as session:
|
|
39
|
+
async with session.get(url, timeout=10) as response:
|
|
40
|
+
# Check if the request was successful
|
|
41
|
+
if response.status == 200:
|
|
42
|
+
sequence = await response.text()
|
|
43
|
+
return sequence # Return if successful
|
|
44
|
+
elif response.status_code == 429:
|
|
45
|
+
retry_after = response.headers.get('Retry-After')
|
|
46
|
+
if retry_after:
|
|
47
|
+
print(f"Rate limited. Retrying after {retry_after} seconds...")
|
|
48
|
+
asyncio.sleep(float(retry_after)) # Wait for the time specified by the server
|
|
49
|
+
retries += 1 # Increment retry count
|
|
50
|
+
else:
|
|
51
|
+
print("Rate limited. No Retry-After Waiting 2 seconds...")
|
|
52
|
+
asyncio.sleep(2) # Wait for the time specified by the server
|
|
53
|
+
retries += 1 # Increment retry count
|
|
54
|
+
|
|
55
|
+
else:
|
|
56
|
+
print(f"Failed to retrieve sequence. Status code: {response.status}")
|
|
57
|
+
retries += 1
|
|
58
|
+
await asyncio.sleep(2) # Wait before retrying
|
|
59
|
+
|
|
60
|
+
except Exception as e:
|
|
61
|
+
print(f"An error occurred: {e} Retry {retries + 1}/{max_retries}.")
|
|
62
|
+
retries += 1
|
|
63
|
+
await asyncio.sleep(2) # Wait before retrying
|
|
64
|
+
|
|
65
|
+
print(f"Failed to retrieve sequence after {max_retries} attempts.")
|
|
66
|
+
return sequence # Return empty sequence if retries exhausted
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def create_fasta(gene_nm, chromosome, sequence, path="."):
|
|
71
|
+
# Define the filename based on the gene name
|
|
72
|
+
filename = f"{path}/{gene_nm}.fasta"
|
|
73
|
+
|
|
74
|
+
# Open the file in write mode
|
|
75
|
+
with open(filename, 'w') as file:
|
|
76
|
+
# Write the header line
|
|
77
|
+
file.write(f">{chromosome}\n")
|
|
78
|
+
|
|
79
|
+
# Write the sequence in lines of 60 characters
|
|
80
|
+
for i in range(0, len(sequence), 60):
|
|
81
|
+
file.write(sequence[i:i+60] + "\n")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def async_create_fasta(gene_nm, chromosome, sequence, path="."):
|
|
85
|
+
# Define the filename based on the gene name
|
|
86
|
+
filename = f"{path}/{gene_nm}.fasta"
|
|
87
|
+
|
|
88
|
+
# Open the file in asynchronous write mode
|
|
89
|
+
async with aiofiles.open(filename, 'w') as file:
|
|
90
|
+
# Write the header line
|
|
91
|
+
await file.write(f">{chromosome}\n")
|
|
92
|
+
|
|
93
|
+
# Write the sequence in lines of 60 characters
|
|
94
|
+
for i in range(0, len(sequence), 60):
|
|
95
|
+
await file.write(sequence[i:i+60] + "\n")
|
|
96
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
module_name = __name__
|
|
7
|
+
module_spec = importlib.util.find_spec(module_name)
|
|
8
|
+
package_path = os.path.dirname(module_spec.origin)
|
|
9
|
+
file_path = os.path.join(package_path, 'data', '2022_JCR_IF.csv')
|
|
10
|
+
|
|
11
|
+
impact_factor_df = pd.read_csv(file_path)
|