mailrith-sdk 0.1.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.
@@ -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,35 @@
1
+ Metadata-Version: 2.4
2
+ Name: mailrith-sdk
3
+ Version: 0.1.0
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: Issues, https://github.com/anrawool/mailrith-agent-platform/issues
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Dynamic: license-file
20
+
21
+ # Mailrith Python SDK
22
+
23
+ Official Python SDK for Mailrith's public API.
24
+
25
+ This package is generated from Mailrith's versioned OpenAPI-derived SDK manifest and provides
26
+ resource namespaces such as `client.subscribers.list()` and `client.broadcasts.send()`.
27
+
28
+ ```py
29
+ from mailrith_sdk import MailrithClient
30
+
31
+ client = MailrithClient(api_key="mrk_example_secret_key")
32
+
33
+ capabilities = client.discovery.get_capabilities()
34
+ subscribers = client.subscribers.list(query={"limit": 25})
35
+ ```
@@ -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,3 @@
1
+ from .client import MailrithApiError, MailrithClient
2
+
3
+ __all__ = ["MailrithApiError", "MailrithClient"]
@@ -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
@@ -0,0 +1,3 @@
1
+ {
2
+ "contract_version": "v1"
3
+ }