ritten-python-sdk 1.0.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.
- ritten/__init__.py +73 -0
- ritten/auth.py +91 -0
- ritten/config.py +85 -0
- ritten/decorators.py +45 -0
- ritten/exceptions.py +95 -0
- ritten/resources/__init__.py +32 -0
- ritten/resources/calendar.py +32 -0
- ritten/resources/cases.py +51 -0
- ritten/resources/contacts.py +52 -0
- ritten/resources/facilities.py +54 -0
- ritten/resources/forms.py +32 -0
- ritten/resources/insurance.py +29 -0
- ritten/resources/organizations.py +66 -0
- ritten/resources/patients.py +115 -0
- ritten/resources/programs.py +51 -0
- ritten/resources/resource.py +25 -0
- ritten/resources/users.py +51 -0
- ritten/ritten.py +171 -0
- ritten/storage/__init__.py +7 -0
- ritten/storage/memory_storage.py +22 -0
- ritten/storage/token_storage.py +25 -0
- ritten/utils.py +15 -0
- ritten_python_sdk-1.0.0.dist-info/METADATA +62 -0
- ritten_python_sdk-1.0.0.dist-info/RECORD +26 -0
- ritten_python_sdk-1.0.0.dist-info/WHEEL +4 -0
- ritten_python_sdk-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Organizations Resource.
|
|
3
|
+
|
|
4
|
+
:copyright: (c) 2026 by Wesley Gonçalves.
|
|
5
|
+
:license: MIT, see LICENSE for more details.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from typing import Dict, Any
|
|
10
|
+
|
|
11
|
+
from ritten.resources.resource import Resource
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Organizations(Resource):
|
|
15
|
+
"""
|
|
16
|
+
Handles all interactions with the Ritten API `/organizations` endpoints.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, client: httpx.Client):
|
|
20
|
+
self._client = client
|
|
21
|
+
self._base_path = "/organizations"
|
|
22
|
+
|
|
23
|
+
def list(
|
|
24
|
+
self,
|
|
25
|
+
limit: int = 20,
|
|
26
|
+
offset: int = 0,
|
|
27
|
+
search: str | None = None,
|
|
28
|
+
sort_by: str | None = None,
|
|
29
|
+
organization_type_ids: list[str] | None = None,
|
|
30
|
+
assigned_user_ids: list[str] | None = None,
|
|
31
|
+
) -> Dict[str, Any]:
|
|
32
|
+
"""Lists active organizations."""
|
|
33
|
+
params = {"limit": limit, "offset": offset}
|
|
34
|
+
if search:
|
|
35
|
+
params["search"] = search
|
|
36
|
+
if sort_by:
|
|
37
|
+
params["sortBy"] = sort_by
|
|
38
|
+
if organization_type_ids:
|
|
39
|
+
params["organizationTypeIds"] = str(organization_type_ids)
|
|
40
|
+
if assigned_user_ids:
|
|
41
|
+
params["assignedUserIds"] = str(assigned_user_ids)
|
|
42
|
+
return self._client.get(self._base_path, params=params).json()
|
|
43
|
+
|
|
44
|
+
def get(self, id: str) -> Dict[str, Any]:
|
|
45
|
+
"""Retrieves a single active organization by its ID."""
|
|
46
|
+
return self._client.get(f"{self._base_path}/{id}").json()
|
|
47
|
+
|
|
48
|
+
def create(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
49
|
+
"""Creates a new organization."""
|
|
50
|
+
return self._client.post(self._base_path, json=payload).json()
|
|
51
|
+
|
|
52
|
+
def update(self, id: str, payload: Dict[str, Any]) -> None | Dict[str, Any]:
|
|
53
|
+
"""Updates an active organization by ID."""
|
|
54
|
+
return self._client.patch(f"{self._base_path}/{id}", json=payload).json()
|
|
55
|
+
|
|
56
|
+
def members(
|
|
57
|
+
self,
|
|
58
|
+
organization_id: str,
|
|
59
|
+
limit: int = 20,
|
|
60
|
+
offset: int = 0,
|
|
61
|
+
) -> Dict[str, Any]:
|
|
62
|
+
"""Lists members of an active organization."""
|
|
63
|
+
params = {"limit": limit, "offset": offset}
|
|
64
|
+
return self._client.get(
|
|
65
|
+
f"{self._base_path}/{organization_id}/members", params=params
|
|
66
|
+
).json()
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Patients Resource.
|
|
3
|
+
|
|
4
|
+
:copyright: (c) 2026 by Wesley Gonçalves.
|
|
5
|
+
:license: MIT, see LICENSE for more details.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from typing import Dict, Any
|
|
10
|
+
|
|
11
|
+
from ritten.resources.resource import Resource
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Patients(Resource):
|
|
15
|
+
"""
|
|
16
|
+
Handles all interactions with the Ritten API `/patients` endpoints.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, client: httpx.Client):
|
|
20
|
+
self._client = client
|
|
21
|
+
self._base_path = "/patients"
|
|
22
|
+
|
|
23
|
+
def list(
|
|
24
|
+
self,
|
|
25
|
+
program_status: str,
|
|
26
|
+
limit: int = 20,
|
|
27
|
+
offset: int = 0,
|
|
28
|
+
) -> Dict[str, Any]:
|
|
29
|
+
"""Lists patients in a clinic."""
|
|
30
|
+
|
|
31
|
+
params = {
|
|
32
|
+
"programStatus": program_status,
|
|
33
|
+
"limit": limit,
|
|
34
|
+
"offset": offset,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return self._client.get(self._base_path, params=params).json()
|
|
38
|
+
|
|
39
|
+
def get(self, id: str) -> Dict[str, Any]:
|
|
40
|
+
"""Retrieves a single patient by their Ritten ID."""
|
|
41
|
+
return self._client.get(f"{self._base_path}/{id}").json()
|
|
42
|
+
|
|
43
|
+
def create(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
44
|
+
"""Creates a new patient record."""
|
|
45
|
+
return self._client.post(self._base_path, json=payload).json()
|
|
46
|
+
|
|
47
|
+
def update(self, id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
48
|
+
"""Updates a patient by ID."""
|
|
49
|
+
return self._client.patch(
|
|
50
|
+
f"{self._base_path}/{id}",
|
|
51
|
+
json=payload,
|
|
52
|
+
).json()
|
|
53
|
+
|
|
54
|
+
def get_by_external_id(self, external_id: str) -> Dict[str, Any]:
|
|
55
|
+
"""Retrieves a patient using an external system ID."""
|
|
56
|
+
return self._client.get(f"{self._base_path}/external/{external_id}").json()
|
|
57
|
+
|
|
58
|
+
# --- Clinical Data ---
|
|
59
|
+
|
|
60
|
+
def record_vitals(self, id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
61
|
+
"""Records a single vital observation for the patient.
|
|
62
|
+
Units are fixed by observation and measurement type; do not include units in the request body.
|
|
63
|
+
"""
|
|
64
|
+
return self._client.post(
|
|
65
|
+
f"{self._base_path}/{id}/vitals",
|
|
66
|
+
json=payload,
|
|
67
|
+
).json()
|
|
68
|
+
|
|
69
|
+
# --- Relationships ---
|
|
70
|
+
|
|
71
|
+
def list_relationships(self, id: str) -> Dict[str, Any]:
|
|
72
|
+
"""Lists a patient's relationships."""
|
|
73
|
+
return self._client.get(f"{self._base_path}/{id}/relationships").json()
|
|
74
|
+
|
|
75
|
+
def create_relationship(self, id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
76
|
+
"""Creates a new patient relationship."""
|
|
77
|
+
return self._client.post(
|
|
78
|
+
f"{self._base_path}/{id}/relationships",
|
|
79
|
+
json=payload,
|
|
80
|
+
).json()
|
|
81
|
+
|
|
82
|
+
def update_relationship(
|
|
83
|
+
self, patient_id: str, relationship_id: str, payload: Dict[str, Any]
|
|
84
|
+
) -> Dict[str, Any]:
|
|
85
|
+
"""Updates a specific patient relationship."""
|
|
86
|
+
return self._client.patch(
|
|
87
|
+
f"{self._base_path}/{patient_id}/relationships/{relationship_id}",
|
|
88
|
+
json=payload,
|
|
89
|
+
).json()
|
|
90
|
+
|
|
91
|
+
def delete_relationship(self, patient_id: str, relationship_id: str) -> None:
|
|
92
|
+
"""Deletes a patient relationship."""
|
|
93
|
+
self._client.delete(
|
|
94
|
+
f"{self._base_path}/{patient_id}/relationships/{relationship_id}",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# --- Chart Documents ---
|
|
98
|
+
|
|
99
|
+
def attach_document(
|
|
100
|
+
self, patient_id: str, payload: Dict[str, Any]
|
|
101
|
+
) -> Dict[str, Any]:
|
|
102
|
+
"""Attaches a document to a patient chart."""
|
|
103
|
+
return self._client.post(
|
|
104
|
+
f"{self._base_path}/{patient_id}/attachments",
|
|
105
|
+
json=payload,
|
|
106
|
+
).json()
|
|
107
|
+
|
|
108
|
+
def update_document(
|
|
109
|
+
self, patient_id: str, attachment_id: str, payload: Dict[str, Any]
|
|
110
|
+
) -> Dict[str, Any]:
|
|
111
|
+
"""Updates the title or type of a document on a patient chart. Omitting a field will leave it unchanged."""
|
|
112
|
+
|
|
113
|
+
return self._client.patch(
|
|
114
|
+
f"{self._base_path}/{patient_id}/attachments/{attachment_id}", json=payload
|
|
115
|
+
).json()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Programs Resource.
|
|
3
|
+
|
|
4
|
+
:copyright: (c) 2026 by Wesley Gonçalves.
|
|
5
|
+
:license: MIT, see LICENSE for more details.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from typing import Dict, Any
|
|
10
|
+
|
|
11
|
+
from ritten.resources.resource import Resource
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Programs(Resource):
|
|
15
|
+
"""
|
|
16
|
+
Handles all interactions with the Ritten API `/programs` endpoints.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, client: httpx.Client):
|
|
20
|
+
self._client = client
|
|
21
|
+
self._base_path = "/programs"
|
|
22
|
+
|
|
23
|
+
def list(
|
|
24
|
+
self,
|
|
25
|
+
limit: int = 20,
|
|
26
|
+
offset: int = 0,
|
|
27
|
+
search: str | None = None,
|
|
28
|
+
programType: str | None = None,
|
|
29
|
+
facility_id: str | None = None,
|
|
30
|
+
) -> Dict[str, Any]:
|
|
31
|
+
"""Lists active programs configured in the clinic."""
|
|
32
|
+
params = {"limit": limit, "offset": offset}
|
|
33
|
+
if search is not None:
|
|
34
|
+
params["search"] = search
|
|
35
|
+
if programType is not None:
|
|
36
|
+
params["programType"] = programType
|
|
37
|
+
if facility_id is not None:
|
|
38
|
+
params["facility_id"] = facility_id
|
|
39
|
+
return self._client.get(self._base_path, params=params).json()
|
|
40
|
+
|
|
41
|
+
def get(self, id: str) -> Dict[str, Any]:
|
|
42
|
+
"""Retrieves an active program by its ID."""
|
|
43
|
+
return self._client.get(f"{self._base_path}/{id}").json()
|
|
44
|
+
|
|
45
|
+
def create(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
46
|
+
"""Creates a new program."""
|
|
47
|
+
return self._client.post(self._base_path, json=payload).json()
|
|
48
|
+
|
|
49
|
+
def update(self, id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
50
|
+
"""Updates an active program's configurations by ID."""
|
|
51
|
+
return self._client.patch(f"{self._base_path}/{id}", json=payload).json()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Resource Base Class.
|
|
3
|
+
|
|
4
|
+
:copyright: (c) 2026 by Wesley Gonçalves.
|
|
5
|
+
:license: MIT, see LICENSE for more details.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import inspect
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from ritten.decorators import exception_handler
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Resource:
|
|
15
|
+
"""
|
|
16
|
+
A base class for all SDK resources.
|
|
17
|
+
It automatically wraps all public methods with an exception handler.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
21
|
+
super().__init_subclass__(**kwargs)
|
|
22
|
+
|
|
23
|
+
for attr_name, attr_value in cls.__dict__.items():
|
|
24
|
+
if inspect.isfunction(attr_value) and not attr_name.startswith("_"):
|
|
25
|
+
setattr(cls, attr_name, exception_handler(attr_value))
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Users Resource.
|
|
3
|
+
|
|
4
|
+
:copyright: (c) 2026 by Wesley Gonçalves.
|
|
5
|
+
:license: MIT, see LICENSE for more details.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from typing import Dict, Any
|
|
10
|
+
|
|
11
|
+
from ritten.resources.resource import Resource
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Users(Resource):
|
|
15
|
+
"""
|
|
16
|
+
Handles all interactions with the Ritten API `/staff`, `/users`, and `/teams` endpoints.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, client: httpx.Client):
|
|
20
|
+
self._client = client
|
|
21
|
+
self._base_path = "/users"
|
|
22
|
+
|
|
23
|
+
def list(self) -> Dict[str, Any]:
|
|
24
|
+
"""Lists users in a clinic."""
|
|
25
|
+
return self._client.get("staff").json()
|
|
26
|
+
|
|
27
|
+
def create(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
28
|
+
"""Creates a new user and triggers an invitation email."""
|
|
29
|
+
return self._client.post(self._base_path, json=payload).json()
|
|
30
|
+
|
|
31
|
+
def delete(self, id: str) -> None:
|
|
32
|
+
"""Deletes a user account."""
|
|
33
|
+
self._client.delete(f"{self._base_path}/{id}")
|
|
34
|
+
|
|
35
|
+
# --- Roles ---
|
|
36
|
+
|
|
37
|
+
def assign_role(self, user_id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
38
|
+
"""Assigns a role to a user."""
|
|
39
|
+
return self._client.post(
|
|
40
|
+
f"{self._base_path}/{user_id}/roles", json=payload
|
|
41
|
+
).json()
|
|
42
|
+
|
|
43
|
+
def remove_role(self, user_id: str, role_id: str) -> None:
|
|
44
|
+
"""Removes a role from a user."""
|
|
45
|
+
self._client.delete(f"{self._base_path}/{user_id}/roles/{role_id}")
|
|
46
|
+
|
|
47
|
+
# --- Clinic Teams ---
|
|
48
|
+
|
|
49
|
+
def list_clinic_teams(self) -> Dict[str, Any]:
|
|
50
|
+
"""Lists all clinic teams and their users."""
|
|
51
|
+
return self._client.get("teams").json()
|
ritten/ritten.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Client.
|
|
3
|
+
|
|
4
|
+
:copyright: (c) 2026 by Wesley Gonçalves.
|
|
5
|
+
:license: MIT, see LICENSE for more details.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from calendar import Calendar
|
|
9
|
+
from functools import cached_property
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
from ritten.auth import Auth
|
|
13
|
+
from ritten.config import Config
|
|
14
|
+
from ritten.decorators import exception_handler
|
|
15
|
+
from ritten.exceptions import RittenClientError, RittenAPIError, ERROR_MAP
|
|
16
|
+
from ritten.resources import (
|
|
17
|
+
Calendar,
|
|
18
|
+
Cases,
|
|
19
|
+
Contacts,
|
|
20
|
+
Facilities,
|
|
21
|
+
Forms,
|
|
22
|
+
Insurance,
|
|
23
|
+
Organizations,
|
|
24
|
+
Patients,
|
|
25
|
+
Programs,
|
|
26
|
+
Users,
|
|
27
|
+
)
|
|
28
|
+
from ritten.storage import TokenStorage
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Ritten:
|
|
32
|
+
"""A client for interacting with the Ritten API."""
|
|
33
|
+
|
|
34
|
+
@exception_handler
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
tenant_id: str | None = None,
|
|
38
|
+
client_id: str | None = None,
|
|
39
|
+
client_secret: str | None = None,
|
|
40
|
+
storage: TokenStorage | None = None,
|
|
41
|
+
*,
|
|
42
|
+
config: Config | None = None,
|
|
43
|
+
):
|
|
44
|
+
"""Initialize the RittenClient with authentication and connection settings."""
|
|
45
|
+
|
|
46
|
+
self.config: Config
|
|
47
|
+
if config:
|
|
48
|
+
self.config = config
|
|
49
|
+
elif tenant_id and client_id and client_secret:
|
|
50
|
+
self.config = Config(
|
|
51
|
+
tenant_id=tenant_id,
|
|
52
|
+
client_id=client_id,
|
|
53
|
+
client_secret=client_secret,
|
|
54
|
+
storage=storage,
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
raise RittenClientError(
|
|
58
|
+
"Either a Config object or tenant_id, client_id, and client_secret must be provided."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
self.auth = Auth(self.config)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
self._build_http_client()
|
|
65
|
+
except Exception as e:
|
|
66
|
+
raise RittenClientError(
|
|
67
|
+
f"Failed to initialize HTTP client: {str(e)}."
|
|
68
|
+
) from e
|
|
69
|
+
|
|
70
|
+
def _get_default_headers(self) -> dict:
|
|
71
|
+
"""Generate default headers for API requests, including authentication."""
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
"Content-Type": "application/json",
|
|
75
|
+
"X-Ritten-Tenant": self.config.tenant_id,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
def _raise_on_error_hook(self, response: httpx.Response):
|
|
79
|
+
"""Hook to raise exceptions on HTTP error responses."""
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
response.raise_for_status()
|
|
83
|
+
except httpx.HTTPStatusError as e:
|
|
84
|
+
status_code = e.response.status_code
|
|
85
|
+
|
|
86
|
+
# Yield to the Auth class for expiration retries
|
|
87
|
+
if status_code == 401:
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
response.read()
|
|
91
|
+
error_message = f"Ritten API Error [{status_code}]: {e.response.text}"
|
|
92
|
+
|
|
93
|
+
# Raise specific exception or default to base
|
|
94
|
+
ExceptionClass = ERROR_MAP.get(status_code, RittenAPIError)
|
|
95
|
+
raise ExceptionClass(error_message, status_code) from e
|
|
96
|
+
|
|
97
|
+
def _build_http_client(self) -> None:
|
|
98
|
+
"""Build an HTTP client with the specified configuration."""
|
|
99
|
+
|
|
100
|
+
# Configure limits explicitly based on user inputs
|
|
101
|
+
limits = httpx.Limits(
|
|
102
|
+
max_connections=self.config.max_connections,
|
|
103
|
+
max_keepalive_connections=self.config.max_keepalive_connections,
|
|
104
|
+
keepalive_expiry=self.config.keepalive_expiry,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
self.client = httpx.Client(
|
|
108
|
+
base_url=self.config.base_url,
|
|
109
|
+
headers=self._get_default_headers(),
|
|
110
|
+
auth=self.auth,
|
|
111
|
+
limits=limits,
|
|
112
|
+
timeout=self.config.timeout,
|
|
113
|
+
event_hooks={"response": [self._raise_on_error_hook]},
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
@exception_handler
|
|
117
|
+
def close(self):
|
|
118
|
+
"""Close the HTTP client and release resources."""
|
|
119
|
+
self.client.close()
|
|
120
|
+
|
|
121
|
+
# --- Resource Accessors ---
|
|
122
|
+
|
|
123
|
+
@cached_property
|
|
124
|
+
def calendar(self):
|
|
125
|
+
"""Access the Calendar resource."""
|
|
126
|
+
return Calendar(self.client)
|
|
127
|
+
|
|
128
|
+
@cached_property
|
|
129
|
+
def cases(self):
|
|
130
|
+
"""Access the Cases resource."""
|
|
131
|
+
return Cases(self.client)
|
|
132
|
+
|
|
133
|
+
@cached_property
|
|
134
|
+
def contacts(self):
|
|
135
|
+
"""Access the Contacts resource."""
|
|
136
|
+
return Contacts(self.client)
|
|
137
|
+
|
|
138
|
+
@cached_property
|
|
139
|
+
def forms(self):
|
|
140
|
+
"""Access the Forms resource."""
|
|
141
|
+
return Forms(self.client)
|
|
142
|
+
|
|
143
|
+
@cached_property
|
|
144
|
+
def facilities(self):
|
|
145
|
+
"""Access the Facilities resource."""
|
|
146
|
+
return Facilities(self.client)
|
|
147
|
+
|
|
148
|
+
@cached_property
|
|
149
|
+
def insurance(self):
|
|
150
|
+
"""Access the Insurance resource."""
|
|
151
|
+
return Insurance(self.client)
|
|
152
|
+
|
|
153
|
+
@cached_property
|
|
154
|
+
def organizations(self):
|
|
155
|
+
"""Access the Organizations resource."""
|
|
156
|
+
return Organizations(self.client)
|
|
157
|
+
|
|
158
|
+
@cached_property
|
|
159
|
+
def patients(self):
|
|
160
|
+
"""Access the Patients resource."""
|
|
161
|
+
return Patients(self.client)
|
|
162
|
+
|
|
163
|
+
@cached_property
|
|
164
|
+
def programs(self):
|
|
165
|
+
"""Access the Programs resource."""
|
|
166
|
+
return Programs(self.client)
|
|
167
|
+
|
|
168
|
+
@cached_property
|
|
169
|
+
def users(self):
|
|
170
|
+
"""Access the Users resource."""
|
|
171
|
+
return Users(self.client)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
In-memory token storage implementation.
|
|
3
|
+
|
|
4
|
+
:copyright: (c) 2026 by Wesley Gonçalves.
|
|
5
|
+
:license: MIT, see LICENSE for more details.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MemoryStorage:
|
|
10
|
+
"""
|
|
11
|
+
In-memory token storage implementation.
|
|
12
|
+
This is a simple storage mechanism that keeps the token in memory.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, initial_token: str | None = None):
|
|
16
|
+
self._token = initial_token
|
|
17
|
+
|
|
18
|
+
def get_token(self) -> str | None:
|
|
19
|
+
return self._token
|
|
20
|
+
|
|
21
|
+
def set_token(self, token: str) -> None:
|
|
22
|
+
self._token = token
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Token Storage Protocol.
|
|
3
|
+
|
|
4
|
+
:copyright: (c) 2026 by Wesley Gonçalves.
|
|
5
|
+
:license: MIT, see LICENSE for more details.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Protocol, runtime_checkable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@runtime_checkable
|
|
12
|
+
class TokenStorage(Protocol):
|
|
13
|
+
"""
|
|
14
|
+
Contract for token storage.
|
|
15
|
+
|
|
16
|
+
This allows for flexible implementations, such as in-memory, file-based, or database storage.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def get_token(self) -> str | None:
|
|
20
|
+
"""Retrieve the current access token, if it exists."""
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
def set_token(self, token: str) -> None:
|
|
24
|
+
"""Store a new access token."""
|
|
25
|
+
...
|
ritten/utils.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for the Ritten SDK.
|
|
3
|
+
|
|
4
|
+
:copyright: (c) 2026 by Wesley Gonçalves.
|
|
5
|
+
:license: MIT, see LICENSE for more details.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def to_iso_format(dt: datetime) -> str:
|
|
12
|
+
"""Converts a datetime object into a clean ISO 8601 string."""
|
|
13
|
+
if dt.tzinfo == timezone.utc:
|
|
14
|
+
return dt.isoformat(timespec="seconds").replace("+00:00", "Z")
|
|
15
|
+
return dt.isoformat(timespec="seconds")
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ritten-python-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python SDK for connecting applications to the Ritten EMR Platform. Simplifies integration with the Behavioral Health Operations API.
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Keywords: ritten,emr,healthcare,sdk,python
|
|
7
|
+
Author: Wesley Gonçalves
|
|
8
|
+
Author-email: dev@wesleygoncalves.com
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
17
|
+
Requires-Dist: pydantic (>=2.13.4,<3.0.0)
|
|
18
|
+
Project-URL: Documentation, https://github.com/wesleygoncalves/ritten-python-sdk/blob/main/README.md
|
|
19
|
+
Project-URL: Homepage, https://github.com/wesleygoncalves/ritten-python-sdk
|
|
20
|
+
Project-URL: changelog, https://github.com/wesleygoncalves/ritten-python-sdk/blob/main/CHANGELOG.md
|
|
21
|
+
Project-URL: issues, https://github.com/wesleygoncalves/ritten-python-sdk/issues
|
|
22
|
+
Project-URL: source, https://github.com/wesleygoncalves/ritten-python-sdk
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Ritten API Integration
|
|
26
|
+
|
|
27
|
+
Python SDK for connecting applications to the [Ritten EMR Platform](https://docs.ritten.io/). This library simplifies integration with Ritten's Behavioral Health Operations API, allowing developers to use Ritten API seamlessly from any Python environment.
|
|
28
|
+
|
|
29
|
+
Ritten API specifications:
|
|
30
|
+
|
|
31
|
+
- [Documentation](https://docs.ritten.io/)
|
|
32
|
+
- Version: 1
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
The SDK requires **Python 3.10 or higher**. Install it directly via `pip`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install ritten-python-sdk
|
|
40
|
+
|
|
41
|
+
# Using poetry
|
|
42
|
+
poetry add ritten-python-sdk
|
|
43
|
+
|
|
44
|
+
# Using pipenv
|
|
45
|
+
pipenv install ritten-python-sdk
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quickstart Guide
|
|
49
|
+
|
|
50
|
+
To get started, you must first obtain your integrator `client_id` and `client_secret` from your Ritten administrator portal.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Advanced Resources
|
|
55
|
+
|
|
56
|
+
- Review the official [Ritten API Documentation](https://docs.ritten.io/) for explicit payload schemas.
|
|
57
|
+
- Check the `/examples` directory inside this repository for production-ready boilerplate code.
|
|
58
|
+
|
|
59
|
+
## About the Author
|
|
60
|
+
|
|
61
|
+
This SDK is developed and maintained by [Wesley Gonçalves](https://github.com/wesleygoncalves) under MIT license. Contributions and feedback are welcome!
|
|
62
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
ritten/__init__.py,sha256=cTgBgEimjNrXHPMdRwzYoGy2mbnbXLRyubNlb9iLDHA,1420
|
|
2
|
+
ritten/auth.py,sha256=TCCGngqqs77Zkzw8oQh3nTpncOspCGoedc-D78XEAJA,2896
|
|
3
|
+
ritten/config.py,sha256=T3ZirCVlHAowexHXLpBlFuyCcGDHejuNxkOEDBSIcrk,2408
|
|
4
|
+
ritten/decorators.py,sha256=5JhGtxuKGq1_0ZLHjQVe5qgb_DP_AiSfOC5QTi0oh5I,1236
|
|
5
|
+
ritten/exceptions.py,sha256=TnkBwrRnzxr4BsILpMHHjr6Ge2flMj8LoaYP9bqErWc,1854
|
|
6
|
+
ritten/resources/__init__.py,sha256=ZrmphNH4aLA2Q101QhVIWPvD6ocfFMRy1C7i1Z_RNOM,825
|
|
7
|
+
ritten/resources/calendar.py,sha256=-Ff1IlaY2Ex-xpCyM5Ev0WT_HX0R1ZQTcPJN_ptKYCo,921
|
|
8
|
+
ritten/resources/cases.py,sha256=-hM06JpXrbUgS_h1WF0C6erChktUI9DP0hotVimvQX4,1530
|
|
9
|
+
ritten/resources/contacts.py,sha256=rVYSTMjHqztxfNBC82-pmESXqgRM0VO8hzQl_9zco9Y,1732
|
|
10
|
+
ritten/resources/facilities.py,sha256=GcsoSn_oIas61a037hEZoN_Pl5WZ_YrTkFtNHLrcc5w,1784
|
|
11
|
+
ritten/resources/forms.py,sha256=XM2hmGcca1Bu0FIUKCXMu4Rn_yaLxbCzkuoPhEpEc5Y,951
|
|
12
|
+
ritten/resources/insurance.py,sha256=dRB-NWMcNZ8jBWGV1fkDMs2imbOANX5i9jxD7wcyDfA,783
|
|
13
|
+
ritten/resources/organizations.py,sha256=jWwWJWsrWx58uNCQatg2wUug6c2DE4htc3You3FRatA,2171
|
|
14
|
+
ritten/resources/patients.py,sha256=N6_7ABzEtBJWGfDKa2WYhb_zJm3jPgMUGBEMD2c_gyY,3832
|
|
15
|
+
ritten/resources/programs.py,sha256=rhWZ_JUw_AJgjYBIc0TPFMFqMZbTx3fXy4YkuNA58uI,1651
|
|
16
|
+
ritten/resources/resource.py,sha256=JaYSSjtvzdQ9MItAozdMBLc8DMxca9qnKyoFzK4EntI,676
|
|
17
|
+
ritten/resources/users.py,sha256=2nJHVgdxEb9oZCfRD3gUfeUNSrtydPnvLPvEZNWv_ao,1548
|
|
18
|
+
ritten/ritten.py,sha256=kdTBWwq9q-zr9_PK0RNWREJGgTg-JLVvle6ch2wJfsI,4880
|
|
19
|
+
ritten/storage/__init__.py,sha256=k-UrqZXOIkTvwkRlWTDNGzkZwqr1umoyoLH9A71sFIc,138
|
|
20
|
+
ritten/storage/memory_storage.py,sha256=74rdYQMNaFHevPz1c4BZoe2hyKbtlDmoWoHmbleDiSs,527
|
|
21
|
+
ritten/storage/token_storage.py,sha256=fEEt91rFeAnhRD5p7XUiSK5Fzm7UnGSD1MyEeVmugoQ,591
|
|
22
|
+
ritten/utils.py,sha256=nACKXwMCXweawRVd1zQuyq_X1kTFr863Gp2bNVKLm30,434
|
|
23
|
+
ritten_python_sdk-1.0.0.dist-info/METADATA,sha256=XoWZy3S8G0XcQ4FgwJgFrYHu08Q7Iw5CVckmTPHls0I,2365
|
|
24
|
+
ritten_python_sdk-1.0.0.dist-info/WHEEL,sha256=EGEvSphFYqXKs23-kQBeyNoJP1nrT8ZJKQoi5p5DYL8,88
|
|
25
|
+
ritten_python_sdk-1.0.0.dist-info/licenses/LICENSE,sha256=RDlmenqWEqYFQ0g09ctFA35mXr1uk_YyK4iLDp_mGeI,1074
|
|
26
|
+
ritten_python_sdk-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wesley Gonçalves
|
|
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.
|