genos-client 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.
Potentially problematic release.
This version of genos-client might be problematic. Click here for more details.
- docs/conf.py +106 -0
- genos/__init__.py +28 -0
- genos/api/__init__.py +19 -0
- genos/api/embedding_extractor.py +118 -0
- genos/api/health.py +123 -0
- genos/api/rna_seq_coverage_track_prediction.py +139 -0
- genos/api/variant_predictor.py +124 -0
- genos/client.py +124 -0
- genos/config.py +81 -0
- genos/exceptions.py +54 -0
- genos/plots/__init__.py +3 -0
- genos/plots/genomic_track_viewer.py +315 -0
- genos/utils/__init__.py +2 -0
- genos/utils/genomic_track_utils.py +308 -0
- genos_client-0.1.0.dist-info/METADATA +247 -0
- genos_client-0.1.0.dist-info/RECORD +19 -0
- genos_client-0.1.0.dist-info/WHEEL +5 -0
- genos_client-0.1.0.dist-info/licenses/LICENSE +21 -0
- genos_client-0.1.0.dist-info/top_level.txt +2 -0
docs/conf.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Configuration file for the Sphinx documentation builder
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from sphinx.ext.autodoc import between
|
|
7
|
+
|
|
8
|
+
# -- Path setup --------------------------------------------------------------
|
|
9
|
+
# Add the project root to sys.path so Sphinx can find modules
|
|
10
|
+
sys.path.insert(0, os.path.abspath('../..')) # Assuming docs/ is inside genos/docs/
|
|
11
|
+
|
|
12
|
+
# -- Project information -----------------------------------------------------
|
|
13
|
+
project = 'Genos SDK'
|
|
14
|
+
author = 'Genos Team'
|
|
15
|
+
copyright = f'{datetime.now().year}, {author}'
|
|
16
|
+
release = '0.1.0'
|
|
17
|
+
|
|
18
|
+
# -- General configuration ---------------------------------------------------
|
|
19
|
+
extensions = [
|
|
20
|
+
'sphinx.ext.autodoc', # Automatically generate documentation from docstrings
|
|
21
|
+
'sphinx.ext.viewcode', # Add links to highlighted source code
|
|
22
|
+
'sphinx.ext.napoleon', # Support for Google/NumPy style docstrings
|
|
23
|
+
'sphinx.ext.todo', # Support for TODO directives
|
|
24
|
+
'sphinx.ext.autosummary', # Generate API summary automatically
|
|
25
|
+
'myst_parser', # Support Markdown files
|
|
26
|
+
'nbsphinx', # Support Jupyter Notebooks
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
# Recognized source file suffixes
|
|
30
|
+
source_suffix = {
|
|
31
|
+
'.rst': 'restructuredtext',
|
|
32
|
+
'.md': 'markdown',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Automatically generate summary tables
|
|
36
|
+
autosummary_generate = True
|
|
37
|
+
autodoc_default_options = {
|
|
38
|
+
'members': True,
|
|
39
|
+
'undoc-members': True,
|
|
40
|
+
'private-members': False,
|
|
41
|
+
'special-members': '__init__',
|
|
42
|
+
'inherited-members': True,
|
|
43
|
+
'show-inheritance': True,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Notebook execution settings
|
|
48
|
+
nbsphinx_execute = 'never' # Do not execute notebook cells
|
|
49
|
+
nbsphinx_allow_errors = True # Ignore execution errors
|
|
50
|
+
|
|
51
|
+
# Avoid adding module names as prefixes to class names
|
|
52
|
+
add_module_names = False
|
|
53
|
+
|
|
54
|
+
# Autodoc default options
|
|
55
|
+
autodoc_default_options = {
|
|
56
|
+
'members': True,
|
|
57
|
+
'undoc-members': True,
|
|
58
|
+
'show-inheritance': True,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Do not add "View page source" link at the top
|
|
62
|
+
html_show_sourcelink = False
|
|
63
|
+
|
|
64
|
+
# Source code view settings
|
|
65
|
+
viewcode_follow_imported_members = False
|
|
66
|
+
|
|
67
|
+
# Enable TODOs in the documentation
|
|
68
|
+
todo_include_todos = True
|
|
69
|
+
|
|
70
|
+
# -- Options for HTML output -------------------------------------------------
|
|
71
|
+
html_theme = 'sphinx_rtd_theme'
|
|
72
|
+
html_theme_options = {
|
|
73
|
+
'collapse_navigation': False,
|
|
74
|
+
'navigation_depth': 4,
|
|
75
|
+
'style_nav_header_background': '#343131',
|
|
76
|
+
}
|
|
77
|
+
html_static_path = ['_static']
|
|
78
|
+
|
|
79
|
+
# -- Autodoc settings --------------------------------------------------------
|
|
80
|
+
autoclass_content = 'class' # Show only the class docstring, not __init__
|
|
81
|
+
autodoc_member_order = 'bysource' # Display members in source code order
|
|
82
|
+
|
|
83
|
+
# -- Suppress specific warnings ----------------------------------------------
|
|
84
|
+
suppress_warnings = [
|
|
85
|
+
'toc.not_readable',
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
# -- Napoleon settings ------------------------------------------------------
|
|
89
|
+
napoleon_google_docstring = True
|
|
90
|
+
napoleon_numpy_docstring = True
|
|
91
|
+
napoleon_include_init_with_doc = False
|
|
92
|
+
napoleon_use_ivar = True
|
|
93
|
+
napoleon_use_param = True
|
|
94
|
+
napoleon_use_rtype = True
|
|
95
|
+
|
|
96
|
+
# -- Custom docstring processing --------------------------------------------
|
|
97
|
+
# Remove unnecessary metadata lines like @File, @Author, @Date, @Desc
|
|
98
|
+
def process_docstring(app, what, name, obj, options, lines):
|
|
99
|
+
filtered = [
|
|
100
|
+
line for line in lines
|
|
101
|
+
if not line.startswith(("@File", "@Author", "@Mail", "@Date", "@Desc"))
|
|
102
|
+
]
|
|
103
|
+
lines[:] = filtered
|
|
104
|
+
|
|
105
|
+
def setup(app):
|
|
106
|
+
app.connect("autodoc-process-docstring", process_docstring)
|
genos/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
from .client import GenosClient
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def create_client(token: str = None, timeout: int = 30) -> GenosClient:
|
|
7
|
+
"""
|
|
8
|
+
Factory function to create a GenosClient instance.
|
|
9
|
+
|
|
10
|
+
Example:
|
|
11
|
+
>>> from genos import create_client
|
|
12
|
+
>>> client = create_client(token="your_token")
|
|
13
|
+
>>> result = client.variant_predict("hg19", "chr6", 51484075, "T", "G")
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
token (str, optional): API access token. Default: None.
|
|
17
|
+
base_url (str, optional): Base URL of the Genos service.
|
|
18
|
+
Defaults to environment variable GENOS_API_URL or "http://localhost:5000".
|
|
19
|
+
timeout (int, optional): Request timeout in seconds. Default: 30.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
GenosClient: A configured client instance ready to make API calls.
|
|
23
|
+
"""
|
|
24
|
+
return GenosClient(token=token, timeout=timeout)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
__all__ = ["GenosClient", "create_client"]
|
|
28
|
+
|
genos/api/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
# api/__init__.py
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Genos API subpackage.
|
|
7
|
+
|
|
8
|
+
This package contains modules that wrap individual Genos API endpoints:
|
|
9
|
+
- HealthAPI: /health endpoint
|
|
10
|
+
- VariantPredictorAPI: /predict endpoint
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .health import HealthAPI
|
|
14
|
+
from .variant_predictor import VariantPredictorAPI
|
|
15
|
+
from .embedding_extractor import EmbeddingExtractorAPI
|
|
16
|
+
from .rna_seq_coverage_track_prediction import RNASeqCoverageTrackPredictionAPI
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = ["HealthAPI", "VariantPredictorAPI", "EmbeddingExtractorAPI", "RNASeqCoverageTrackPredictionAPI"]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import requests
|
|
4
|
+
import time
|
|
5
|
+
import logging
|
|
6
|
+
from typing import List, Union
|
|
7
|
+
from .health import BaseAPI
|
|
8
|
+
from ..exceptions import APIRequestError, ValidationError
|
|
9
|
+
import torch
|
|
10
|
+
|
|
11
|
+
# 设置日志
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EmbeddingExtractorAPI(BaseAPI):
|
|
16
|
+
"""Wrapper class for the Embedding Extraction API endpoints.
|
|
17
|
+
|
|
18
|
+
This class handles requests to the embedding extraction endpoints,
|
|
19
|
+
including single sequence and batch processing.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, session: requests.Session, base_url: str, timeout: int = 30, config=None):
|
|
23
|
+
"""
|
|
24
|
+
Initialize the EmbeddingExtractorAPI client.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
session (requests.Session): Reusable HTTP session for API requests.
|
|
28
|
+
base_url (str): Base URL of the Genos service.
|
|
29
|
+
timeout (int, optional): Request timeout in seconds. Default is 30.
|
|
30
|
+
config (GenosConfig, optional): Configuration object for endpoint management.
|
|
31
|
+
"""
|
|
32
|
+
super().__init__(session, base_url, timeout)
|
|
33
|
+
self.config = config
|
|
34
|
+
|
|
35
|
+
def extract(self, sequence: str, model_name: str = "Genos-1.2B",
|
|
36
|
+
pooling_method: str = "mean") -> Union[dict, List[dict]]:
|
|
37
|
+
"""
|
|
38
|
+
Extracts a numerical embedding representation for a given nucleotide sequence.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
sequence (str ): DNA sequence string .
|
|
42
|
+
model_name (str, optional): Model name to use. Default is "Genos-1.2B".
|
|
43
|
+
Options: "Genos-1.2B", "Genos-10B"
|
|
44
|
+
pooling_method (str, optional): Pooling method. Default is "mean".
|
|
45
|
+
Options: "mean", "max", "last", "none"
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
dict:
|
|
49
|
+
- "token_count": number of tokens
|
|
50
|
+
- "embedding_shape": shape of embedding array
|
|
51
|
+
- "embedding_dim": dimension of embedding
|
|
52
|
+
- "embedding": embedding array (list)
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
ValueError: If sequence is not a valid string or list.
|
|
56
|
+
ValidationError: If parameters are invalid.
|
|
57
|
+
APIRequestError: If the API request fails.
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
>>> # Single sequence
|
|
61
|
+
>>> result = embedding_api.extract("ATCGATCGATCG")
|
|
62
|
+
>>> print(result['embedding_dim'])
|
|
63
|
+
4096
|
|
64
|
+
"""
|
|
65
|
+
# 判断是单个序列还是批量
|
|
66
|
+
if isinstance(sequence, str):
|
|
67
|
+
# 单个序列
|
|
68
|
+
return self._extract_single(sequence, model_name, pooling_method)
|
|
69
|
+
else:
|
|
70
|
+
raise ValueError("sequence must be a string or list of strings")
|
|
71
|
+
|
|
72
|
+
def _extract_single(self, sequence: str, model_name: str, pooling_method: str) -> dict:
|
|
73
|
+
"""内部方法:提取单个序列"""
|
|
74
|
+
# Validate input
|
|
75
|
+
if len(sequence) == 0:
|
|
76
|
+
raise ValidationError("sequence cannot be empty")
|
|
77
|
+
|
|
78
|
+
# Check sequence length limit (128K characters)
|
|
79
|
+
if len(sequence) > 128000:
|
|
80
|
+
raise ValidationError("sequence length cannot exceed 128,000 bases")
|
|
81
|
+
|
|
82
|
+
# Prepare request
|
|
83
|
+
# 从配置获取端点路径
|
|
84
|
+
if self.config:
|
|
85
|
+
endpoint = self.config.get_endpoint("embedding.extract")
|
|
86
|
+
else:
|
|
87
|
+
endpoint = "" # 默认值(向后兼容)
|
|
88
|
+
|
|
89
|
+
url = f"{self.base_url}{endpoint}"
|
|
90
|
+
payload = {
|
|
91
|
+
"sequence": sequence,
|
|
92
|
+
"model_name": model_name,
|
|
93
|
+
"pooling_method": pooling_method
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# Start timing
|
|
97
|
+
start_time = time.time()
|
|
98
|
+
|
|
99
|
+
# Use the base class method for request handling with token validation
|
|
100
|
+
data = self._make_request('POST', url, json=payload)
|
|
101
|
+
|
|
102
|
+
# Calculate elapsed time
|
|
103
|
+
elapsed_time = time.time() - start_time
|
|
104
|
+
|
|
105
|
+
# Check response status - new format has 'status' field
|
|
106
|
+
# if data.get("status") != 200:
|
|
107
|
+
# error_msg = data.get("messages", "Unknown API error")
|
|
108
|
+
# raise APIRequestError(f"API request failed: {error_msg}")
|
|
109
|
+
|
|
110
|
+
embedding = torch.tensor(data["result"]["embedding"])
|
|
111
|
+
data["result"]["embedding"] = embedding
|
|
112
|
+
# Print elapsed time to screen
|
|
113
|
+
logger.info(f"⏱️ Embedding extraction completed in {elapsed_time:.4f}s "
|
|
114
|
+
f"(sequence_length={data.get('sequence_length', 'N/A')})")
|
|
115
|
+
res = {}
|
|
116
|
+
for key in ("result", "status", "message"):
|
|
117
|
+
res[key] = data.get(key)
|
|
118
|
+
return res
|
genos/api/health.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import requests
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from ..exceptions import APIRequestError, PaymentInsufficientError, AuthenticationError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseAPI(ABC):
|
|
9
|
+
"""Base class for all Genos API endpoints with token validation and error handling.
|
|
10
|
+
|
|
11
|
+
This class provides common functionality for all API endpoints, including:
|
|
12
|
+
- Token validation and authentication error handling
|
|
13
|
+
- Payment/credit insufficiency checking
|
|
14
|
+
- Standardized error response handling
|
|
15
|
+
- HTTP status code validation
|
|
16
|
+
|
|
17
|
+
Only responses with status code 200 are considered successful and will proceed
|
|
18
|
+
to further analysis. All other status codes will raise appropriate exceptions
|
|
19
|
+
with the received error message.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, session: requests.Session, base_url: str, timeout: int = 30):
|
|
23
|
+
"""
|
|
24
|
+
Initialize the base API client.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
session (requests.Session): Reusable HTTP session for API requests.
|
|
28
|
+
base_url (str): Base URL of the Genos service.
|
|
29
|
+
timeout (int, optional): Request timeout in seconds. Default is 30.
|
|
30
|
+
"""
|
|
31
|
+
self.session = session
|
|
32
|
+
self.base_url = base_url.rstrip("/")
|
|
33
|
+
self.timeout = timeout
|
|
34
|
+
|
|
35
|
+
def _validate_response(self, response: requests.Response) -> dict:
|
|
36
|
+
"""
|
|
37
|
+
Validate API response and handle different status codes.
|
|
38
|
+
|
|
39
|
+
This method ensures that only successful responses (status code 200)
|
|
40
|
+
proceed to further analysis. All other status codes will print the
|
|
41
|
+
error information and raise appropriate exceptions.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
response (requests.Response): The HTTP response to validate.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
dict: JSON response data if status code is 200.
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
APIRequestError: For all non-200 status codes with server error message.
|
|
51
|
+
"""
|
|
52
|
+
status_code = response.status_code
|
|
53
|
+
|
|
54
|
+
if status_code == 200:
|
|
55
|
+
# Success - proceed with analysis
|
|
56
|
+
return response.json()
|
|
57
|
+
else:
|
|
58
|
+
# Error responses - print error info and raise exception
|
|
59
|
+
try:
|
|
60
|
+
error_data = response.json()
|
|
61
|
+
error_message = error_data.get('messages', error_data.get('message', f'HTTP {status_code} error'))
|
|
62
|
+
except:
|
|
63
|
+
error_message = f'HTTP {status_code} error'
|
|
64
|
+
|
|
65
|
+
# Print error status and message before raising exception
|
|
66
|
+
print(f"❌ API Error - Status Code: {status_code}")
|
|
67
|
+
print(f"❌ Error Message: {error_message}")
|
|
68
|
+
raise APIRequestError(f"API request failed: {error_message}", status_code)
|
|
69
|
+
|
|
70
|
+
def _make_request(self, method: str, url: str, **kwargs) -> dict:
|
|
71
|
+
"""
|
|
72
|
+
Make an HTTP request with automatic response validation.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
method (str): HTTP method (GET, POST, etc.).
|
|
76
|
+
url (str): Full URL for the request.
|
|
77
|
+
**kwargs: Additional arguments passed to requests.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
dict: JSON response data for successful requests (status code 200).
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
APIRequestError: For non-200 responses or network issues.
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
response = getattr(self.session, method.lower())(url, timeout=self.timeout, **kwargs)
|
|
87
|
+
return self._validate_response(response)
|
|
88
|
+
except requests.RequestException as e:
|
|
89
|
+
raise APIRequestError(f"Network request failed: {e}")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class HealthAPI(BaseAPI):
|
|
93
|
+
"""Wrapper class for the Health Check API endpoint.
|
|
94
|
+
|
|
95
|
+
This class handles requests to the `/health` endpoint, allowing clients
|
|
96
|
+
to verify that the Genos service is running and responsive.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(self, session: requests.Session, base_url: str, timeout: int = 30):
|
|
100
|
+
"""
|
|
101
|
+
Initialize the HealthAPI client.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
session (requests.Session): Reusable HTTP session for API requests.
|
|
105
|
+
base_url (str): Base URL of the Genos service.
|
|
106
|
+
timeout (int, optional): Request timeout in seconds. Default is 30.
|
|
107
|
+
"""
|
|
108
|
+
super().__init__(session, base_url, timeout)
|
|
109
|
+
|
|
110
|
+
def check(self) -> dict:
|
|
111
|
+
"""
|
|
112
|
+
Perform a health check request.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
dict: JSON response from the health endpoint, typically containing:
|
|
116
|
+
- "status": "healthy" or "unhealthy"
|
|
117
|
+
- "message": optional status message
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
APIRequestError: If the request fails or the server is unreachable.
|
|
121
|
+
"""
|
|
122
|
+
url = f"{self.base_url}/health"
|
|
123
|
+
return self._make_request('GET', url)
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import json
|
|
4
|
+
import requests
|
|
5
|
+
import time
|
|
6
|
+
import logging
|
|
7
|
+
from .health import BaseAPI
|
|
8
|
+
from ..exceptions import APIRequestError
|
|
9
|
+
|
|
10
|
+
CHROMOSOMES = ['chr1', 'chr2', 'chr3', 'chr4', 'chr5', 'chr6', 'chr7', 'chr8', 'chr9', 'chr10', 'chr11', 'chr12',
|
|
11
|
+
'chr13', 'chr14', 'chr15', 'chr16', 'chr17', 'chr18', 'chr19', 'chr20', 'chr21', 'chr22']
|
|
12
|
+
|
|
13
|
+
# 设置日志
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
# Dictionary mapping chromosome names to their lengths (GRCh38/hg38)
|
|
16
|
+
CHROM_LENGTHS = {
|
|
17
|
+
'chr1': 248956422,
|
|
18
|
+
'chr2': 242193529,
|
|
19
|
+
'chr3': 198295559,
|
|
20
|
+
'chr4': 190214555,
|
|
21
|
+
'chr5': 181538259,
|
|
22
|
+
'chr6': 170805979,
|
|
23
|
+
'chr7': 159345973,
|
|
24
|
+
'chr8': 145138636,
|
|
25
|
+
'chr9': 138394717,
|
|
26
|
+
'chr10': 133797422,
|
|
27
|
+
'chr11': 135086622,
|
|
28
|
+
'chr12': 133275309,
|
|
29
|
+
'chr13': 114364328,
|
|
30
|
+
'chr14': 107043718,
|
|
31
|
+
'chr15': 101991189,
|
|
32
|
+
'chr16': 90338345,
|
|
33
|
+
'chr17': 83257441,
|
|
34
|
+
'chr18': 80373285,
|
|
35
|
+
'chr19': 58617616,
|
|
36
|
+
'chr20': 64444167,
|
|
37
|
+
'chr21': 46709983,
|
|
38
|
+
'chr22': 50818468,
|
|
39
|
+
'chrX': 156040895,
|
|
40
|
+
'chrY': 57227415,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def check_chromosome(chrom: str) -> bool:
|
|
45
|
+
"""
|
|
46
|
+
Check if the chromosome is valid.
|
|
47
|
+
"""
|
|
48
|
+
return chrom in CHROMOSOMES
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_start_position_range(chrom: str, gen_length: int = 32000):
|
|
52
|
+
"""
|
|
53
|
+
Return the allowed range for start_pos for the given chromosome.
|
|
54
|
+
Allowed: 1 ~ (chrom_length - 32000), inclusive.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
ValueError: If the chromosome is not recognized.
|
|
58
|
+
"""
|
|
59
|
+
if chrom not in CHROM_LENGTHS:
|
|
60
|
+
raise ValueError(f"Unknown chromosome: {chrom}")
|
|
61
|
+
chrom_length = CHROM_LENGTHS[chrom]
|
|
62
|
+
# Ensure the end is not less than 1
|
|
63
|
+
end_pos = max(1, chrom_length - gen_length)
|
|
64
|
+
return end_pos # inclusive of end_pos
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def check_valid_parameters(chrom: str, start_pos: int, gen_length: int = 32000):
|
|
68
|
+
"""
|
|
69
|
+
Check if the parameters are valid.
|
|
70
|
+
Args:
|
|
71
|
+
chrom (str): Chromosome name.
|
|
72
|
+
start_pos (int): Start position.
|
|
73
|
+
gen_length (int): Generation length.
|
|
74
|
+
Returns:
|
|
75
|
+
True if the parameters are valid, False otherwise.
|
|
76
|
+
"""
|
|
77
|
+
# check chromosome
|
|
78
|
+
if not check_chromosome(chrom):
|
|
79
|
+
raise ValueError(f"Invalid chromosome: {chrom}. Allowed: {', '.join(CHROMOSOMES)}")
|
|
80
|
+
# check start position
|
|
81
|
+
if not isinstance(start_pos, int) or start_pos <= 0:
|
|
82
|
+
raise ValueError(f"Start position must be a positive integer, but got {start_pos}")
|
|
83
|
+
end_pos_max = get_start_position_range(chrom, gen_length)
|
|
84
|
+
if start_pos > end_pos_max:
|
|
85
|
+
raise ValueError(f"Start position must be less than {end_pos_max}, but got {start_pos}")
|
|
86
|
+
return True
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class RNASeqCoverageTrackPredictionAPI(BaseAPI):
|
|
90
|
+
"""Client for the RNA-seq Coverage Track Prediction service in GeneOS.
|
|
91
|
+
|
|
92
|
+
This class provides access to RNA-seq Coverage Track Prediction
|
|
93
|
+
models, allowing users to predict the coverage track of an RNA-seq
|
|
94
|
+
experiment based on the genomic coordinates.
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(self, session: requests.Session, base_url: str, timeout: int = 30):
|
|
99
|
+
"""
|
|
100
|
+
Initialize the RNA-seq Coverage Track Prediction API module.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
session (requests.Session): Shared HTTP session from GenosClient.
|
|
104
|
+
base_url (str): Endpoint URL for RNA-seq Coverage Track Prediction service.
|
|
105
|
+
timeout (int, optional): Request timeout (seconds). Default: 30.
|
|
106
|
+
"""
|
|
107
|
+
super().__init__(session, base_url, timeout)
|
|
108
|
+
|
|
109
|
+
def predict(self, chrom: str, start_pos: int) -> dict:
|
|
110
|
+
"""
|
|
111
|
+
Predict the coverage track of an RNA-seq experiment based on the genomic coordinates.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
chrom (str): Chromosome name.
|
|
115
|
+
start_pos (int): Start position.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
dict: A JSON response from the RNA-seq Coverage Track Prediction model containing the predicted coverage track.
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
ValueError: If the parameters are invalid.
|
|
122
|
+
APIRequestError: If the API request fails.
|
|
123
|
+
"""
|
|
124
|
+
# check parameters
|
|
125
|
+
check_valid_parameters(chrom, start_pos)
|
|
126
|
+
|
|
127
|
+
payload = {"chrom": chrom, "start_pos": start_pos}
|
|
128
|
+
|
|
129
|
+
start_time = time.time()
|
|
130
|
+
# Use the base class method for request handling with token validation
|
|
131
|
+
data = self._make_request('POST', self.base_url, json=payload)
|
|
132
|
+
elapsed_time = time.time() - start_time
|
|
133
|
+
print(f"RNA-seq coverage track prediction completed in {elapsed_time:.4f}s")
|
|
134
|
+
res = {}
|
|
135
|
+
for key in ("result", "status", "message"):
|
|
136
|
+
res[key] = data.get(key)
|
|
137
|
+
return res
|
|
138
|
+
|
|
139
|
+
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
import time
|
|
6
|
+
import logging
|
|
7
|
+
from .health import BaseAPI
|
|
8
|
+
from ..exceptions import APIRequestError
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
CHROMOSOMES = ['chr1', 'chr2', 'chr3', 'chr4', 'chr5', 'chr6', 'chr7', 'chr8', 'chr9', 'chr10', 'chr11', 'chr12', 'chr13', 'chr14', 'chr15', 'chr16', 'chr17', 'chr18', 'chr19', 'chr20', 'chr21', 'chr22', 'chrX', 'chrY']
|
|
13
|
+
|
|
14
|
+
ALLOWED_RANGES = {
|
|
15
|
+
'hg38': {'chr1': (10001, 248946422), 'chr2': (10001, 242183529), 'chr3': (10001, 198235559), 'chr4': (10001, 190204555), 'chr5': (10001, 181478259), 'chr6': (60001, 170745979), 'chr7': (10001, 159335973), 'chr8': (60001, 145078636), 'chr9': (10001, 138334717), 'chr10': (10001, 133787422), 'chr11': (60001, 135076622), 'chr12': (10001, 133265309), 'chr13': (16000001, 114354328), 'chr14': (16000001, 106883718), 'chr15': (17000001, 101981189), 'chr16': (10001, 90228345), 'chr17': (60001, 83247441), 'chr18': (10001, 80263285), 'chr19': (60001, 58607616), 'chr20': (60001, 64334167), 'chr21': (5010001, 46699983), 'chr22': (10510001, 50808468), 'chrX': (10001, 156030895), 'chrY': (10001, 57217415)},
|
|
16
|
+
'hg19': {'chr1': (10001, 249240621), 'chr2': (10001, 243189373), 'chr3': (60001, 197962430), 'chr4': (10001, 191044276), 'chr5': (10001, 180905260), 'chr6': (60001, 171055067), 'chr7': (10001, 159128663), 'chr8': (10001, 146304022), 'chr9': (10001, 141153431), 'chr10': (60001, 135524747), 'chr11': (60001, 134946516), 'chr12': (60001, 133841895), 'chr13': (19020001, 115109878), 'chr14': (19000001, 107289540), 'chr15': (20000001, 102521392), 'chr16': (60001, 90294753), 'chr17': (1, 81195210), 'chr18': (10001, 78017248), 'chr19': (60001, 59118983), 'chr20': (60001, 62965520), 'chr21': (9411194, 48119895), 'chr22': (16050001, 51244566), 'chrX': (60001, 155260560), 'chrY': (10001, 59363566)}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def check_chromosome(chrom: str) -> bool:
|
|
21
|
+
"""
|
|
22
|
+
Check if the chromosome is valid.
|
|
23
|
+
"""
|
|
24
|
+
return chrom in CHROMOSOMES
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def check_position(assembly: str, chrom: str, pos: int) -> bool:
|
|
28
|
+
"""
|
|
29
|
+
Check if the position is valid.
|
|
30
|
+
"""
|
|
31
|
+
return ALLOWED_RANGES[assembly][chrom][0] <= pos <= ALLOWED_RANGES[assembly][chrom][1]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class VariantPredictorAPI(BaseAPI):
|
|
36
|
+
"""Wrapper class for the Variant Prediction API endpoint.
|
|
37
|
+
|
|
38
|
+
This class handles requests to the `/predict` endpoint for variant
|
|
39
|
+
pathogenicity prediction. It validates input and raises structured
|
|
40
|
+
SDK exceptions on errors.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, session: requests.Session, base_url: str, timeout: int = 30):
|
|
44
|
+
"""
|
|
45
|
+
Initialize the VariantPredictorAPI client.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
session (requests.Session): Reusable HTTP session for API requests.
|
|
49
|
+
base_url (str): Base URL of the Genos service.
|
|
50
|
+
timeout (int, optional): Request timeout in seconds. Default is 30.
|
|
51
|
+
"""
|
|
52
|
+
super().__init__(session, base_url, timeout)
|
|
53
|
+
|
|
54
|
+
def predict(self, assembly: str, chrom: str, pos: int, ref: str, alt: str) -> dict:
|
|
55
|
+
"""
|
|
56
|
+
Predict the pathogenicity of a genetic variant.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
assembly (str): Reference genome version, allowed values: 'hg38' or 'hg19'.
|
|
60
|
+
chrom (str): Chromosome, e.g., 'chr6'.
|
|
61
|
+
pos (int): Position, 1-based coordinate.
|
|
62
|
+
ref (str): Reference allele, single letter or sequence.
|
|
63
|
+
alt (str): Alternate allele, single letter or sequence.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
dict: Prediction result, typically containing:
|
|
67
|
+
- "variant": input variant
|
|
68
|
+
- "prediction": "Pathogenic" or "Benign"
|
|
69
|
+
- "score_Benign": float
|
|
70
|
+
- "score_Pathogenic": float
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
ValueError: If any argument is invalid.
|
|
74
|
+
APIRequestError: If the API request fails or the server returns an error.
|
|
75
|
+
"""
|
|
76
|
+
# Validate assembly
|
|
77
|
+
allowed_assemblies = ["hg38", "hg19"]
|
|
78
|
+
if assembly not in allowed_assemblies:
|
|
79
|
+
raise ValueError(f"assembly must be one of {allowed_assemblies}, but got '{assembly}'")
|
|
80
|
+
|
|
81
|
+
# Validate chromosome
|
|
82
|
+
if not check_chromosome(chrom):
|
|
83
|
+
raise ValueError(f"Invalid chromosome: {chrom}. Allowed: {', '.join(CHROMOSOMES)}")
|
|
84
|
+
|
|
85
|
+
# Validate position
|
|
86
|
+
if not isinstance(pos, int) or not check_position(assembly, chrom, pos):
|
|
87
|
+
raise ValueError(f"Position {pos} not allowed on {assembly} {chrom}")
|
|
88
|
+
|
|
89
|
+
# Validate reference and alternate alleles
|
|
90
|
+
valid_bases = set('ATCGN')
|
|
91
|
+
if not isinstance(ref, str) or len(ref) == 0 or any(base.upper() not in valid_bases for base in ref):
|
|
92
|
+
raise ValueError("ref allele must be a non-empty string composed only of nucleotides A, T, C, G, or N")
|
|
93
|
+
|
|
94
|
+
if not isinstance(alt, str) or len(alt) == 0 or any(base.upper() not in valid_bases for base in alt):
|
|
95
|
+
raise ValueError("alt allele must be a non-empty string composed only of nucleotides A, T, C, G, or N")
|
|
96
|
+
|
|
97
|
+
# Prepare payload with individual parameters
|
|
98
|
+
url = f"{self.base_url}"
|
|
99
|
+
payload = {
|
|
100
|
+
"assembly": assembly,
|
|
101
|
+
"chrom": chrom,
|
|
102
|
+
"pos": pos,
|
|
103
|
+
"ref": ref.upper(),
|
|
104
|
+
"alt": alt.upper()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
start_time = time.time()
|
|
108
|
+
|
|
109
|
+
# Use the base class method for request handling with token validation
|
|
110
|
+
data = self._make_request('POST', url, json=payload)
|
|
111
|
+
|
|
112
|
+
elapsed_time = time.time() - start_time
|
|
113
|
+
|
|
114
|
+
# Check response status - new format has 'status' field
|
|
115
|
+
# if data.get("status") != 200:
|
|
116
|
+
# error_msg = data.get("messages", "Unknown API error")
|
|
117
|
+
# raise APIRequestError(f"API request failed: {error_msg}")
|
|
118
|
+
|
|
119
|
+
logger.info(f"Variant prediction completed in {elapsed_time:.4f}s ")
|
|
120
|
+
|
|
121
|
+
res = {}
|
|
122
|
+
for key in ("result", "status", "message"):
|
|
123
|
+
res[key] = data.get(key)
|
|
124
|
+
return res
|