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