mailrith-sdk 0.1.0b1__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.
- mailrith_sdk-0.1.0b1/LICENSE +21 -0
- mailrith_sdk-0.1.0b1/PKG-INFO +36 -0
- mailrith_sdk-0.1.0b1/README.md +15 -0
- mailrith_sdk-0.1.0b1/mailrith_sdk/__init__.py +3 -0
- mailrith_sdk-0.1.0b1/mailrith_sdk/client.py +273 -0
- mailrith_sdk-0.1.0b1/mailrith_sdk/contract.json +3 -0
- mailrith_sdk-0.1.0b1/mailrith_sdk/manifest.json +4598 -0
- mailrith_sdk-0.1.0b1/mailrith_sdk/py.typed +1 -0
- mailrith_sdk-0.1.0b1/mailrith_sdk.egg-info/PKG-INFO +36 -0
- mailrith_sdk-0.1.0b1/mailrith_sdk.egg-info/SOURCES.txt +13 -0
- mailrith_sdk-0.1.0b1/mailrith_sdk.egg-info/dependency_links.txt +1 -0
- mailrith_sdk-0.1.0b1/mailrith_sdk.egg-info/top_level.txt +1 -0
- mailrith_sdk-0.1.0b1/pyproject.toml +39 -0
- mailrith_sdk-0.1.0b1/setup.cfg +4 -0
- mailrith_sdk-0.1.0b1/tests/test_client.py +103 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mailrith
|
|
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,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mailrith-sdk
|
|
3
|
+
Version: 0.1.0b1
|
|
4
|
+
Summary: Official Python SDK for Mailrith's public API.
|
|
5
|
+
Author: Mailrith
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Documentation, https://mailrith.com/developers/sdks
|
|
8
|
+
Project-URL: Homepage, https://mailrith.com
|
|
9
|
+
Project-URL: Source, https://github.com/anrawool/mailrith-agent-platform
|
|
10
|
+
Project-URL: Issues, https://github.com/anrawool/mailrith-agent-platform/issues
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# Mailrith Python SDK
|
|
23
|
+
|
|
24
|
+
Official Python SDK for Mailrith's public API.
|
|
25
|
+
|
|
26
|
+
This package is generated from Mailrith's versioned OpenAPI-derived SDK manifest and provides
|
|
27
|
+
resource namespaces such as `client.subscribers.list()` and `client.broadcasts.send()`.
|
|
28
|
+
|
|
29
|
+
```py
|
|
30
|
+
from mailrith_sdk import MailrithClient
|
|
31
|
+
|
|
32
|
+
client = MailrithClient(api_key="mrk_example_secret_key")
|
|
33
|
+
|
|
34
|
+
capabilities = client.discovery.get_capabilities()
|
|
35
|
+
subscribers = client.subscribers.list(query={"limit": 25})
|
|
36
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Mailrith Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for Mailrith's public API.
|
|
4
|
+
|
|
5
|
+
This package is generated from Mailrith's versioned OpenAPI-derived SDK manifest and provides
|
|
6
|
+
resource namespaces such as `client.subscribers.list()` and `client.broadcasts.send()`.
|
|
7
|
+
|
|
8
|
+
```py
|
|
9
|
+
from mailrith_sdk import MailrithClient
|
|
10
|
+
|
|
11
|
+
client = MailrithClient(api_key="mrk_example_secret_key")
|
|
12
|
+
|
|
13
|
+
capabilities = client.discovery.get_capabilities()
|
|
14
|
+
subscribers = client.subscribers.list(query={"limit": 25})
|
|
15
|
+
```
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import uuid
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from importlib import resources
|
|
7
|
+
from typing import Any, Callable, Mapping
|
|
8
|
+
from urllib import error, parse, request
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
TransportResponse = tuple[int, Mapping[str, str], Any]
|
|
12
|
+
Transport = Callable[[str, str, Mapping[str, str], str | None], TransportResponse]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _load_manifest() -> list[dict[str, Any]]:
|
|
16
|
+
with resources.files("mailrith_sdk").joinpath("manifest.json").open(
|
|
17
|
+
"r", encoding="utf-8"
|
|
18
|
+
) as handle:
|
|
19
|
+
return json.load(handle)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
_SDK_MANIFEST = _load_manifest()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MailrithApiError(RuntimeError):
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
*,
|
|
29
|
+
status: int,
|
|
30
|
+
message: str,
|
|
31
|
+
error_type: str | None = None,
|
|
32
|
+
code: str | None = None,
|
|
33
|
+
response_body: Any = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
super().__init__(message)
|
|
36
|
+
self.status = status
|
|
37
|
+
self.type = error_type
|
|
38
|
+
self.code = code
|
|
39
|
+
self.response_body = response_body
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class _Operation:
|
|
44
|
+
namespace: str
|
|
45
|
+
method_name: str
|
|
46
|
+
operation_id: str
|
|
47
|
+
method: str
|
|
48
|
+
path: str
|
|
49
|
+
auth_required: bool
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _default_transport(
|
|
53
|
+
method: str,
|
|
54
|
+
url: str,
|
|
55
|
+
headers: Mapping[str, str],
|
|
56
|
+
body: str | None,
|
|
57
|
+
) -> TransportResponse:
|
|
58
|
+
http_request = request.Request(
|
|
59
|
+
url=url,
|
|
60
|
+
method=method,
|
|
61
|
+
headers=dict(headers),
|
|
62
|
+
data=body.encode("utf-8") if body is not None else None,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
with request.urlopen(http_request) as response:
|
|
67
|
+
payload = response.read().decode("utf-8")
|
|
68
|
+
content_type = response.headers.get("Content-Type", "")
|
|
69
|
+
parsed = json.loads(payload) if payload and "application/json" in content_type else payload or None
|
|
70
|
+
return response.status, dict(response.headers.items()), parsed
|
|
71
|
+
except error.HTTPError as exc:
|
|
72
|
+
payload = exc.read().decode("utf-8")
|
|
73
|
+
content_type = exc.headers.get("Content-Type", "")
|
|
74
|
+
parsed = json.loads(payload) if payload and "application/json" in content_type else payload or None
|
|
75
|
+
return exc.code, dict(exc.headers.items()), parsed
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _normalize_base_url(value: str | None) -> str:
|
|
79
|
+
return (value or "https://api.mailrith.com").rstrip("/")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _encode_path(pathname: str, path_params: Mapping[str, str | int] | None) -> str:
|
|
83
|
+
encoded = pathname
|
|
84
|
+
for segment in _extract_path_params(pathname):
|
|
85
|
+
if path_params is None or segment not in path_params:
|
|
86
|
+
raise ValueError(f"Missing required path parameter: {segment}.")
|
|
87
|
+
encoded = encoded.replace(
|
|
88
|
+
"{" + segment + "}",
|
|
89
|
+
parse.quote(str(path_params[segment]), safe=""),
|
|
90
|
+
)
|
|
91
|
+
return encoded
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _extract_path_params(pathname: str) -> list[str]:
|
|
95
|
+
parts: list[str] = []
|
|
96
|
+
cursor = 0
|
|
97
|
+
while True:
|
|
98
|
+
start = pathname.find("{", cursor)
|
|
99
|
+
if start < 0:
|
|
100
|
+
return parts
|
|
101
|
+
end = pathname.find("}", start)
|
|
102
|
+
if end < 0:
|
|
103
|
+
return parts
|
|
104
|
+
parts.append(pathname[start + 1 : end])
|
|
105
|
+
cursor = end + 1
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _append_query(url: str, query: Mapping[str, Any] | None) -> str:
|
|
109
|
+
if not query:
|
|
110
|
+
return url
|
|
111
|
+
|
|
112
|
+
items: list[tuple[str, str]] = []
|
|
113
|
+
for key, value in query.items():
|
|
114
|
+
if value is None:
|
|
115
|
+
continue
|
|
116
|
+
if isinstance(value, list):
|
|
117
|
+
for item in value:
|
|
118
|
+
if item is not None:
|
|
119
|
+
items.append((key, _stringify_query_value(item)))
|
|
120
|
+
continue
|
|
121
|
+
items.append((key, _stringify_query_value(value)))
|
|
122
|
+
|
|
123
|
+
if not items:
|
|
124
|
+
return url
|
|
125
|
+
separator = "&" if "?" in url else "?"
|
|
126
|
+
return f"{url}{separator}{parse.urlencode(items)}"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _stringify_query_value(value: Any) -> str:
|
|
130
|
+
if isinstance(value, bool):
|
|
131
|
+
return "true" if value else "false"
|
|
132
|
+
return str(value)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class _ResourceNamespace:
|
|
136
|
+
def __init__(self, client: "MailrithClient", resource: dict[str, Any]) -> None:
|
|
137
|
+
self._client = client
|
|
138
|
+
self._operations = {
|
|
139
|
+
operation["method_name"]: _Operation(
|
|
140
|
+
namespace=resource["namespace"],
|
|
141
|
+
method_name=operation["method_name"],
|
|
142
|
+
operation_id=operation["operation_id"],
|
|
143
|
+
method=operation["method"],
|
|
144
|
+
path=operation["path"],
|
|
145
|
+
auth_required=bool(operation["auth_required"]),
|
|
146
|
+
)
|
|
147
|
+
for operation in resource["operations"]
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
def __getattr__(self, method_name: str):
|
|
151
|
+
operation = self._operations.get(method_name)
|
|
152
|
+
if operation is None:
|
|
153
|
+
raise AttributeError(method_name)
|
|
154
|
+
|
|
155
|
+
def invoke(
|
|
156
|
+
*,
|
|
157
|
+
path: Mapping[str, str | int] | None = None,
|
|
158
|
+
query: Mapping[str, Any] | None = None,
|
|
159
|
+
body: Any = None,
|
|
160
|
+
headers: Mapping[str, str] | None = None,
|
|
161
|
+
idempotency_key: str | None = None,
|
|
162
|
+
api_key: str | None = None,
|
|
163
|
+
) -> Any:
|
|
164
|
+
return self._client.request(
|
|
165
|
+
operation,
|
|
166
|
+
path=path,
|
|
167
|
+
query=query,
|
|
168
|
+
body=body,
|
|
169
|
+
headers=headers,
|
|
170
|
+
idempotency_key=idempotency_key,
|
|
171
|
+
api_key=api_key,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return invoke
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class MailrithClient:
|
|
178
|
+
def __init__(
|
|
179
|
+
self,
|
|
180
|
+
*,
|
|
181
|
+
base_url: str | None = None,
|
|
182
|
+
api_key: str | None = None,
|
|
183
|
+
transport: Transport | None = None,
|
|
184
|
+
default_headers: Mapping[str, str] | None = None,
|
|
185
|
+
) -> None:
|
|
186
|
+
self.base_url = _normalize_base_url(base_url)
|
|
187
|
+
self.api_key = api_key
|
|
188
|
+
self.transport = transport or _default_transport
|
|
189
|
+
self.default_headers = dict(default_headers or {})
|
|
190
|
+
self.operations = _SDK_MANIFEST
|
|
191
|
+
|
|
192
|
+
for resource in _SDK_MANIFEST:
|
|
193
|
+
setattr(self, resource["namespace"], _ResourceNamespace(self, resource))
|
|
194
|
+
|
|
195
|
+
def with_api_key(self, api_key: str) -> "MailrithClient":
|
|
196
|
+
return MailrithClient(
|
|
197
|
+
base_url=self.base_url,
|
|
198
|
+
api_key=api_key,
|
|
199
|
+
transport=self.transport,
|
|
200
|
+
default_headers=self.default_headers,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def get_operation(self, namespace: str, method_name: str) -> dict[str, Any] | None:
|
|
204
|
+
for resource in _SDK_MANIFEST:
|
|
205
|
+
if resource["namespace"] != namespace:
|
|
206
|
+
continue
|
|
207
|
+
for operation in resource["operations"]:
|
|
208
|
+
if operation["method_name"] == method_name:
|
|
209
|
+
return operation
|
|
210
|
+
return None
|
|
211
|
+
|
|
212
|
+
def request(
|
|
213
|
+
self,
|
|
214
|
+
operation: _Operation,
|
|
215
|
+
*,
|
|
216
|
+
path: Mapping[str, str | int] | None = None,
|
|
217
|
+
query: Mapping[str, Any] | None = None,
|
|
218
|
+
body: Any = None,
|
|
219
|
+
headers: Mapping[str, str] | None = None,
|
|
220
|
+
idempotency_key: str | None = None,
|
|
221
|
+
api_key: str | None = None,
|
|
222
|
+
) -> Any:
|
|
223
|
+
resolved_api_key = api_key or self.api_key
|
|
224
|
+
if operation.auth_required and not resolved_api_key:
|
|
225
|
+
raise ValueError(
|
|
226
|
+
f"Mailrith operation {operation.operation_id} requires a bearer credential."
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
url = self.base_url + _encode_path(operation.path, path)
|
|
230
|
+
url = _append_query(url, query)
|
|
231
|
+
|
|
232
|
+
request_headers: dict[str, str] = {
|
|
233
|
+
"accept": "application/json",
|
|
234
|
+
"x-mailrith-client": "python_sdk/dev",
|
|
235
|
+
"x-mailrith-request-id": f"req_{uuid.uuid4()}",
|
|
236
|
+
**self.default_headers,
|
|
237
|
+
**dict(headers or {}),
|
|
238
|
+
}
|
|
239
|
+
if resolved_api_key:
|
|
240
|
+
request_headers["authorization"] = f"Bearer {resolved_api_key}"
|
|
241
|
+
if idempotency_key:
|
|
242
|
+
request_headers["idempotency-key"] = idempotency_key
|
|
243
|
+
|
|
244
|
+
serialized_body: str | None = None
|
|
245
|
+
if body is not None:
|
|
246
|
+
request_headers["content-type"] = "application/json"
|
|
247
|
+
serialized_body = body if isinstance(body, str) else json.dumps(body)
|
|
248
|
+
|
|
249
|
+
status, response_headers, response_body = self.transport(
|
|
250
|
+
operation.method,
|
|
251
|
+
url,
|
|
252
|
+
request_headers,
|
|
253
|
+
serialized_body,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if status < 200 or status >= 300:
|
|
257
|
+
error_payload = (
|
|
258
|
+
response_body.get("error")
|
|
259
|
+
if isinstance(response_body, dict) and isinstance(response_body.get("error"), dict)
|
|
260
|
+
else {}
|
|
261
|
+
)
|
|
262
|
+
raise MailrithApiError(
|
|
263
|
+
status=status,
|
|
264
|
+
message=error_payload.get(
|
|
265
|
+
"message",
|
|
266
|
+
f"Mailrith request failed with status {status}.",
|
|
267
|
+
),
|
|
268
|
+
error_type=error_payload.get("type"),
|
|
269
|
+
code=error_payload.get("code"),
|
|
270
|
+
response_body=response_body,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
return response_body
|