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 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)