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.
@@ -0,0 +1,6 @@
1
+ from .base import (
2
+ SAMSapi,
3
+ extract_HPO_terms_from_phenopacket,
4
+ extract_disease_terms_from_phenopacket,
5
+ filter_phenopacket_by_onset,
6
+ )
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -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