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
ritten/__init__.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK.
|
|
3
|
+
|
|
4
|
+
:copyright: (c) 2026 by Wesley Gonçalves.
|
|
5
|
+
:license: MIT, see LICENSE for more details.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
9
|
+
|
|
10
|
+
from ritten.ritten import Ritten
|
|
11
|
+
from ritten.auth import Auth
|
|
12
|
+
from ritten.config import Config
|
|
13
|
+
from ritten.exceptions import (
|
|
14
|
+
RittenError,
|
|
15
|
+
RittenClientError,
|
|
16
|
+
RittenConnectionError,
|
|
17
|
+
RittenAPIError,
|
|
18
|
+
RittenAuthError,
|
|
19
|
+
RittenUnauthorizedError,
|
|
20
|
+
RittenValidationError,
|
|
21
|
+
RittenNotFoundError,
|
|
22
|
+
RittenRateLimitError,
|
|
23
|
+
RittenServerError,
|
|
24
|
+
)
|
|
25
|
+
from ritten.resources import (
|
|
26
|
+
Resource,
|
|
27
|
+
Calendar,
|
|
28
|
+
Cases,
|
|
29
|
+
Contacts,
|
|
30
|
+
Facilities,
|
|
31
|
+
Forms,
|
|
32
|
+
Insurance,
|
|
33
|
+
Organizations,
|
|
34
|
+
Patients,
|
|
35
|
+
Programs,
|
|
36
|
+
Users,
|
|
37
|
+
)
|
|
38
|
+
from ritten.storage import TokenStorage, MemoryStorage
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"Ritten",
|
|
42
|
+
"Auth",
|
|
43
|
+
"Config",
|
|
44
|
+
"TokenStorage",
|
|
45
|
+
"MemoryStorage",
|
|
46
|
+
"RittenError",
|
|
47
|
+
"RittenClientError",
|
|
48
|
+
"RittenConnectionError",
|
|
49
|
+
"RittenAPIError",
|
|
50
|
+
"RittenAuthError",
|
|
51
|
+
"RittenUnauthorizedError",
|
|
52
|
+
"RittenValidationError",
|
|
53
|
+
"RittenNotFoundError",
|
|
54
|
+
"RittenRateLimitError",
|
|
55
|
+
"RittenServerError",
|
|
56
|
+
"Resource",
|
|
57
|
+
"Calendar",
|
|
58
|
+
"Cases",
|
|
59
|
+
"Contacts",
|
|
60
|
+
"Facilities",
|
|
61
|
+
"Forms",
|
|
62
|
+
"Insurance",
|
|
63
|
+
"Organizations",
|
|
64
|
+
"Patients",
|
|
65
|
+
"Programs",
|
|
66
|
+
"Users",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
__version__ = version("ritten-python-sdk")
|
|
72
|
+
except PackageNotFoundError:
|
|
73
|
+
__version__ = "unknown"
|
ritten/auth.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Authentication Handler.
|
|
3
|
+
|
|
4
|
+
:copyright: (c) 2026 by Wesley Gonçalves.
|
|
5
|
+
:license: MIT, see LICENSE for more details.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from ritten.config import Config
|
|
10
|
+
from ritten.exceptions import RittenAuthError, RittenUnauthorizedError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Auth(httpx.Auth):
|
|
14
|
+
"""Authentication handler for the Ritten API."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, config: Config):
|
|
17
|
+
self.config = config
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def access_token(self) -> str | None:
|
|
21
|
+
"""Fetch the access token."""
|
|
22
|
+
token = self.config.storage.get_token()
|
|
23
|
+
return token
|
|
24
|
+
|
|
25
|
+
@access_token.setter
|
|
26
|
+
def access_token(self, value: str) -> Auth:
|
|
27
|
+
"""Set a new access token."""
|
|
28
|
+
self.config.storage.set_token(value)
|
|
29
|
+
|
|
30
|
+
return self
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def client_id(self) -> str | None:
|
|
34
|
+
"""Fetch the client ID for authentication."""
|
|
35
|
+
return self.config.client_id
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def client_secret(self) -> str | None:
|
|
39
|
+
"""Fetch the client secret for authentication."""
|
|
40
|
+
return self.config.client_secret
|
|
41
|
+
|
|
42
|
+
def _fetch_new_token(self) -> None:
|
|
43
|
+
"""Fetch a new access token using client credentials."""
|
|
44
|
+
if not self.config.client_id or not self.config.client_secret:
|
|
45
|
+
raise RittenAuthError(
|
|
46
|
+
"Client ID and Client Secret are required to fetch a new access token."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
response = httpx.post(
|
|
50
|
+
f"{self.config.base_url}/oauth/token",
|
|
51
|
+
json={
|
|
52
|
+
"grant_type": "client_credentials",
|
|
53
|
+
"client_id": self.config.client_id,
|
|
54
|
+
"client_secret": self.config.client_secret,
|
|
55
|
+
"audience": self.config.audience,
|
|
56
|
+
},
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if response.status_code == 200:
|
|
60
|
+
self.access_token = response.json()["access_token"]
|
|
61
|
+
|
|
62
|
+
def add_bearer_token(self, request: httpx.Request) -> httpx.Request:
|
|
63
|
+
"""Add the Bearer token to the request headers."""
|
|
64
|
+
request.headers["Authorization"] = f"Bearer {self.access_token}"
|
|
65
|
+
return request
|
|
66
|
+
|
|
67
|
+
def auth_flow(self, request: httpx.Request):
|
|
68
|
+
"""Controls the entire authentication workflow."""
|
|
69
|
+
|
|
70
|
+
if not self.access_token:
|
|
71
|
+
self._fetch_new_token()
|
|
72
|
+
self.add_bearer_token(request)
|
|
73
|
+
|
|
74
|
+
response = yield request
|
|
75
|
+
|
|
76
|
+
# Handle Expiration (401 Unauthorized)
|
|
77
|
+
if response.status_code == 401:
|
|
78
|
+
self._fetch_new_token()
|
|
79
|
+
self.add_bearer_token(request)
|
|
80
|
+
|
|
81
|
+
# replays the API call
|
|
82
|
+
retry_request = yield request
|
|
83
|
+
|
|
84
|
+
# if still fails
|
|
85
|
+
if retry_request.status_code == 401:
|
|
86
|
+
retry_request.read()
|
|
87
|
+
response = retry_request.json()
|
|
88
|
+
raise RittenUnauthorizedError(
|
|
89
|
+
message=response.get("message", "Unauthorized request."),
|
|
90
|
+
status_code=retry_request.status_code,
|
|
91
|
+
)
|
ritten/config.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Configuration.
|
|
3
|
+
|
|
4
|
+
:copyright: (c) 2026 by Wesley Gonçalves.
|
|
5
|
+
:license: MIT, see LICENSE for more details.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
from pydantic import (
|
|
10
|
+
BaseModel,
|
|
11
|
+
Field,
|
|
12
|
+
field_validator,
|
|
13
|
+
ConfigDict,
|
|
14
|
+
)
|
|
15
|
+
from ritten.storage import TokenStorage, MemoryStorage
|
|
16
|
+
from ritten.exceptions import RittenError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Config(BaseModel):
|
|
20
|
+
"""Configuration for the Ritten SDK client."""
|
|
21
|
+
|
|
22
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
23
|
+
|
|
24
|
+
base_url: str = Field(
|
|
25
|
+
default="https://api.ritten.io/v1",
|
|
26
|
+
description="Base URL for the Ritten API.",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
audience: str = Field(
|
|
30
|
+
default="https://external-api.ritten.io",
|
|
31
|
+
description="Audience for OAuth token requests.",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Authentication
|
|
35
|
+
tenant_id: str | None = Field(
|
|
36
|
+
default="ritclinic",
|
|
37
|
+
description="Tenant ID for the Ritten API.",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
client_id: str | None = Field(
|
|
41
|
+
default=None,
|
|
42
|
+
description="Client ID for authentication.",
|
|
43
|
+
)
|
|
44
|
+
client_secret: str | None = Field(
|
|
45
|
+
default=None,
|
|
46
|
+
description="Client secret for authentication.",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
storage: TokenStorage = Field(
|
|
50
|
+
default=MemoryStorage(),
|
|
51
|
+
description="Token storage mechanism. Defaults to in-memory storage.",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
@field_validator("storage", mode="after")
|
|
55
|
+
@classmethod
|
|
56
|
+
def validate_storage_interface(cls, value: Any) -> Any:
|
|
57
|
+
"""Validates that the provided storage object implements the `TokenStorage` protocol if it is not None."""
|
|
58
|
+
if value is None:
|
|
59
|
+
return value
|
|
60
|
+
|
|
61
|
+
if not isinstance(value, TokenStorage):
|
|
62
|
+
raise RittenError(
|
|
63
|
+
"The provided storage object must implement the `TokenStorage` protocol."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return value
|
|
67
|
+
|
|
68
|
+
# Connection pooling and timeout settings
|
|
69
|
+
timeout: float = Field(
|
|
70
|
+
default=30.0,
|
|
71
|
+
description="The timeout in seconds for the HTTP client.",
|
|
72
|
+
)
|
|
73
|
+
max_connections: int = Field(
|
|
74
|
+
default=10,
|
|
75
|
+
description="Maximum number of concurrent connections in the pool.",
|
|
76
|
+
)
|
|
77
|
+
max_keepalive_connections: int = Field(
|
|
78
|
+
default=5,
|
|
79
|
+
description="Maximum number of keep-alive connections to maintain.",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
keepalive_expiry: float = Field(
|
|
83
|
+
default=5.0,
|
|
84
|
+
description="Time in seconds before a keep-alive connection is closed. Keep this low for serverless.",
|
|
85
|
+
)
|
ritten/decorators.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
import httpx
|
|
3
|
+
from pydantic import ValidationError
|
|
4
|
+
import json
|
|
5
|
+
from ritten.exceptions import (
|
|
6
|
+
RittenError,
|
|
7
|
+
RittenConnectionError,
|
|
8
|
+
RittenValueError,
|
|
9
|
+
RittenParseError,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def exception_handler(func):
|
|
14
|
+
"""
|
|
15
|
+
Decorator that translates third-party exceptions into native SDK exceptions.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@wraps(func)
|
|
19
|
+
def wrapper(*args, **kwargs):
|
|
20
|
+
try:
|
|
21
|
+
return func(*args, **kwargs)
|
|
22
|
+
|
|
23
|
+
# If it's a RittenError exception, reraise it.
|
|
24
|
+
except RittenError:
|
|
25
|
+
raise
|
|
26
|
+
|
|
27
|
+
# Network-level failures
|
|
28
|
+
except httpx.RequestError as e:
|
|
29
|
+
raise RittenConnectionError(f"A network error occurred: {str(e)}") from e
|
|
30
|
+
|
|
31
|
+
# Validation errors
|
|
32
|
+
except ValidationError as e:
|
|
33
|
+
raise RittenValueError(f"Data validation failed: {str(e)}") from e
|
|
34
|
+
|
|
35
|
+
# JSON parsing errors
|
|
36
|
+
except json.JSONDecodeError as e:
|
|
37
|
+
raise RittenParseError(
|
|
38
|
+
f"Failed to parse API response as JSON: {str(e)}"
|
|
39
|
+
) from e
|
|
40
|
+
|
|
41
|
+
# The Ultimate Catch-All for anything else
|
|
42
|
+
except Exception as e:
|
|
43
|
+
raise RittenError(f"An unexpected SDK error occurred: {str(e)}") from e
|
|
44
|
+
|
|
45
|
+
return wrapper
|
ritten/exceptions.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Exceptions.
|
|
3
|
+
|
|
4
|
+
:copyright: (c) 2026 by Wesley Gonçalves.
|
|
5
|
+
:license: MIT, see LICENSE for more details.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RittenError(Exception):
|
|
10
|
+
"""The base exception for all Ritten SDK errors."""
|
|
11
|
+
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RittenClientError(RittenError):
|
|
16
|
+
"""Client is misconfigured or used incorrectly."""
|
|
17
|
+
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RittenConnectionError(RittenError):
|
|
22
|
+
"""SDK cannot reach the API."""
|
|
23
|
+
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RittenValueError(RittenError):
|
|
28
|
+
"""Invalid value provided to a method."""
|
|
29
|
+
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RittenParseError(RittenError):
|
|
34
|
+
"""Failed to parse API response."""
|
|
35
|
+
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RittenAuthError(RittenError):
|
|
40
|
+
"""Authentication error."""
|
|
41
|
+
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# HTTP Response Errors
|
|
46
|
+
class RittenAPIError(RittenError):
|
|
47
|
+
"""Base class for errors returned by the Ritten API (HTTP 4xx and 5xx)."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, message: str, status_code: int, payload: dict | None = None):
|
|
50
|
+
super().__init__(message)
|
|
51
|
+
self.status_code = status_code
|
|
52
|
+
self.payload = payload or {}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class RittenUnauthorizedError(RittenAPIError):
|
|
56
|
+
"""401 Unauthorized or 403 Forbidden."""
|
|
57
|
+
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class RittenValidationError(RittenAPIError):
|
|
62
|
+
"""400 Bad Request or 422 Unprocessable Entity."""
|
|
63
|
+
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class RittenNotFoundError(RittenAPIError):
|
|
68
|
+
"""404 Not Found."""
|
|
69
|
+
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class RittenRateLimitError(RittenAPIError):
|
|
74
|
+
"""429 Too Many Requests."""
|
|
75
|
+
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class RittenServerError(RittenAPIError):
|
|
80
|
+
"""5xx Server Errors (e.g., 500, 502, 503)."""
|
|
81
|
+
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
ERROR_MAP = {
|
|
86
|
+
400: RittenValidationError,
|
|
87
|
+
403: RittenUnauthorizedError,
|
|
88
|
+
404: RittenNotFoundError,
|
|
89
|
+
422: RittenValidationError,
|
|
90
|
+
429: RittenRateLimitError,
|
|
91
|
+
500: RittenServerError,
|
|
92
|
+
502: RittenServerError,
|
|
93
|
+
503: RittenServerError,
|
|
94
|
+
}
|
|
95
|
+
"""Mapping of HTTP status codes to specific error classes for structured error handling."""
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Resources.
|
|
3
|
+
|
|
4
|
+
:copyright: (c) 2026 by Wesley Gonçalves.
|
|
5
|
+
:license: MIT, see LICENSE for more details.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from ritten.resources.resource import Resource
|
|
9
|
+
from ritten.resources.calendar import Calendar
|
|
10
|
+
from ritten.resources.cases import Cases
|
|
11
|
+
from ritten.resources.contacts import Contacts
|
|
12
|
+
from ritten.resources.facilities import Facilities
|
|
13
|
+
from ritten.resources.forms import Forms
|
|
14
|
+
from ritten.resources.insurance import Insurance
|
|
15
|
+
from ritten.resources.organizations import Organizations
|
|
16
|
+
from ritten.resources.patients import Patients
|
|
17
|
+
from ritten.resources.programs import Programs
|
|
18
|
+
from ritten.resources.users import Users
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"Resource",
|
|
22
|
+
"Calendar",
|
|
23
|
+
"Cases",
|
|
24
|
+
"Contacts",
|
|
25
|
+
"Facilities",
|
|
26
|
+
"Forms",
|
|
27
|
+
"Insurance",
|
|
28
|
+
"Organizations",
|
|
29
|
+
"Patients",
|
|
30
|
+
"Programs",
|
|
31
|
+
"Users",
|
|
32
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Calendar 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 Calendar(Resource):
|
|
15
|
+
"""
|
|
16
|
+
Handles all interactions with the Ritten API `/calendar/events` endpoints.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, client: httpx.Client):
|
|
20
|
+
self._client = client
|
|
21
|
+
self._base_path = "/calendar/events"
|
|
22
|
+
|
|
23
|
+
def list(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
24
|
+
"""
|
|
25
|
+
Queries calendar events based on specific criteria.
|
|
26
|
+
The POST body is the query object.
|
|
27
|
+
"""
|
|
28
|
+
return self._client.post(f"{self._base_path}/list", json=payload).json()
|
|
29
|
+
|
|
30
|
+
def create(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
31
|
+
"""Creates a new calendar event."""
|
|
32
|
+
return self._client.post(f"{self._base_path}", json=payload).json()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Cases 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 Cases(Resource):
|
|
15
|
+
"""
|
|
16
|
+
Handles all interactions with the Ritten API `/cases` endpoints.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, client: httpx.Client):
|
|
20
|
+
self._client = client
|
|
21
|
+
self._base_path = "/cases"
|
|
22
|
+
|
|
23
|
+
def list(self, limit: int = 20, offset: int = 0) -> Dict[str, Any]:
|
|
24
|
+
"""Lists cases in a clinic."""
|
|
25
|
+
params = {
|
|
26
|
+
"limit": limit,
|
|
27
|
+
"offset": offset,
|
|
28
|
+
}
|
|
29
|
+
return self._client.get(self._base_path, params=params).json()
|
|
30
|
+
|
|
31
|
+
def get(self, id: str) -> Dict[str, Any]:
|
|
32
|
+
"""Retrieves a single case by its ID."""
|
|
33
|
+
return self._client.get(f"{self._base_path}/{id}").json()
|
|
34
|
+
|
|
35
|
+
def create(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
36
|
+
"""Creates a new case."""
|
|
37
|
+
return self._client.post(self._base_path, json=payload).json()
|
|
38
|
+
|
|
39
|
+
def update(self, id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
40
|
+
"""Updates an existing case by ID."""
|
|
41
|
+
return self._client.patch(
|
|
42
|
+
f"{self._base_path}/{id}",
|
|
43
|
+
json=payload,
|
|
44
|
+
).json()
|
|
45
|
+
|
|
46
|
+
def create_note(self, id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
47
|
+
"""Creates a note for a specific case."""
|
|
48
|
+
return self._client.post(
|
|
49
|
+
f"{self._base_path}/{id}/notes",
|
|
50
|
+
json=payload,
|
|
51
|
+
).json()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Contacts 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 Contacts(Resource):
|
|
15
|
+
"""
|
|
16
|
+
Handles all interactions with the Ritten API `/contacts` endpoints.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, client: httpx.Client):
|
|
20
|
+
self._client = client
|
|
21
|
+
self._base_path = "/contacts"
|
|
22
|
+
|
|
23
|
+
def list(self, limit: int = 20, offset: int = 0) -> Dict[str, Any]:
|
|
24
|
+
"""Lists contacts in a clinic."""
|
|
25
|
+
params = {"limit": limit, "offset": offset}
|
|
26
|
+
return self._client.get(self._base_path, params=params).json()
|
|
27
|
+
|
|
28
|
+
def get(self, id: str) -> Dict[str, Any]:
|
|
29
|
+
"""Retrieves a single contact by their ID."""
|
|
30
|
+
return self._client.get(f"{self._base_path}/{id}").json()
|
|
31
|
+
|
|
32
|
+
def create(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
33
|
+
"""Creates a new contact."""
|
|
34
|
+
return self._client.post(self._base_path, json=payload).json()
|
|
35
|
+
|
|
36
|
+
def update(self, id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
37
|
+
"""Updates an existing contact by ID."""
|
|
38
|
+
return self._client.patch(
|
|
39
|
+
f"{self._base_path}/{id}",
|
|
40
|
+
json=payload,
|
|
41
|
+
).json()
|
|
42
|
+
|
|
43
|
+
def list_relationships(self, id: str) -> Dict[str, Any]:
|
|
44
|
+
"""Lists a contact's relationships."""
|
|
45
|
+
return self._client.get(f"{self._base_path}/{id}/relationships").json()
|
|
46
|
+
|
|
47
|
+
def create_relationship(self, id: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
48
|
+
"""Creates a new relationship for a contact."""
|
|
49
|
+
return self._client.post(
|
|
50
|
+
f"{self._base_path}/{id}/relationships",
|
|
51
|
+
json=payload,
|
|
52
|
+
).json()
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Facilities 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
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
from ritten.resources.resource import Resource
|
|
13
|
+
from ritten.utils import to_iso_format
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Facilities(Resource):
|
|
17
|
+
"""
|
|
18
|
+
Handles all interactions with the Ritten API `/facilities` endpoints.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, client: httpx.Client):
|
|
22
|
+
self._client = client
|
|
23
|
+
self._base_path = "/facilities"
|
|
24
|
+
|
|
25
|
+
def list(
|
|
26
|
+
self,
|
|
27
|
+
limit: int = 20,
|
|
28
|
+
offset: int = 0,
|
|
29
|
+
search: str | None = None,
|
|
30
|
+
created_after: datetime | None = None,
|
|
31
|
+
) -> Dict[str, Any]:
|
|
32
|
+
"""Lists active clinic facilities.
|
|
33
|
+
|
|
34
|
+
Arguments:
|
|
35
|
+
limit: Number of facilities to return (default: 20).
|
|
36
|
+
offset: Number of facilities to skip for pagination (default: 0).
|
|
37
|
+
search: Optional case-insensitive search to filter facilities by name.
|
|
38
|
+
created_after: Optional datetime to filter facilities created after this date.
|
|
39
|
+
"""
|
|
40
|
+
params = {"limit": limit, "offset": offset}
|
|
41
|
+
if search:
|
|
42
|
+
params["search"] = search
|
|
43
|
+
if created_after:
|
|
44
|
+
params["createdAfter"] = to_iso_format(created_after)
|
|
45
|
+
return self._client.get(self._base_path, params=params).json()
|
|
46
|
+
|
|
47
|
+
def create(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
48
|
+
"""Creates a new facility."""
|
|
49
|
+
return self._client.post(self._base_path, json=payload).json()
|
|
50
|
+
|
|
51
|
+
def update(self, id: str, name: str) -> None | Dict[str, Any]:
|
|
52
|
+
"""Updates an existing facility by ID."""
|
|
53
|
+
payload = {"name": name}
|
|
54
|
+
return self._client.patch(f"{self._base_path}/{id}", json=payload).json()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Forms 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 Forms(Resource):
|
|
15
|
+
"""
|
|
16
|
+
Handles all interactions with the Ritten API `/forms/definitions` endpoints.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, client: httpx.Client):
|
|
20
|
+
self._client = client
|
|
21
|
+
self._base_path = "/forms/definitions"
|
|
22
|
+
|
|
23
|
+
def list(
|
|
24
|
+
self, show_archived: bool, is_tx_plan: bool, field_definition_ids: list[str]
|
|
25
|
+
) -> Dict[str, Any]:
|
|
26
|
+
"""List all form definitions configured for the clinic, including section definitions and signature requirements."""
|
|
27
|
+
params = {
|
|
28
|
+
"showArchived": show_archived,
|
|
29
|
+
"isTxPlan": is_tx_plan,
|
|
30
|
+
"fieldDefinitionIds": field_definition_ids,
|
|
31
|
+
}
|
|
32
|
+
return self._client.get(f"{self._base_path}", params=params).json()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ritten SDK Insurance 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 Insurance(Resource):
|
|
15
|
+
"""
|
|
16
|
+
Handles all interactions with the Ritten API `/insurance/payers` endpoints.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, client: httpx.Client):
|
|
20
|
+
self._client = client
|
|
21
|
+
self._base_path = "/insurance/payers"
|
|
22
|
+
|
|
23
|
+
def list(self) -> Dict[str, Any]:
|
|
24
|
+
"""List all insurance payers."""
|
|
25
|
+
return self._client.get(f"{self._base_path}").json()
|
|
26
|
+
|
|
27
|
+
def get(self, id: str) -> Dict[str, Any]:
|
|
28
|
+
"""Retrieves a single insurance payer by their ID."""
|
|
29
|
+
return self._client.get(f"{self._base_path}/{id}").json()
|