athena-client 1.0.4__tar.gz

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,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: athena-client
3
+ Version: 1.0.4
4
+ Summary: Production-ready Python SDK for the OHDSI Athena Concepts API
5
+ Author: Athena-Client Core Engineering Team
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/username/athena-client
8
+ Project-URL: Documentation, https://athena-client.readthedocs.io
9
+ Project-URL: Issues, https://github.com/username/athena-client/issues
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: orjson>=3.9.0
13
+ Requires-Dist: pydantic>=2.0.0
14
+ Requires-Dist: pydantic-settings
15
+ Provides-Extra: async
16
+ Requires-Dist: httpx>=0.18.0; extra == "async"
17
+ Provides-Extra: pandas
18
+ Requires-Dist: pandas>=1.3.0; extra == "pandas"
19
+ Provides-Extra: cli
20
+ Requires-Dist: click>=8.0.0; extra == "cli"
21
+ Requires-Dist: rich>=10.0.0; extra == "cli"
22
+ Provides-Extra: yaml
23
+ Requires-Dist: pyyaml>=6.0; extra == "yaml"
24
+ Provides-Extra: crypto
25
+ Requires-Dist: cryptography>=36.0.0; extra == "crypto"
26
+ Provides-Extra: all
27
+ Requires-Dist: httpx>=0.18.0; extra == "all"
28
+ Requires-Dist: pandas>=1.3.0; extra == "all"
29
+ Requires-Dist: click>=8.0.0; extra == "all"
30
+ Requires-Dist: rich>=10.0.0; extra == "all"
31
+ Requires-Dist: pyyaml>=6.0; extra == "all"
32
+ Requires-Dist: cryptography>=36.0.0; extra == "all"
33
+ Provides-Extra: dev
34
+ Requires-Dist: ruff>=0.4.0; extra == "dev"
35
+ Requires-Dist: cyclonedx-bom>=3.15.0; extra == "dev"
36
+ Requires-Dist: mypy>=1.10; extra == "dev"
37
+ Requires-Dist: pip-audit>=2.6; extra == "dev"
38
+ Requires-Dist: pytest>=8.2; extra == "dev"
39
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
40
+ Requires-Dist: pytest-asyncio[mode-auto]>=0.23; extra == "dev"
41
+ Requires-Dist: hypothesis>=6.103; extra == "dev"
42
+ Requires-Dist: vcrpy>=6.0; extra == "dev"
43
+ Requires-Dist: rich>=13.7; extra == "dev"
44
+ Requires-Dist: build>=1.2; extra == "dev"
45
+ Requires-Dist: twine>=5.1; extra == "dev"
46
+ Requires-Dist: pytest-benchmark>=4.0; extra == "dev"
47
+ Requires-Dist: bandit>=1.7.5; extra == "dev"
48
+ Requires-Dist: cyclonedx-python-lib>=5.2.0; extra == "dev"
49
+ Requires-Dist: types-requests; extra == "dev"
50
+ Requires-Dist: types-PyYAML; extra == "dev"
51
+ Requires-Dist: pandas-stubs; extra == "dev"
52
+
53
+ # athena-client
54
+ [![SBOM](https://img.shields.io/badge/SBOM-available-blue)](sbom.json)
55
+
56
+ A production-ready Python SDK for the OHDSI Athena Concepts API.
57
+
58
+ ## Installation
59
+
60
+ ```bash
61
+ pip install athena-client
62
+ ```
63
+
64
+ With optional dependencies:
65
+ ```bash
66
+ pip install athena-client[cli] # Command-line interface
67
+ pip install athena-client[async] # Async client
68
+ pip install athena-client[pandas] # DataFrame output support
69
+ pip install athena-client[yaml] # YAML output format
70
+ pip install athena-client[crypto] # HMAC authentication
71
+ pip install athena-client[all] # All optional dependencies
72
+ ```
73
+
74
+ ## Quick Start
75
+
76
+ ```python
77
+ from athena_client import Athena
78
+
79
+ # Create a client with default settings (public Athena server)
80
+ athena = Athena()
81
+
82
+ # Search for concepts
83
+ results = athena.search("aspirin")
84
+
85
+ # Various output formats
86
+ concepts = results.all() # List of Pydantic models
87
+ top_three = results.top(3) # First three results
88
+ as_dict = results.to_list() # List of dictionaries
89
+ as_json = results.to_json() # JSON string
90
+ as_df = results.to_df() # pandas DataFrame
91
+
92
+ # Get details for a specific concept
93
+ details = athena.details(concept_id=1127433)
94
+
95
+ # Get relationships
96
+ rels = athena.relationships(concept_id=1127433)
97
+
98
+ # Get graph
99
+ graph = athena.graph(concept_id=1127433, depth=5)
100
+
101
+ # Get comprehensive summary
102
+ summary = athena.summary(concept_id=1127433)
103
+ ```
104
+
105
+ ## CLI Usage
106
+
107
+ ```bash
108
+ # Install CLI dependencies
109
+ pip install "athena-client[cli]"
110
+
111
+ # Search for concepts
112
+ athena search "aspirin"
113
+
114
+ # Get details for a specific concept
115
+ athena details 1127433
116
+
117
+ # Get a summary with various output formats
118
+ athena summary 1127433 --output yaml
119
+ ```
120
+
121
+ ## Configuration
122
+
123
+ The client can be configured through:
124
+ 1. Constructor arguments
125
+ 2. Environment variables
126
+ 3. A `.env` file
127
+ 4. Default values
128
+
129
+ ```python
130
+ # Explicit configuration
131
+ athena = Athena(
132
+ base_url="https://custom.athena.server/api/v1",
133
+ token="your-bearer-token",
134
+ timeout=15,
135
+ max_retries=5
136
+ )
137
+ ```
138
+
139
+ Or use environment variables:
140
+ ```
141
+ ATHENA_BASE_URL=https://custom.athena.server/api/v1
142
+ ATHENA_TOKEN=your-bearer-token
143
+ ATHENA_TIMEOUT_SECONDS=15
144
+ ATHENA_MAX_RETRIES=5
145
+ ```
146
+
147
+ ## Advanced Query DSL
148
+
149
+ For complex queries, use the Query DSL:
150
+
151
+ ```python
152
+ from athena_client.query import Q
153
+
154
+ # Build complex queries
155
+ q = (Q.term("diabetes") & Q.term("type 2")) | Q.exact('"diabetic nephropathy"')
156
+
157
+ # Use with search
158
+ results = athena.search(q)
159
+ ```
160
+
161
+ ### Property-Based Tests
162
+
163
+ We use **Hypothesis** for edge-case discovery. New core utilities or parsers **must** include at least one Hypothesis scenario.
164
+
165
+ ## Modern Installation & Packaging
166
+
167
+ This project uses the modern Python packaging standard with `pyproject.toml` for build and dependency management. You do not need to use `setup.py` for installation or development. Instead, use the following commands:
168
+
169
+ ### Install with pip (recommended)
170
+
171
+ ```bash
172
+ pip install .
173
+ ```
174
+
175
+ Or, for development (editable install with dev dependencies):
176
+
177
+ > **Note:** For editable installs with extras, make sure you have recent versions of pip and setuptools:
178
+ > ```bash
179
+ > pip install --upgrade pip setuptools
180
+ > ```
181
+ ```bash
182
+ pip install -e '.[dev]'
183
+ ```
184
+
185
+ ### Why `pyproject.toml`?
186
+ - All build, dependency, and metadata configuration is in `pyproject.toml`.
187
+ - Compatible with modern Python tooling (pip, build, poetry, etc).
188
+ - `setup.py` is only needed for legacy or advanced customizations.
189
+
190
+ For more details, see [Packaging Python Projects](https://packaging.python.org/en/latest/tutorials/packaging-projects/).
191
+
192
+ ## Documentation
193
+
194
+ For complete documentation, visit: [https://athena-client.readthedocs.io](https://athena-client.readthedocs.io)
195
+
196
+ ## License
197
+
198
+ MIT
@@ -0,0 +1,146 @@
1
+ # athena-client
2
+ [![SBOM](https://img.shields.io/badge/SBOM-available-blue)](sbom.json)
3
+
4
+ A production-ready Python SDK for the OHDSI Athena Concepts API.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ pip install athena-client
10
+ ```
11
+
12
+ With optional dependencies:
13
+ ```bash
14
+ pip install athena-client[cli] # Command-line interface
15
+ pip install athena-client[async] # Async client
16
+ pip install athena-client[pandas] # DataFrame output support
17
+ pip install athena-client[yaml] # YAML output format
18
+ pip install athena-client[crypto] # HMAC authentication
19
+ pip install athena-client[all] # All optional dependencies
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ```python
25
+ from athena_client import Athena
26
+
27
+ # Create a client with default settings (public Athena server)
28
+ athena = Athena()
29
+
30
+ # Search for concepts
31
+ results = athena.search("aspirin")
32
+
33
+ # Various output formats
34
+ concepts = results.all() # List of Pydantic models
35
+ top_three = results.top(3) # First three results
36
+ as_dict = results.to_list() # List of dictionaries
37
+ as_json = results.to_json() # JSON string
38
+ as_df = results.to_df() # pandas DataFrame
39
+
40
+ # Get details for a specific concept
41
+ details = athena.details(concept_id=1127433)
42
+
43
+ # Get relationships
44
+ rels = athena.relationships(concept_id=1127433)
45
+
46
+ # Get graph
47
+ graph = athena.graph(concept_id=1127433, depth=5)
48
+
49
+ # Get comprehensive summary
50
+ summary = athena.summary(concept_id=1127433)
51
+ ```
52
+
53
+ ## CLI Usage
54
+
55
+ ```bash
56
+ # Install CLI dependencies
57
+ pip install "athena-client[cli]"
58
+
59
+ # Search for concepts
60
+ athena search "aspirin"
61
+
62
+ # Get details for a specific concept
63
+ athena details 1127433
64
+
65
+ # Get a summary with various output formats
66
+ athena summary 1127433 --output yaml
67
+ ```
68
+
69
+ ## Configuration
70
+
71
+ The client can be configured through:
72
+ 1. Constructor arguments
73
+ 2. Environment variables
74
+ 3. A `.env` file
75
+ 4. Default values
76
+
77
+ ```python
78
+ # Explicit configuration
79
+ athena = Athena(
80
+ base_url="https://custom.athena.server/api/v1",
81
+ token="your-bearer-token",
82
+ timeout=15,
83
+ max_retries=5
84
+ )
85
+ ```
86
+
87
+ Or use environment variables:
88
+ ```
89
+ ATHENA_BASE_URL=https://custom.athena.server/api/v1
90
+ ATHENA_TOKEN=your-bearer-token
91
+ ATHENA_TIMEOUT_SECONDS=15
92
+ ATHENA_MAX_RETRIES=5
93
+ ```
94
+
95
+ ## Advanced Query DSL
96
+
97
+ For complex queries, use the Query DSL:
98
+
99
+ ```python
100
+ from athena_client.query import Q
101
+
102
+ # Build complex queries
103
+ q = (Q.term("diabetes") & Q.term("type 2")) | Q.exact('"diabetic nephropathy"')
104
+
105
+ # Use with search
106
+ results = athena.search(q)
107
+ ```
108
+
109
+ ### Property-Based Tests
110
+
111
+ We use **Hypothesis** for edge-case discovery. New core utilities or parsers **must** include at least one Hypothesis scenario.
112
+
113
+ ## Modern Installation & Packaging
114
+
115
+ This project uses the modern Python packaging standard with `pyproject.toml` for build and dependency management. You do not need to use `setup.py` for installation or development. Instead, use the following commands:
116
+
117
+ ### Install with pip (recommended)
118
+
119
+ ```bash
120
+ pip install .
121
+ ```
122
+
123
+ Or, for development (editable install with dev dependencies):
124
+
125
+ > **Note:** For editable installs with extras, make sure you have recent versions of pip and setuptools:
126
+ > ```bash
127
+ > pip install --upgrade pip setuptools
128
+ > ```
129
+ ```bash
130
+ pip install -e '.[dev]'
131
+ ```
132
+
133
+ ### Why `pyproject.toml`?
134
+ - All build, dependency, and metadata configuration is in `pyproject.toml`.
135
+ - Compatible with modern Python tooling (pip, build, poetry, etc).
136
+ - `setup.py` is only needed for legacy or advanced customizations.
137
+
138
+ For more details, see [Packaging Python Projects](https://packaging.python.org/en/latest/tutorials/packaging-projects/).
139
+
140
+ ## Documentation
141
+
142
+ For complete documentation, visit: [https://athena-client.readthedocs.io](https://athena-client.readthedocs.io)
143
+
144
+ ## License
145
+
146
+ MIT
@@ -0,0 +1,233 @@
1
+ """
2
+ athena-client: Production-ready Python SDK for the OHDSI Athena Concepts API
3
+ """
4
+
5
+ from typing import Any, Dict, Optional
6
+
7
+ from .models import ConceptDetails, ConceptRelationsGraph, ConceptRelationship
8
+
9
+ __version__ = "1.0.4"
10
+
11
+
12
+ class Athena:
13
+ """
14
+ Main facade for the Athena API client.
15
+
16
+ This class provides a simplified interface to the Athena API with six
17
+ intuitive verbs that cover 95% of day-to-day use cases.
18
+ """
19
+
20
+ def __init__(
21
+ self,
22
+ base_url: Optional[str] = None,
23
+ token: Optional[str] = None,
24
+ client_id: Optional[str] = None,
25
+ private_key: Optional[str] = None,
26
+ timeout: Optional[int] = None,
27
+ max_retries: Optional[int] = None,
28
+ backoff_factor: Optional[float] = None,
29
+ ) -> None:
30
+ """
31
+ Initialize the Athena facade with optional configuration.
32
+
33
+ Args:
34
+ base_url: The base URL for the Athena API.
35
+ token: Bearer token for authentication.
36
+ client_id: Client ID for HMAC authentication.
37
+ private_key: Private key for HMAC signing.
38
+ timeout: HTTP timeout in seconds.
39
+ max_retries: Maximum number of retry attempts.
40
+ backoff_factor: Exponential backoff factor for retries.
41
+ """
42
+ # Import here to avoid circular imports
43
+ from .client import AthenaClient # pylint: disable=import-outside-toplevel
44
+
45
+ self._client = AthenaClient(
46
+ base_url=base_url,
47
+ token=token,
48
+ client_id=client_id,
49
+ private_key=private_key,
50
+ timeout=timeout,
51
+ max_retries=max_retries,
52
+ backoff_factor=backoff_factor,
53
+ )
54
+
55
+ def search(
56
+ self,
57
+ query: Any, # Can be str or Q object
58
+ *,
59
+ exact: Optional[str] = None,
60
+ fuzzy: bool = False,
61
+ wildcard: Optional[str] = None,
62
+ boosts: Optional[Dict[str, Any]] = None,
63
+ debug: bool = False,
64
+ page_size: int = 20,
65
+ page: int = 0,
66
+ domain: Optional[str] = None,
67
+ vocabulary: Optional[str] = None,
68
+ standard_concept: Optional[str] = None,
69
+ ) -> "SearchResult":
70
+ """
71
+ Search for concepts in the Athena vocabulary.
72
+
73
+ Args:
74
+ query: The search query string
75
+ exact: Exact match phrase
76
+ fuzzy: Whether to enable fuzzy matching
77
+ wildcard: Wildcard pattern
78
+ boosts: Dictionary of field boosts
79
+ debug: Enable debug mode
80
+ page_size: Number of results per page
81
+ page: Page number (0-indexed)
82
+ domain: Filter by domain
83
+ vocabulary: Filter by vocabulary
84
+ standard_concept: Filter by standard concept status
85
+
86
+ Returns:
87
+ SearchResult object containing the search results
88
+ """
89
+ from .search_result import SearchResult
90
+
91
+ # Handle Q object if provided
92
+ query_str = query
93
+ if hasattr(query, "to_boosts") and callable(query.to_boosts):
94
+ boosts = query.to_boosts()
95
+ query_str = ""
96
+
97
+ data = self._client.search_concepts(
98
+ query=query_str,
99
+ exact=exact,
100
+ fuzzy=fuzzy,
101
+ wildcard=wildcard,
102
+ boosts=boosts,
103
+ debug=debug,
104
+ page_size=page_size,
105
+ page=page,
106
+ domain=domain,
107
+ vocabulary=vocabulary,
108
+ standard_concept=standard_concept,
109
+ )
110
+
111
+ return SearchResult(data)
112
+
113
+ def details(self, concept_id: int) -> ConceptDetails:
114
+ """
115
+ Get detailed information for a specific concept.
116
+
117
+ Args:
118
+ concept_id: The concept ID to get details for
119
+
120
+ Returns:
121
+ ConceptDetails object
122
+ """
123
+ return self._client.get_concept_details(concept_id)
124
+
125
+ def relationships(
126
+ self,
127
+ concept_id: int,
128
+ *,
129
+ relationship_id: Optional[str] = None,
130
+ only_standard: bool = False,
131
+ ) -> ConceptRelationship:
132
+ """
133
+ Get relationships for a specific concept.
134
+
135
+ Args:
136
+ concept_id: The concept ID to get relationships for
137
+ relationship_id: Filter by relationship type
138
+ only_standard: Only include standard concepts
139
+
140
+ Returns:
141
+ ConceptRelationship object
142
+ """
143
+ return self._client.get_concept_relationships(
144
+ concept_id=concept_id,
145
+ relationship_id=relationship_id,
146
+ only_standard=only_standard,
147
+ )
148
+
149
+ def graph(
150
+ self,
151
+ concept_id: int,
152
+ *,
153
+ depth: int = 10,
154
+ zoom_level: int = 4,
155
+ ) -> ConceptRelationsGraph:
156
+ """
157
+ Get relationship graph for a specific concept.
158
+
159
+ Args:
160
+ concept_id: The concept ID to get graph for
161
+ depth: Maximum depth of relationships to traverse
162
+ zoom_level: Zoom level for the graph
163
+
164
+ Returns:
165
+ ConceptRelationsGraph object
166
+ """
167
+ return self._client.get_concept_graph(
168
+ concept_id=concept_id,
169
+ depth=depth,
170
+ zoom_level=zoom_level,
171
+ )
172
+
173
+ def summary(self, concept_id: int) -> Dict[str, Any]:
174
+ """
175
+ Get a comprehensive summary for a concept.
176
+
177
+ This aggregates details, relationships, and graph information.
178
+
179
+ Args:
180
+ concept_id: The concept ID to summarize
181
+
182
+ Returns:
183
+ Dictionary containing details, relationships, and graph
184
+ """
185
+ details = self.details(concept_id)
186
+ relationships = self.relationships(concept_id)
187
+ graph = self.graph(concept_id)
188
+
189
+ return {
190
+ "details": details,
191
+ "relationships": relationships,
192
+ "graph": graph,
193
+ }
194
+
195
+ @staticmethod
196
+ def capabilities() -> Dict[str, Dict[str, Any]]:
197
+ """
198
+ Get machine-readable manifest of all supported verbs.
199
+
200
+ Returns:
201
+ Dictionary containing capabilities information
202
+ """
203
+ return {
204
+ "search": {
205
+ "endpoint": "/concepts",
206
+ "auth": "anonymous|bearer",
207
+ "outputs": ["models", "list", "dataframe", "json", "yaml", "csv"],
208
+ },
209
+ "details": {
210
+ "endpoint": "/concepts/{id}",
211
+ "auth": "anonymous|bearer",
212
+ "outputs": ["model", "json"],
213
+ },
214
+ "relationships": {
215
+ "endpoint": "/concepts/{id}/relationships",
216
+ "auth": "anonymous|bearer",
217
+ "outputs": ["model", "json"],
218
+ },
219
+ "graph": {
220
+ "endpoint": "/concepts/{id}/relations",
221
+ "auth": "anonymous|bearer",
222
+ "outputs": ["model", "json"],
223
+ },
224
+ "summary": {
225
+ "composed_of": ["details", "relationships", "graph"],
226
+ "outputs": ["dict"],
227
+ },
228
+ }
229
+
230
+
231
+ # Type hint for return annotation - needed at the end to avoid circular imports
232
+ # This import is used for type hints only, which is why it's placed at the bottom
233
+ from .search_result import SearchResult # noqa: E402