olira 1.0.0__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.
- olira-1.0.0/.devcontainer/Dockerfile +14 -0
- olira-1.0.0/.devcontainer/README.md +19 -0
- olira-1.0.0/.devcontainer/devcontainer.json +74 -0
- olira-1.0.0/.github/workflows/ci.yml +36 -0
- olira-1.0.0/.github/workflows/publish.yml +53 -0
- olira-1.0.0/CHANGELOG.md +26 -0
- olira-1.0.0/LICENSE +17 -0
- olira-1.0.0/PKG-INFO +218 -0
- olira-1.0.0/README.md +191 -0
- olira-1.0.0/SDK_DOCUMENTATION.md +1933 -0
- olira-1.0.0/examples/.env.example +14 -0
- olira-1.0.0/examples/00_quickstart.py +61 -0
- olira-1.0.0/examples/01_patient_management.py +101 -0
- olira-1.0.0/examples/02_event_logging.py +156 -0
- olira-1.0.0/examples/03_historical_ingestion.py +172 -0
- olira-1.0.0/examples/04_logs_only_workflow.py +154 -0
- olira-1.0.0/examples/05_read_patient_state.py +113 -0
- olira-1.0.0/examples/README.md +28 -0
- olira-1.0.0/examples/pyproject.toml +10 -0
- olira-1.0.0/pyproject.toml +73 -0
- olira-1.0.0/scripts/check-version.sh +98 -0
- olira-1.0.0/scripts/install-dev.sh +20 -0
- olira-1.0.0/scripts/lint.sh +16 -0
- olira-1.0.0/scripts/pre-pr.sh +34 -0
- olira-1.0.0/scripts/test.sh +10 -0
- olira-1.0.0/scripts/uv.sh +6 -0
- olira-1.0.0/src/olira/__init__.py +535 -0
- olira-1.0.0/src/olira/client.py +1118 -0
- olira-1.0.0/src/olira/exceptions.py +33 -0
- olira-1.0.0/src/olira/http.py +557 -0
- olira-1.0.0/src/olira/models.py +629 -0
- olira-1.0.0/src/olira/py.typed +0 -0
- olira-1.0.0/src/olira/queue.py +142 -0
- olira-1.0.0/src/olira/validation.py +365 -0
- olira-1.0.0/src/olira/version.py +3 -0
- olira-1.0.0/tests/__init__.py +1 -0
- olira-1.0.0/tests/conftest.py +14 -0
- olira-1.0.0/tests/test_async_client.py +131 -0
- olira-1.0.0/tests/test_client.py +82 -0
- olira-1.0.0/tests/test_event_recorders.py +234 -0
- olira-1.0.0/tests/test_privacy.py +42 -0
- olira-1.0.0/tests/test_retry.py +52 -0
- olira-1.0.0/uv.lock +644 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.0/containers/python-3
|
|
2
|
+
ARG VARIANT=3.11-bookworm
|
|
3
|
+
FROM mcr.microsoft.com/devcontainers/python:${VARIANT}
|
|
4
|
+
|
|
5
|
+
# Install uv - fast Python package installer
|
|
6
|
+
RUN curl -LsSf https://astral.sh/uv/install.sh | sh && \
|
|
7
|
+
mkdir -p /usr/local/bin && \
|
|
8
|
+
if [ -f /root/.cargo/bin/uv ]; then \
|
|
9
|
+
mv /root/.cargo/bin/uv /usr/local/bin/uv; \
|
|
10
|
+
elif [ -f /root/.local/bin/uv ]; then \
|
|
11
|
+
mv /root/.local/bin/uv /usr/local/bin/uv; \
|
|
12
|
+
fi && \
|
|
13
|
+
chmod +x /usr/local/bin/uv && \
|
|
14
|
+
uv --version
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Development container
|
|
2
|
+
|
|
3
|
+
VS Code dev container for the Olira Python SDK.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
1. Open this repository in VS Code.
|
|
8
|
+
2. Install the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension.
|
|
9
|
+
3. **Reopen in Container** (Command Palette → Dev Containers: Reopen in Container).
|
|
10
|
+
4. After the container builds: `bash scripts/install-dev.sh` (PyPI only)
|
|
11
|
+
|
|
12
|
+
API reference: [https://olira.ai/api-docs](https://olira.ai/api-docs) (Python SDK tab).
|
|
13
|
+
|
|
14
|
+
## Commands
|
|
15
|
+
|
|
16
|
+
- Pre-PR: `./scripts/pre-pr.sh`
|
|
17
|
+
- Lint: `./scripts/lint.sh`
|
|
18
|
+
- Tests: `./scripts/test.sh`
|
|
19
|
+
- Version check: `./scripts/check-version.sh`
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Olira Python SDK",
|
|
3
|
+
"build": {
|
|
4
|
+
"dockerfile": "Dockerfile",
|
|
5
|
+
"context": "..",
|
|
6
|
+
"args": {
|
|
7
|
+
"VARIANT": "3.11-bookworm"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"customizations": {
|
|
11
|
+
"vscode": {
|
|
12
|
+
"settings": {
|
|
13
|
+
"python.defaultInterpreterPath": "/home/vscode/.venv/bin/python",
|
|
14
|
+
"python.linting.enabled": true,
|
|
15
|
+
"python.formatting.ruffPath": "/usr/local/py-utils/bin/ruff",
|
|
16
|
+
"python.linting.ruffPath": "/usr/local/py-utils/bin/ruff",
|
|
17
|
+
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
|
|
18
|
+
"files.watcherExclude": {
|
|
19
|
+
"**/__pycache__/**": true,
|
|
20
|
+
"**/.mypy_cache/**": true,
|
|
21
|
+
"**/.pytest_cache/**": true,
|
|
22
|
+
"**/.ruff_cache/**": true,
|
|
23
|
+
"**/.venv/**": true,
|
|
24
|
+
"**/venv/**": true
|
|
25
|
+
},
|
|
26
|
+
"files.watcherInclude": [
|
|
27
|
+
"**/*.py",
|
|
28
|
+
"**/*.yaml",
|
|
29
|
+
"**/*.yml",
|
|
30
|
+
"**/*.json",
|
|
31
|
+
"**/*.toml"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"extensions": [
|
|
35
|
+
"ms-python.python",
|
|
36
|
+
"ms-python.vscode-pylance",
|
|
37
|
+
"charliermarsh.ruff",
|
|
38
|
+
"eamodio.gitlens",
|
|
39
|
+
"ms-python.mypy-type-checker"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"remoteUser": "vscode",
|
|
44
|
+
"mounts": [
|
|
45
|
+
"source=${localEnv:HOME}/.ssh/,target=/home/vscode/.ssh/,type=bind,consistency=cached",
|
|
46
|
+
"source=olira-sdk-venv,target=/home/vscode/.venv,type=volume",
|
|
47
|
+
"source=olira-sdk-pycache,target=/tmp/pycache,type=volume",
|
|
48
|
+
"source=olira-sdk-mypy-cache,target=/tmp/.mypy_cache,type=volume",
|
|
49
|
+
"source=olira-sdk-pytest-cache,target=/tmp/.pytest_cache,type=volume",
|
|
50
|
+
"source=olira-sdk-ruff-cache,target=/tmp/.ruff_cache,type=volume",
|
|
51
|
+
"source=olira-sdk-uv-cache,target=/root/.cache/uv,type=volume"
|
|
52
|
+
],
|
|
53
|
+
"containerEnv": {
|
|
54
|
+
"PYTHONPATH": "${workspaceFolder}/src",
|
|
55
|
+
"UV_PROJECT_ENVIRONMENT": "/home/vscode/.venv",
|
|
56
|
+
"PYTHONPYCACHEPREFIX": "/tmp/pycache",
|
|
57
|
+
"MYPY_CACHE_DIR": "/tmp/.mypy_cache",
|
|
58
|
+
"PYTEST_CACHE_DIR": "/tmp/.pytest_cache",
|
|
59
|
+
"RUFF_CACHE_DIR": "/tmp/.ruff_cache"
|
|
60
|
+
},
|
|
61
|
+
"features": {
|
|
62
|
+
"git": "latest"
|
|
63
|
+
},
|
|
64
|
+
"postCreateCommand": "bash -c 'sudo chown -R vscode:vscode /home/vscode/.venv /tmp/pycache /tmp/.mypy_cache /tmp/.pytest_cache /tmp/.ruff_cache 2>/dev/null || true; cd ${containerWorkspaceFolder} && bash scripts/install-dev.sh'",
|
|
65
|
+
"hostRequirements": {
|
|
66
|
+
"cpus": 2,
|
|
67
|
+
"memory": "4gb",
|
|
68
|
+
"storage": "8gb"
|
|
69
|
+
},
|
|
70
|
+
"runArgs": [
|
|
71
|
+
"--cpus=2",
|
|
72
|
+
"--memory=4g"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
ci:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout repository
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
with:
|
|
18
|
+
fetch-depth: 0
|
|
19
|
+
|
|
20
|
+
- name: Set up Python
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.11"
|
|
24
|
+
|
|
25
|
+
- name: Install uv
|
|
26
|
+
uses: astral-sh/setup-uv@v4
|
|
27
|
+
with:
|
|
28
|
+
version: "latest"
|
|
29
|
+
enable-cache: true
|
|
30
|
+
|
|
31
|
+
- name: Pre-PR validation
|
|
32
|
+
env:
|
|
33
|
+
CI: "true"
|
|
34
|
+
GITHUB_BASE_REF: ${{ github.base_ref }}
|
|
35
|
+
UV_INDEX_URL: https://pypi.org/simple/
|
|
36
|
+
run: ./scripts/pre-pr.sh
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
id-token: write
|
|
10
|
+
contents: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
publish:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
environment: pypi
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout repository
|
|
19
|
+
uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.11"
|
|
25
|
+
|
|
26
|
+
- name: Install uv
|
|
27
|
+
uses: astral-sh/setup-uv@v4
|
|
28
|
+
with:
|
|
29
|
+
version: "latest"
|
|
30
|
+
|
|
31
|
+
- name: Read version
|
|
32
|
+
id: version
|
|
33
|
+
run: echo "version=$(grep -E '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/')" >> "$GITHUB_OUTPUT"
|
|
34
|
+
|
|
35
|
+
- name: Reject pre-release versions
|
|
36
|
+
run: |
|
|
37
|
+
VERSION="${{ steps.version.outputs.version }}"
|
|
38
|
+
if [[ "$VERSION" =~ -(alpha|beta|rc) ]] || [[ "$VERSION" == *a* ]] || [[ "$VERSION" == *b[0-9] ]]; then
|
|
39
|
+
echo "::error::Refusing to publish pre-release version ($VERSION) to PyPI."
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
echo "Publishing stable version $VERSION to PyPI."
|
|
43
|
+
|
|
44
|
+
- name: Build package
|
|
45
|
+
env:
|
|
46
|
+
UV_INDEX_URL: https://pypi.org/simple/
|
|
47
|
+
run: uv build
|
|
48
|
+
|
|
49
|
+
- name: Publish to PyPI
|
|
50
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
51
|
+
with:
|
|
52
|
+
packages-dir: dist/
|
|
53
|
+
print-hash: true
|
olira-1.0.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-05-20
|
|
9
|
+
|
|
10
|
+
First public release of the Olira Python SDK (`pip install olira`).
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Event logging** (`sdk:event-log`): `OliraClient.log()`, `log_batch()`, background queue with `flush()`, and module-level `olira.init()` / `olira.log()` / `olira.flush()`.
|
|
15
|
+
- **Patient management** (`api:manage-patients`): create, read, update, delete, list, and batch-create patients; `ExternalIdentifier` for linking to EMR or partner IDs.
|
|
16
|
+
- **Patient token** (`sdk:patient-token`): `get_patient_token()` for short-lived JWTs used with the [Olira MCP Patient State server](https://olira.ai/api-docs).
|
|
17
|
+
- **Patient state read** (`sdk:state-read`): stable modules, event state modules, views, logs, events, and memories — REST-backed access to compiled patient state from Python.
|
|
18
|
+
- **Historical data ingestion** (`sdk:historical-ingest`): JSONL file or inline record upload, two-phase confirm flow, job polling, and local pre-flight validation (`validate_ingestion_file`, `validate_ingestion_records`).
|
|
19
|
+
- **Async client**: `AsyncOliraClient` with the same surface as `OliraClient`.
|
|
20
|
+
- **Typed models**: `OliraLogType`, payload helpers (`EsasItem`, `LabResultItem`, …), and structured error types (`AuthError`, `ValidationError`, `RateLimitError`, `ServerError`).
|
|
21
|
+
- **Examples**: runnable scripts under `examples/` for quickstart, patients, logging, ingestion, and state read.
|
|
22
|
+
|
|
23
|
+
### Documentation
|
|
24
|
+
|
|
25
|
+
- API reference: [https://olira.ai/api-docs](https://olira.ai/api-docs) (Python SDK tab)
|
|
26
|
+
- Local reference: `SDK_DOCUMENTATION.md`
|
olira-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 Olira AI
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
olira-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: olira
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Olira Python SDK — event ingestion client for the Olira platform
|
|
5
|
+
Project-URL: Documentation, https://olira.ai/api-docs
|
|
6
|
+
Author-email: Olira AI <dev@olira.ai>
|
|
7
|
+
License: Apache-2.0
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: events,health,ingestion,olira,sdk
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Requires-Dist: httpx>=0.27
|
|
19
|
+
Requires-Dist: pydantic>=2.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# Olira Python SDK
|
|
29
|
+
|
|
30
|
+
Log ingestion, patient management, and patient token client for the Olira platform.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install olira
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Documentation
|
|
39
|
+
|
|
40
|
+
Full API reference: [https://olira.ai/api-docs](https://olira.ai/api-docs) (Python SDK tab).
|
|
41
|
+
|
|
42
|
+
Local copy: [SDK_DOCUMENTATION.md](SDK_DOCUMENTATION.md).
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Authentication
|
|
47
|
+
|
|
48
|
+
All SDK methods authenticate with an **Olira API key** (`olira_prod_...`). Create keys from the Olira Console under **Settings → API Keys**, selecting the scopes you need:
|
|
49
|
+
|
|
50
|
+
| Scope | What it unlocks |
|
|
51
|
+
| --------------------- | ------------------------------------- |
|
|
52
|
+
| `sdk:event-log` | Log events |
|
|
53
|
+
| `api:manage-patients` | Create, read, update, delete patients |
|
|
54
|
+
| `sdk:patient-token` | Mint short-lived patient-scoped JWTs |
|
|
55
|
+
|
|
56
|
+
See [API key scopes](https://olira.ai/api-docs) for the full list.
|
|
57
|
+
|
|
58
|
+
Pass the key to `OliraClient` or to `olira.init()`:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import olira
|
|
62
|
+
olira.init(api_key="olira_prod_...") # or set OLIRA_API_KEY env var
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Patient Management
|
|
68
|
+
|
|
69
|
+
Patients must exist before you can log events against them. Use the `api:manage-patients` scope.
|
|
70
|
+
|
|
71
|
+
Olira assigns a stable `id` to each patient at creation time. The `id` returned in the `Patient` object is what you use in all subsequent calls.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from olira import OliraClient
|
|
75
|
+
|
|
76
|
+
client = OliraClient(api_key="olira_prod_...")
|
|
77
|
+
|
|
78
|
+
# Create — Olira assigns the id; store it for future calls
|
|
79
|
+
patient = client.create_patient(
|
|
80
|
+
first_name="Jane",
|
|
81
|
+
last_name="Smith",
|
|
82
|
+
timezone="America/New_York",
|
|
83
|
+
primary_disease_site="breast",
|
|
84
|
+
disease_stage="II",
|
|
85
|
+
)
|
|
86
|
+
patient_id = patient.id
|
|
87
|
+
|
|
88
|
+
# Get
|
|
89
|
+
patient = client.get_patient(patient_id=patient_id)
|
|
90
|
+
|
|
91
|
+
# List (paginated, returns PatientListResult)
|
|
92
|
+
result = client.list_patients(limit=50, offset=0)
|
|
93
|
+
for p in result.patients:
|
|
94
|
+
print(p.id, p.first_name, p.last_name)
|
|
95
|
+
|
|
96
|
+
# Update (only supplied fields are changed)
|
|
97
|
+
patient = client.update_patient(patient_id=patient_id, disease_stage="III")
|
|
98
|
+
|
|
99
|
+
# Soft-delete
|
|
100
|
+
client.delete_patient(patient_id=patient_id)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Event Logging
|
|
106
|
+
|
|
107
|
+
Log a single event in the background (fire-and-forget):
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
import olira
|
|
111
|
+
from olira import OliraLogType
|
|
112
|
+
|
|
113
|
+
olira.init(api_key="olira_prod_...")
|
|
114
|
+
|
|
115
|
+
olira.log(
|
|
116
|
+
log_type=OliraLogType.USER_LOGIN,
|
|
117
|
+
patient_id=patient_id, # id from patient.id
|
|
118
|
+
)
|
|
119
|
+
olira.flush() # block until delivery
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Send a batch directly and get back a result:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from olira import OliraClient, LogSpec, OliraLogType, EsasItem
|
|
126
|
+
|
|
127
|
+
client = OliraClient(api_key="olira_prod_...")
|
|
128
|
+
result = client.log_batch([
|
|
129
|
+
LogSpec(log_type=OliraLogType.USER_LOGIN, patient_id=patient_id),
|
|
130
|
+
LogSpec(
|
|
131
|
+
log_type=OliraLogType.SYMPTOM_REPORT,
|
|
132
|
+
patient_id=patient_id,
|
|
133
|
+
payload={
|
|
134
|
+
"instrument": "esas_r",
|
|
135
|
+
"symptoms": [EsasItem(name="pain", score=4).model_dump()],
|
|
136
|
+
},
|
|
137
|
+
),
|
|
138
|
+
])
|
|
139
|
+
print(f"accepted={result.accepted}, failed={result.failed}")
|
|
140
|
+
client.close()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Patient Token
|
|
146
|
+
|
|
147
|
+
Mint a short-lived JWT scoped to a single patient. Requires the `sdk:patient-token` scope.
|
|
148
|
+
|
|
149
|
+
Use this when a patient device needs to communicate with the [Olira MCP Patient State server](https://olira.ai/api-docs) — pass the token as a Bearer header. The token expires after 15 minutes and is locked to the specified patient.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from olira import OliraClient
|
|
153
|
+
|
|
154
|
+
client = OliraClient(api_key="olira_prod_...")
|
|
155
|
+
token = client.get_patient_token(patient_id=patient_id)
|
|
156
|
+
|
|
157
|
+
print(token.access_token) # forward this to the patient device
|
|
158
|
+
print(token.expires_in) # 900 (seconds)
|
|
159
|
+
client.close()
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Async client
|
|
165
|
+
|
|
166
|
+
All methods are available on `AsyncOliraClient` as coroutines:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
import asyncio
|
|
170
|
+
from olira import AsyncOliraClient, OliraLogType
|
|
171
|
+
|
|
172
|
+
async def main():
|
|
173
|
+
async with AsyncOliraClient(api_key="olira_prod_...") as client:
|
|
174
|
+
patient = await client.create_patient(
|
|
175
|
+
first_name="Jane",
|
|
176
|
+
last_name="Smith",
|
|
177
|
+
)
|
|
178
|
+
await client.log(
|
|
179
|
+
log_type=OliraLogType.USER_LOGIN,
|
|
180
|
+
patient_id=patient.id,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
asyncio.run(main())
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Error handling
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from olira import AuthError, ValidationError, RateLimitError, ServerError
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
client.log_batch([...])
|
|
195
|
+
except AuthError:
|
|
196
|
+
# Invalid or revoked API key, or missing scope
|
|
197
|
+
...
|
|
198
|
+
except ValidationError:
|
|
199
|
+
# Bad request (400/404/422) — e.g. unknown event type or missing required field
|
|
200
|
+
...
|
|
201
|
+
except RateLimitError as e:
|
|
202
|
+
# Retry after e.retry_after seconds
|
|
203
|
+
...
|
|
204
|
+
except ServerError:
|
|
205
|
+
# Transient server error after all retries exhausted
|
|
206
|
+
...
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Contributing
|
|
212
|
+
|
|
213
|
+
Dependencies are public PyPI packages only:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
bash scripts/install-dev.sh
|
|
217
|
+
./scripts/pre-pr.sh
|
|
218
|
+
```
|
olira-1.0.0/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# Olira Python SDK
|
|
2
|
+
|
|
3
|
+
Log ingestion, patient management, and patient token client for the Olira platform.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install olira
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Documentation
|
|
12
|
+
|
|
13
|
+
Full API reference: [https://olira.ai/api-docs](https://olira.ai/api-docs) (Python SDK tab).
|
|
14
|
+
|
|
15
|
+
Local copy: [SDK_DOCUMENTATION.md](SDK_DOCUMENTATION.md).
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Authentication
|
|
20
|
+
|
|
21
|
+
All SDK methods authenticate with an **Olira API key** (`olira_prod_...`). Create keys from the Olira Console under **Settings → API Keys**, selecting the scopes you need:
|
|
22
|
+
|
|
23
|
+
| Scope | What it unlocks |
|
|
24
|
+
| --------------------- | ------------------------------------- |
|
|
25
|
+
| `sdk:event-log` | Log events |
|
|
26
|
+
| `api:manage-patients` | Create, read, update, delete patients |
|
|
27
|
+
| `sdk:patient-token` | Mint short-lived patient-scoped JWTs |
|
|
28
|
+
|
|
29
|
+
See [API key scopes](https://olira.ai/api-docs) for the full list.
|
|
30
|
+
|
|
31
|
+
Pass the key to `OliraClient` or to `olira.init()`:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import olira
|
|
35
|
+
olira.init(api_key="olira_prod_...") # or set OLIRA_API_KEY env var
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Patient Management
|
|
41
|
+
|
|
42
|
+
Patients must exist before you can log events against them. Use the `api:manage-patients` scope.
|
|
43
|
+
|
|
44
|
+
Olira assigns a stable `id` to each patient at creation time. The `id` returned in the `Patient` object is what you use in all subsequent calls.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from olira import OliraClient
|
|
48
|
+
|
|
49
|
+
client = OliraClient(api_key="olira_prod_...")
|
|
50
|
+
|
|
51
|
+
# Create — Olira assigns the id; store it for future calls
|
|
52
|
+
patient = client.create_patient(
|
|
53
|
+
first_name="Jane",
|
|
54
|
+
last_name="Smith",
|
|
55
|
+
timezone="America/New_York",
|
|
56
|
+
primary_disease_site="breast",
|
|
57
|
+
disease_stage="II",
|
|
58
|
+
)
|
|
59
|
+
patient_id = patient.id
|
|
60
|
+
|
|
61
|
+
# Get
|
|
62
|
+
patient = client.get_patient(patient_id=patient_id)
|
|
63
|
+
|
|
64
|
+
# List (paginated, returns PatientListResult)
|
|
65
|
+
result = client.list_patients(limit=50, offset=0)
|
|
66
|
+
for p in result.patients:
|
|
67
|
+
print(p.id, p.first_name, p.last_name)
|
|
68
|
+
|
|
69
|
+
# Update (only supplied fields are changed)
|
|
70
|
+
patient = client.update_patient(patient_id=patient_id, disease_stage="III")
|
|
71
|
+
|
|
72
|
+
# Soft-delete
|
|
73
|
+
client.delete_patient(patient_id=patient_id)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Event Logging
|
|
79
|
+
|
|
80
|
+
Log a single event in the background (fire-and-forget):
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
import olira
|
|
84
|
+
from olira import OliraLogType
|
|
85
|
+
|
|
86
|
+
olira.init(api_key="olira_prod_...")
|
|
87
|
+
|
|
88
|
+
olira.log(
|
|
89
|
+
log_type=OliraLogType.USER_LOGIN,
|
|
90
|
+
patient_id=patient_id, # id from patient.id
|
|
91
|
+
)
|
|
92
|
+
olira.flush() # block until delivery
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Send a batch directly and get back a result:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from olira import OliraClient, LogSpec, OliraLogType, EsasItem
|
|
99
|
+
|
|
100
|
+
client = OliraClient(api_key="olira_prod_...")
|
|
101
|
+
result = client.log_batch([
|
|
102
|
+
LogSpec(log_type=OliraLogType.USER_LOGIN, patient_id=patient_id),
|
|
103
|
+
LogSpec(
|
|
104
|
+
log_type=OliraLogType.SYMPTOM_REPORT,
|
|
105
|
+
patient_id=patient_id,
|
|
106
|
+
payload={
|
|
107
|
+
"instrument": "esas_r",
|
|
108
|
+
"symptoms": [EsasItem(name="pain", score=4).model_dump()],
|
|
109
|
+
},
|
|
110
|
+
),
|
|
111
|
+
])
|
|
112
|
+
print(f"accepted={result.accepted}, failed={result.failed}")
|
|
113
|
+
client.close()
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Patient Token
|
|
119
|
+
|
|
120
|
+
Mint a short-lived JWT scoped to a single patient. Requires the `sdk:patient-token` scope.
|
|
121
|
+
|
|
122
|
+
Use this when a patient device needs to communicate with the [Olira MCP Patient State server](https://olira.ai/api-docs) — pass the token as a Bearer header. The token expires after 15 minutes and is locked to the specified patient.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from olira import OliraClient
|
|
126
|
+
|
|
127
|
+
client = OliraClient(api_key="olira_prod_...")
|
|
128
|
+
token = client.get_patient_token(patient_id=patient_id)
|
|
129
|
+
|
|
130
|
+
print(token.access_token) # forward this to the patient device
|
|
131
|
+
print(token.expires_in) # 900 (seconds)
|
|
132
|
+
client.close()
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Async client
|
|
138
|
+
|
|
139
|
+
All methods are available on `AsyncOliraClient` as coroutines:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
import asyncio
|
|
143
|
+
from olira import AsyncOliraClient, OliraLogType
|
|
144
|
+
|
|
145
|
+
async def main():
|
|
146
|
+
async with AsyncOliraClient(api_key="olira_prod_...") as client:
|
|
147
|
+
patient = await client.create_patient(
|
|
148
|
+
first_name="Jane",
|
|
149
|
+
last_name="Smith",
|
|
150
|
+
)
|
|
151
|
+
await client.log(
|
|
152
|
+
log_type=OliraLogType.USER_LOGIN,
|
|
153
|
+
patient_id=patient.id,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
asyncio.run(main())
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Error handling
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from olira import AuthError, ValidationError, RateLimitError, ServerError
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
client.log_batch([...])
|
|
168
|
+
except AuthError:
|
|
169
|
+
# Invalid or revoked API key, or missing scope
|
|
170
|
+
...
|
|
171
|
+
except ValidationError:
|
|
172
|
+
# Bad request (400/404/422) — e.g. unknown event type or missing required field
|
|
173
|
+
...
|
|
174
|
+
except RateLimitError as e:
|
|
175
|
+
# Retry after e.retry_after seconds
|
|
176
|
+
...
|
|
177
|
+
except ServerError:
|
|
178
|
+
# Transient server error after all retries exhausted
|
|
179
|
+
...
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Contributing
|
|
185
|
+
|
|
186
|
+
Dependencies are public PyPI packages only:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
bash scripts/install-dev.sh
|
|
190
|
+
./scripts/pre-pr.sh
|
|
191
|
+
```
|