simple_sams_api 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.
- simple_sams_api/__init__.py +6 -0
- simple_sams_api/base.py +200 -0
- simple_sams_api-0.1.0.dist-info/METADATA +106 -0
- simple_sams_api-0.1.0.dist-info/RECORD +7 -0
- simple_sams_api-0.1.0.dist-info/WHEEL +5 -0
- simple_sams_api-0.1.0.dist-info/licenses/LICENSE +21 -0
- simple_sams_api-0.1.0.dist-info/top_level.txt +1 -0
simple_sams_api/base.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
import requests
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
SAMS_URL = "https://www.genecascade.org/sams-cgi"
|
|
9
|
+
LOGIN_URL = f"{SAMS_URL}/login.cgi"
|
|
10
|
+
EXPORT_PHENOPACKETS_URL = f"{SAMS_URL}/ExportPhenopacket.cgi?export_all=1"
|
|
11
|
+
EXPORT_PHENOPACKET_BY_ID_URL = (
|
|
12
|
+
f"{SAMS_URL}/export_phenopacket.cgi?external_id={{patient_id}}"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class SAMSapi:
|
|
18
|
+
session: requests.Session = field(default_factory=requests.Session)
|
|
19
|
+
phenopackets: dict = None
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def loggedIn(self):
|
|
23
|
+
return "SAMSI" in self.session.cookies
|
|
24
|
+
|
|
25
|
+
def _login(self, username, password):
|
|
26
|
+
data = {"email": username, "password": password}
|
|
27
|
+
resp = self.session.post(LOGIN_URL, data=data)
|
|
28
|
+
resp.raise_for_status()
|
|
29
|
+
|
|
30
|
+
def login_with_credentials_file(self, credentials_file: str):
|
|
31
|
+
"""Login to SAMS using credentials from a file
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
credentials_file (str): Path to the file containing the credentials (first line username, second line password)
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
SAMS: Instance of SAMS
|
|
38
|
+
"""
|
|
39
|
+
with open(credentials_file) as f:
|
|
40
|
+
username, password = [l.strip() for l in f.readlines()]
|
|
41
|
+
self._login(username, password)
|
|
42
|
+
|
|
43
|
+
def login_with_username(self, username: str, password: str):
|
|
44
|
+
"""Login to SAMS using username and password
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
username (str): Name of the user
|
|
48
|
+
password (str): Password of the user
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
SAMS: Instance of SAMS
|
|
52
|
+
"""
|
|
53
|
+
self._login(username, password)
|
|
54
|
+
|
|
55
|
+
def get_phenopackets(self) -> List[dict]:
|
|
56
|
+
"""Load all phenopackets from SAMS for the current user
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
List[dict]: List of phenopackets
|
|
60
|
+
"""
|
|
61
|
+
resp = self.session.get(EXPORT_PHENOPACKETS_URL)
|
|
62
|
+
resp.raise_for_status()
|
|
63
|
+
all_data = resp.json()
|
|
64
|
+
return all_data
|
|
65
|
+
|
|
66
|
+
def get_phenopacket(self, patient_id: str) -> dict:
|
|
67
|
+
"""Get phenopacket for a specific patient
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
patient_id (str): ID of the patient
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
RuntimeError: If the phenopacket for the patient could not be found
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
dict: Phenopacket for the patient
|
|
77
|
+
"""
|
|
78
|
+
resp = self.session.get(
|
|
79
|
+
EXPORT_PHENOPACKET_BY_ID_URL.format(patient_id=patient_id)
|
|
80
|
+
)
|
|
81
|
+
resp.raise_for_status()
|
|
82
|
+
patient_data = resp.json()
|
|
83
|
+
if patient_data["subject"]["id"] != patient_id:
|
|
84
|
+
raise RuntimeError(
|
|
85
|
+
f"Failed to obtain phenopacket for external id {patient_id}"
|
|
86
|
+
)
|
|
87
|
+
return patient_data
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def extract_HPO_terms_from_phenopacket(
|
|
91
|
+
phenopacket: dict, ignore_excluded: bool = True
|
|
92
|
+
) -> str:
|
|
93
|
+
"""Extract HPO terms of a given phenopacket
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
phenopacket (dict): Phenopacket containing phenotypic features
|
|
97
|
+
ignore_excluded (bool, optional): Whether to ignore excluded phenotypic features. Defaults to True.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
str: String of HPO terms for the phenopacket in the format "HP:0000001 - Phenotype 1; HP:0000002 - Phenotype 2; ..."
|
|
101
|
+
If feature is excluded, it will be marked as "HP:0000001 - Phenotype 1 (excluded)"
|
|
102
|
+
"""
|
|
103
|
+
# Check if key exists
|
|
104
|
+
if "phenotypicFeatures" not in phenopacket:
|
|
105
|
+
sams_entry = phenopacket["subject"]["id"]
|
|
106
|
+
logger.warning(f"SAMS: No phenotypicFeatures found for {sams_entry}")
|
|
107
|
+
return ""
|
|
108
|
+
|
|
109
|
+
else:
|
|
110
|
+
phenotypes = phenopacket["phenotypicFeatures"] # Get HPO terms from phenopacket
|
|
111
|
+
|
|
112
|
+
pheno_strings = []
|
|
113
|
+
for feature in phenotypes:
|
|
114
|
+
pheno_string = f"{feature['type']['id']} - {feature['type']['label']}"
|
|
115
|
+
|
|
116
|
+
if feature.get("excluded", 0):
|
|
117
|
+
if ignore_excluded:
|
|
118
|
+
continue
|
|
119
|
+
else:
|
|
120
|
+
pheno_string += " (excluded)"
|
|
121
|
+
|
|
122
|
+
pheno_strings.append(pheno_string)
|
|
123
|
+
|
|
124
|
+
return "; ".join(pheno_strings)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def extract_disease_terms_from_phenopacket(
|
|
128
|
+
phenopacket: dict, ignore_excluded: bool = True
|
|
129
|
+
) -> str:
|
|
130
|
+
"""Extract disease terms (OMIM, ORPHANET)of a given phenopacket
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
phenopacket (dict): Phenopacket containing diseases
|
|
134
|
+
ignore_excluded (bool, optional): Whether to ignore excluded diseases. Defaults to True.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
str: String of disease terms for the phenopacket in the format "OMIM:0000001 - Disease 1; OMIM:0000002 - Disease 2; ..."
|
|
138
|
+
"""
|
|
139
|
+
if "diseases" not in phenopacket:
|
|
140
|
+
sams_entry = phenopacket["subject"]["id"]
|
|
141
|
+
logger.warning(f"SAMS: No diseases found for {sams_entry}")
|
|
142
|
+
return ""
|
|
143
|
+
|
|
144
|
+
else:
|
|
145
|
+
diseases = phenopacket["diseases"] # Get disease terms from phenopacket
|
|
146
|
+
|
|
147
|
+
disease_strings = []
|
|
148
|
+
for disease in diseases:
|
|
149
|
+
disease_string = f"{disease['term']['id']} - {disease['term']['label']}"
|
|
150
|
+
|
|
151
|
+
if disease.get("excluded", 0):
|
|
152
|
+
if ignore_excluded:
|
|
153
|
+
continue
|
|
154
|
+
else:
|
|
155
|
+
disease_string += " (excluded)"
|
|
156
|
+
|
|
157
|
+
disease_strings.append(disease_string)
|
|
158
|
+
return "; ".join(disease_strings)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def filter_phenopacket_by_onset(phenopacket: dict, input_onset_timestamp: str) -> dict:
|
|
162
|
+
"""Filter phenopacket by onset timestamp
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
phenopacket (dict): Phenopacket containing phenotypic features
|
|
166
|
+
input_onset_timestamp (str): Onset timestamp to filter by (e.g. "2026-02-12T00:00:00Z")
|
|
167
|
+
If set to "earliest", it will filter by the earliest onset timestamp in the phenopacket,
|
|
168
|
+
If set to "latest", it will filter by the latest onset timestamp in the phenopacket
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
dict: Filtered phenopacket containing only phenotypic features with the given onset timestamp
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
def compute_onset_timestamp(onset: str) -> str:
|
|
175
|
+
if onset == "earliest":
|
|
176
|
+
onset = min(
|
|
177
|
+
feature["onset"]["timestamp"]
|
|
178
|
+
for feature in phenopacket.get("phenotypicFeatures", [])
|
|
179
|
+
)
|
|
180
|
+
elif onset == "latest":
|
|
181
|
+
onset = max(
|
|
182
|
+
feature["onset"]["timestamp"]
|
|
183
|
+
for feature in phenopacket.get("phenotypicFeatures", [])
|
|
184
|
+
)
|
|
185
|
+
return onset
|
|
186
|
+
|
|
187
|
+
onset_timestamp = compute_onset_timestamp(input_onset_timestamp)
|
|
188
|
+
|
|
189
|
+
filered_phenotypes = []
|
|
190
|
+
filtered_diseases = []
|
|
191
|
+
for feature in phenopacket.get("phenotypicFeatures", []):
|
|
192
|
+
if feature["onset"]["timestamp"] == onset_timestamp:
|
|
193
|
+
filered_phenotypes.append(feature)
|
|
194
|
+
for disease in phenopacket.get("diseases", []):
|
|
195
|
+
if disease["onset"]["timestamp"] == onset_timestamp:
|
|
196
|
+
filtered_diseases.append(disease)
|
|
197
|
+
|
|
198
|
+
phenopacket["phenotypicFeatures"] = filered_phenotypes
|
|
199
|
+
phenopacket["diseases"] = filtered_diseases
|
|
200
|
+
return phenopacket
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: simple_sams_api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A simple Python API for interacting with the SAMS system
|
|
5
|
+
Author-email: Oliver Küchler <oliver.kuechler@charite.de>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) [year] [fullname]
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
Project-URL: Homepage, https://github.com/Kuechler/simple_sams_api
|
|
28
|
+
Project-URL: Documentation, https://github.com/KuechlerO/simple_sams_api#readme
|
|
29
|
+
Project-URL: Source, https://github.com/KuechlerO/simple_sams_api
|
|
30
|
+
Project-URL: Tracker, https://github.com/KuechlerO/simple_sams_api/issues
|
|
31
|
+
Keywords: SAMS,API,phenopacket,bioinformatics
|
|
32
|
+
Classifier: Programming Language :: Python :: 3
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Operating System :: OS Independent
|
|
35
|
+
Classifier: Intended Audience :: Science/Research
|
|
36
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
37
|
+
Requires-Python: >=3.7
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
License-File: LICENSE
|
|
40
|
+
Requires-Dist: requests
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
|
|
43
|
+
# Simple SAMS API
|
|
44
|
+
|
|
45
|
+
This module provides a Python interface for interacting with the [SAMS](https://www.genecascade.org/sams-cgi/Patients.cgi) (Symptom Annotation Made Simple) web service, allowing you to authenticate, retrieve phenopacket data, and extract relevant medical terms for downstream analysis.
|
|
46
|
+
|
|
47
|
+
## Features
|
|
48
|
+
- **Authentication**: Login to SAMS using username/password or credentials file.
|
|
49
|
+
- **Phenopacket Retrieval**: Download all phenopackets or a specific phenopacket by patient ID.
|
|
50
|
+
- **HPO Term Extraction**: Extract Human Phenotype Ontology (HPO) terms from phenopacket data.
|
|
51
|
+
- **Disease Term Extraction**: Extract disease terms (OMIM, ORPHANET) from phenopacket data.
|
|
52
|
+
- **Onset Filtering**: Filter phenopacket features and diseases by onset timestamp.
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
### Installation
|
|
57
|
+
Simply copy the module into your project and install the required dependencies:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
pip install requests
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Example
|
|
64
|
+
```python
|
|
65
|
+
from simple_sams_api.base import SAMSapi, extract_HPO_terms_from_phenopacket
|
|
66
|
+
|
|
67
|
+
# Initialize API and login
|
|
68
|
+
api = SAMSapi()
|
|
69
|
+
api.login_with_username('your_email', 'your_password')
|
|
70
|
+
|
|
71
|
+
# Retrieve all phenopackets
|
|
72
|
+
phenopackets = api.get_phenopackets()
|
|
73
|
+
|
|
74
|
+
# Extract HPO terms from a phenopacket
|
|
75
|
+
hpo_terms = extract_HPO_terms_from_phenopacket(phenopackets[0])
|
|
76
|
+
print(hpo_terms)
|
|
77
|
+
# Example output: "HP:0000001 - Phenotype 1; HP:0000002 - Phenotype 2; ..."
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API Reference
|
|
81
|
+
|
|
82
|
+
### Class: `SAMSapi`
|
|
83
|
+
- `login_with_username(username, password)`: Login using username and password.
|
|
84
|
+
- `login_with_credentials_file(credentials_file)`: Login using a credentials file (first line: username, second line: password).
|
|
85
|
+
- `get_phenopackets()`: Retrieve all phenopackets for the current user.
|
|
86
|
+
- `get_phenopacket(patient_id)`: Retrieve a phenopacket for a specific patient.
|
|
87
|
+
- `loggedIn`: Property indicating login status.
|
|
88
|
+
|
|
89
|
+
### Functions
|
|
90
|
+
- `extract_HPO_terms_from_phenopacket(phenopacket, ignore_excluded=True)`: Extract HPO terms from a phenopacket.
|
|
91
|
+
- `extract_disease_terms_from_phenopacket(phenopacket, ignore_excluded=True)`: Extract disease terms from a phenopacket.
|
|
92
|
+
- `filter_phenopacket_by_onset(phenopacket, input_onset_timestamp)`: Filter phenopacket features and diseases by onset timestamp ("earliest", "latest", or specific timestamp).
|
|
93
|
+
|
|
94
|
+
## Testing
|
|
95
|
+
Run `python -m unittest tests/test_base.py`.
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
MIT License
|
|
99
|
+
|
|
100
|
+
## Author
|
|
101
|
+
Oliver Küchler
|
|
102
|
+
|
|
103
|
+
## Notes
|
|
104
|
+
- Make sure you have access to the SAMS web service and valid credentials.
|
|
105
|
+
- For more details, see the docstrings in the source code.
|
|
106
|
+
- The GitHub Actions workflow is based on: [Using uv in GitHub Actions](https://docs.astral.sh/uv/guides/integration/github/#publishing-to-pypi).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
simple_sams_api/__init__.py,sha256=f7KQ4LYaFbzXQUTMMU0Km9hbP2YSZRJVusnoMfa9WUw,152
|
|
2
|
+
simple_sams_api/base.py,sha256=UbI79tSesCRu990wYZwRuXVFGM9qnhzd92cBOoSF9cs,6845
|
|
3
|
+
simple_sams_api-0.1.0.dist-info/licenses/LICENSE,sha256=pAZXnNE2dxxwXFIduGyn1gpvPefJtUYOYZOi3yeGG94,1068
|
|
4
|
+
simple_sams_api-0.1.0.dist-info/METADATA,sha256=xZEesSp_fbld4dp6yZxtzcr0bdmkxqlC4ID3uwJRuWQ,4791
|
|
5
|
+
simple_sams_api-0.1.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
6
|
+
simple_sams_api-0.1.0.dist-info/top_level.txt,sha256=0Kpajvp9FwCfjW-W1lr6oPJox1vCCgc8KWHfi7LALZg,16
|
|
7
|
+
simple_sams_api-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [year] [fullname]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
simple_sams_api
|