ritten-python-sdk 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.
- ritten_python_sdk-1.0.0/LICENSE +21 -0
- ritten_python_sdk-1.0.0/PKG-INFO +62 -0
- ritten_python_sdk-1.0.0/README.md +37 -0
- ritten_python_sdk-1.0.0/pyproject.toml +31 -0
- ritten_python_sdk-1.0.0/src/ritten/__init__.py +73 -0
- ritten_python_sdk-1.0.0/src/ritten/auth.py +91 -0
- ritten_python_sdk-1.0.0/src/ritten/config.py +85 -0
- ritten_python_sdk-1.0.0/src/ritten/decorators.py +45 -0
- ritten_python_sdk-1.0.0/src/ritten/exceptions.py +95 -0
- ritten_python_sdk-1.0.0/src/ritten/resources/__init__.py +32 -0
- ritten_python_sdk-1.0.0/src/ritten/resources/calendar.py +32 -0
- ritten_python_sdk-1.0.0/src/ritten/resources/cases.py +51 -0
- ritten_python_sdk-1.0.0/src/ritten/resources/contacts.py +52 -0
- ritten_python_sdk-1.0.0/src/ritten/resources/facilities.py +54 -0
- ritten_python_sdk-1.0.0/src/ritten/resources/forms.py +32 -0
- ritten_python_sdk-1.0.0/src/ritten/resources/insurance.py +29 -0
- ritten_python_sdk-1.0.0/src/ritten/resources/organizations.py +66 -0
- ritten_python_sdk-1.0.0/src/ritten/resources/patients.py +115 -0
- ritten_python_sdk-1.0.0/src/ritten/resources/programs.py +51 -0
- ritten_python_sdk-1.0.0/src/ritten/resources/resource.py +25 -0
- ritten_python_sdk-1.0.0/src/ritten/resources/users.py +51 -0
- ritten_python_sdk-1.0.0/src/ritten/ritten.py +171 -0
- ritten_python_sdk-1.0.0/src/ritten/storage/__init__.py +7 -0
- ritten_python_sdk-1.0.0/src/ritten/storage/memory_storage.py +22 -0
- ritten_python_sdk-1.0.0/src/ritten/storage/token_storage.py +25 -0
- ritten_python_sdk-1.0.0/src/ritten/utils.py +15 -0
|
@@ -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.
|
|
@@ -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,37 @@
|
|
|
1
|
+
# Ritten API Integration
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
Ritten API specifications:
|
|
6
|
+
|
|
7
|
+
- [Documentation](https://docs.ritten.io/)
|
|
8
|
+
- Version: 1
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
The SDK requires **Python 3.10 or higher**. Install it directly via `pip`:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install ritten-python-sdk
|
|
16
|
+
|
|
17
|
+
# Using poetry
|
|
18
|
+
poetry add ritten-python-sdk
|
|
19
|
+
|
|
20
|
+
# Using pipenv
|
|
21
|
+
pipenv install ritten-python-sdk
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quickstart Guide
|
|
25
|
+
|
|
26
|
+
To get started, you must first obtain your integrator `client_id` and `client_secret` from your Ritten administrator portal.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Advanced Resources
|
|
31
|
+
|
|
32
|
+
- Review the official [Ritten API Documentation](https://docs.ritten.io/) for explicit payload schemas.
|
|
33
|
+
- Check the `/examples` directory inside this repository for production-ready boilerplate code.
|
|
34
|
+
|
|
35
|
+
## About the Author
|
|
36
|
+
|
|
37
|
+
This SDK is developed and maintained by [Wesley Gonçalves](https://github.com/wesleygoncalves) under MIT license. Contributions and feedback are welcome!
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ritten-python-sdk"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Python SDK for connecting applications to the Ritten EMR Platform. Simplifies integration with the Behavioral Health Operations API."
|
|
5
|
+
keywords = ["ritten", "emr", "healthcare", "sdk", "python"]
|
|
6
|
+
authors = [
|
|
7
|
+
{name = "Wesley Gonçalves",email = "dev@wesleygoncalves.com"}
|
|
8
|
+
]
|
|
9
|
+
license-files = ["LICENSE"]
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"httpx >=0.28.1,<0.29.0",
|
|
14
|
+
"pydantic >=2.13.4,<3.0.0"
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
homepage = "https://github.com/wesleygoncalves/ritten-python-sdk"
|
|
19
|
+
source = "https://github.com/wesleygoncalves/ritten-python-sdk"
|
|
20
|
+
issues = "https://github.com/wesleygoncalves/ritten-python-sdk/issues"
|
|
21
|
+
changelog = "https://github.com/wesleygoncalves/ritten-python-sdk/blob/main/CHANGELOG.md"
|
|
22
|
+
documentation = "https://github.com/wesleygoncalves/ritten-python-sdk/blob/main/README.md"
|
|
23
|
+
|
|
24
|
+
[tool.poetry]
|
|
25
|
+
packages = [
|
|
26
|
+
{ include = "ritten", from = "src" },
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
31
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -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"
|
|
@@ -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
|
+
)
|
|
@@ -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
|
+
)
|
|
@@ -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
|
|
@@ -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()
|