dominus-sdk-python 2.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.
@@ -0,0 +1,251 @@
1
+ """
2
+ Courier Namespace - Email delivery via Postmark.
3
+
4
+ Provides email sending using Postmark templates.
5
+ """
6
+ from typing import Any, Dict, Optional, TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from ..start import Dominus
10
+
11
+
12
+ class CourierNamespace:
13
+ """
14
+ Email delivery namespace.
15
+
16
+ All email operations go through /api/courier/* endpoints.
17
+ Uses Postmark for transactional email delivery.
18
+
19
+ Usage:
20
+ # Send welcome email
21
+ result = await dominus.courier.send(
22
+ template_alias="welcome",
23
+ to="user@example.com",
24
+ from_email="noreply@myapp.com",
25
+ model={
26
+ "name": "John Smith",
27
+ "product_name": "My App"
28
+ }
29
+ )
30
+
31
+ # Send with tracking tag
32
+ result = await dominus.courier.send(
33
+ template_alias="password-reset",
34
+ to="user@example.com",
35
+ from_email="noreply@myapp.com",
36
+ model={"reset_link": "https://..."},
37
+ tag="password-reset"
38
+ )
39
+ """
40
+
41
+ def __init__(self, client: "Dominus"):
42
+ self._client = client
43
+
44
+ async def send(
45
+ self,
46
+ template_alias: str,
47
+ to: str,
48
+ from_email: str,
49
+ model: Dict[str, Any],
50
+ tag: Optional[str] = None,
51
+ reply_to: Optional[str] = None
52
+ ) -> Dict[str, Any]:
53
+ """
54
+ Send email using Postmark template.
55
+
56
+ Args:
57
+ template_alias: Template alias in Postmark (e.g., "welcome", "password-reset")
58
+ to: Recipient email address
59
+ from_email: Sender email address (must be verified in Postmark)
60
+ model: Template model variables (dict of key-value pairs)
61
+ tag: Optional tag for tracking/filtering in Postmark
62
+ reply_to: Optional reply-to address
63
+
64
+ Returns:
65
+ Dict with:
66
+ - to: Recipient email
67
+ - message_id: Postmark message UUID
68
+ - submitted_at: Timestamp
69
+
70
+ Example:
71
+ result = await dominus.courier.send(
72
+ template_alias="welcome",
73
+ to="john@example.com",
74
+ from_email="hello@myapp.com",
75
+ model={
76
+ "name": "John",
77
+ "company_name": "My App",
78
+ "action_url": "https://myapp.com/verify?token=abc123"
79
+ },
80
+ tag="welcome"
81
+ )
82
+ print(f"Email sent: {result['message_id']}")
83
+ """
84
+ body = {
85
+ "template_alias": template_alias,
86
+ "to": to,
87
+ "from": from_email,
88
+ "model": model
89
+ }
90
+
91
+ if tag:
92
+ body["tag"] = tag
93
+ if reply_to:
94
+ body["reply_to"] = reply_to
95
+
96
+ return await self._client._request(
97
+ endpoint="/api/courier/send",
98
+ body=body
99
+ )
100
+
101
+ # Convenience methods for common templates
102
+
103
+ async def send_welcome(
104
+ self,
105
+ to: str,
106
+ from_email: str,
107
+ name: str,
108
+ product_name: str,
109
+ action_url: Optional[str] = None,
110
+ **extra_model
111
+ ) -> Dict[str, Any]:
112
+ """
113
+ Send welcome email.
114
+
115
+ Args:
116
+ to: Recipient email
117
+ from_email: Sender email
118
+ name: User's name
119
+ product_name: Your product/app name
120
+ action_url: Optional action button URL
121
+ **extra_model: Additional template variables
122
+
123
+ Returns:
124
+ Send result dict
125
+ """
126
+ model = {
127
+ "name": name,
128
+ "product_name": product_name,
129
+ **extra_model
130
+ }
131
+ if action_url:
132
+ model["action_url"] = action_url
133
+
134
+ return await self.send(
135
+ template_alias="welcome",
136
+ to=to,
137
+ from_email=from_email,
138
+ model=model,
139
+ tag="welcome"
140
+ )
141
+
142
+ async def send_password_reset(
143
+ self,
144
+ to: str,
145
+ from_email: str,
146
+ name: str,
147
+ reset_url: str,
148
+ product_name: str,
149
+ **extra_model
150
+ ) -> Dict[str, Any]:
151
+ """
152
+ Send password reset email.
153
+
154
+ Args:
155
+ to: Recipient email
156
+ from_email: Sender email
157
+ name: User's name
158
+ reset_url: Password reset link
159
+ product_name: Your product/app name
160
+ **extra_model: Additional template variables
161
+
162
+ Returns:
163
+ Send result dict
164
+ """
165
+ return await self.send(
166
+ template_alias="password-reset",
167
+ to=to,
168
+ from_email=from_email,
169
+ model={
170
+ "name": name,
171
+ "action_url": reset_url,
172
+ "product_name": product_name,
173
+ **extra_model
174
+ },
175
+ tag="password-reset"
176
+ )
177
+
178
+ async def send_email_verification(
179
+ self,
180
+ to: str,
181
+ from_email: str,
182
+ name: str,
183
+ verify_url: str,
184
+ product_name: str,
185
+ **extra_model
186
+ ) -> Dict[str, Any]:
187
+ """
188
+ Send email verification email.
189
+
190
+ Args:
191
+ to: Recipient email
192
+ from_email: Sender email
193
+ name: User's name
194
+ verify_url: Email verification link
195
+ product_name: Your product/app name
196
+ **extra_model: Additional template variables
197
+
198
+ Returns:
199
+ Send result dict
200
+ """
201
+ return await self.send(
202
+ template_alias="email-verification",
203
+ to=to,
204
+ from_email=from_email,
205
+ model={
206
+ "name": name,
207
+ "action_url": verify_url,
208
+ "product_name": product_name,
209
+ **extra_model
210
+ },
211
+ tag="email-verification"
212
+ )
213
+
214
+ async def send_invitation(
215
+ self,
216
+ to: str,
217
+ from_email: str,
218
+ name: str,
219
+ invite_url: str,
220
+ inviter_name: str,
221
+ product_name: str,
222
+ **extra_model
223
+ ) -> Dict[str, Any]:
224
+ """
225
+ Send user invitation email.
226
+
227
+ Args:
228
+ to: Recipient email
229
+ from_email: Sender email
230
+ name: Invitee's name
231
+ invite_url: Invitation accept link
232
+ inviter_name: Name of person who invited
233
+ product_name: Your product/app name
234
+ **extra_model: Additional template variables
235
+
236
+ Returns:
237
+ Send result dict
238
+ """
239
+ return await self.send(
240
+ template_alias="invitation",
241
+ to=to,
242
+ from_email=from_email,
243
+ model={
244
+ "name": name,
245
+ "action_url": invite_url,
246
+ "inviter_name": inviter_name,
247
+ "product_name": product_name,
248
+ **extra_model
249
+ },
250
+ tag="invitation"
251
+ )
@@ -0,0 +1,267 @@
1
+ """
2
+ Database Namespace - Scribe data CRUD operations.
3
+
4
+ Provides data operations for all schemas with support for secure table access.
5
+ """
6
+ from typing import Any, Dict, List, Optional, TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from ..start import Dominus
10
+
11
+
12
+ class DbNamespace:
13
+ """
14
+ Database CRUD namespace.
15
+
16
+ All data operations go through /api/scribe/data/* endpoints.
17
+ Secure tables (registered in auth.secure_tables) require a `reason` parameter.
18
+
19
+ Usage:
20
+ # List tables
21
+ tables = await dominus.db.tables()
22
+ tables = await dominus.db.tables(schema="tenant_acme")
23
+
24
+ # Query with filters
25
+ users = await dominus.db.query("users", filters={"status": "active"})
26
+
27
+ # Query secure table (requires reason)
28
+ patients = await dominus.db.query(
29
+ "patients",
30
+ schema="tenant_acme",
31
+ reason="Reviewing chart for appointment #123",
32
+ actor=current_user_id
33
+ )
34
+
35
+ # Insert data
36
+ await dominus.db.insert("users", {"name": "John", "email": "john@example.com"})
37
+
38
+ # Update rows
39
+ await dominus.db.update("users", {"status": "inactive"}, filters={"id": user_id})
40
+
41
+ # Delete rows
42
+ await dominus.db.delete("users", filters={"id": user_id})
43
+ """
44
+
45
+ def __init__(self, client: "Dominus"):
46
+ self._client = client
47
+
48
+ async def tables(self, schema: str = "public") -> List[Dict[str, Any]]:
49
+ """
50
+ List tables in a schema.
51
+
52
+ Args:
53
+ schema: Schema name (default: "public")
54
+
55
+ Returns:
56
+ List of table metadata
57
+ """
58
+ result = await self._client._request(
59
+ endpoint=f"/api/scribe/data/{schema}/tables",
60
+ method="GET"
61
+ )
62
+ return result.get("tables", result) if isinstance(result, dict) else result
63
+
64
+ async def columns(self, table: str, schema: str = "public") -> List[Dict[str, Any]]:
65
+ """
66
+ List columns in a table.
67
+
68
+ Args:
69
+ table: Table name
70
+ schema: Schema name (default: "public")
71
+
72
+ Returns:
73
+ List of column metadata
74
+ """
75
+ result = await self._client._request(
76
+ endpoint=f"/api/scribe/data/{schema}/{table}/columns",
77
+ method="GET"
78
+ )
79
+ return result.get("columns", result) if isinstance(result, dict) else result
80
+
81
+ async def query(
82
+ self,
83
+ table: str,
84
+ schema: str = "public",
85
+ filters: Optional[Dict[str, Any]] = None,
86
+ sort_by: Optional[str] = None,
87
+ sort_order: str = "ASC",
88
+ limit: int = 100,
89
+ offset: int = 0,
90
+ reason: Optional[str] = None,
91
+ actor: Optional[str] = None
92
+ ) -> Dict[str, Any]:
93
+ """
94
+ Query table data with filtering, sorting, and pagination.
95
+
96
+ Args:
97
+ table: Table name
98
+ schema: Schema name (default: "public")
99
+ filters: Column:value filter dictionary
100
+ sort_by: Column to sort by
101
+ sort_order: "ASC" or "DESC"
102
+ limit: Maximum rows to return (default: 100)
103
+ offset: Rows to skip (default: 0)
104
+ reason: Access justification (required for secure tables)
105
+ actor: User ID or "machine" for audit trail
106
+
107
+ Returns:
108
+ Dict with "rows" and "total" keys
109
+
110
+ Raises:
111
+ SecureTableError: If accessing secure table without reason
112
+ """
113
+ body = {
114
+ "filters": filters,
115
+ "sort_by": sort_by,
116
+ "sort_order": sort_order,
117
+ "limit": limit,
118
+ "offset": offset
119
+ }
120
+
121
+ if reason:
122
+ body["reason"] = reason
123
+ if actor:
124
+ body["actor"] = actor
125
+
126
+ return await self._client._request(
127
+ endpoint=f"/api/scribe/data/{schema}/{table}/query",
128
+ body=body
129
+ )
130
+
131
+ async def insert(
132
+ self,
133
+ table: str,
134
+ data: Dict[str, Any],
135
+ schema: str = "public",
136
+ reason: Optional[str] = None,
137
+ actor: Optional[str] = None
138
+ ) -> Dict[str, Any]:
139
+ """
140
+ Insert a row into a table.
141
+
142
+ Args:
143
+ table: Table name
144
+ data: Column:value dictionary
145
+ schema: Schema name (default: "public")
146
+ reason: Access justification (required for secure tables)
147
+ actor: User ID or "machine" for audit trail
148
+
149
+ Returns:
150
+ Inserted row data
151
+ """
152
+ body = {"data": data}
153
+
154
+ if reason:
155
+ body["reason"] = reason
156
+ if actor:
157
+ body["actor"] = actor
158
+
159
+ return await self._client._request(
160
+ endpoint=f"/api/scribe/data/{schema}/{table}/insert",
161
+ body=body
162
+ )
163
+
164
+ async def update(
165
+ self,
166
+ table: str,
167
+ data: Dict[str, Any],
168
+ filters: Dict[str, Any],
169
+ schema: str = "public",
170
+ reason: Optional[str] = None,
171
+ actor: Optional[str] = None
172
+ ) -> Dict[str, Any]:
173
+ """
174
+ Update rows matching filters.
175
+
176
+ Args:
177
+ table: Table name
178
+ data: Column:value dictionary of updates
179
+ filters: Column:value dictionary for WHERE clause
180
+ schema: Schema name (default: "public")
181
+ reason: Access justification (required for secure tables)
182
+ actor: User ID or "machine" for audit trail
183
+
184
+ Returns:
185
+ Dict with "affected_rows" count
186
+ """
187
+ body = {
188
+ "data": data,
189
+ "filters": filters
190
+ }
191
+
192
+ if reason:
193
+ body["reason"] = reason
194
+ if actor:
195
+ body["actor"] = actor
196
+
197
+ return await self._client._request(
198
+ endpoint=f"/api/scribe/data/{schema}/{table}/update",
199
+ body=body
200
+ )
201
+
202
+ async def delete(
203
+ self,
204
+ table: str,
205
+ filters: Dict[str, Any],
206
+ schema: str = "public",
207
+ reason: Optional[str] = None,
208
+ actor: Optional[str] = None
209
+ ) -> Dict[str, Any]:
210
+ """
211
+ Delete rows matching filters.
212
+
213
+ Args:
214
+ table: Table name
215
+ filters: Column:value dictionary for WHERE clause
216
+ schema: Schema name (default: "public")
217
+ reason: Access justification (required for secure tables)
218
+ actor: User ID or "machine" for audit trail
219
+
220
+ Returns:
221
+ Dict with "affected_rows" count
222
+ """
223
+ body = {"filters": filters}
224
+
225
+ if reason:
226
+ body["reason"] = reason
227
+ if actor:
228
+ body["actor"] = actor
229
+
230
+ return await self._client._request(
231
+ endpoint=f"/api/scribe/data/{schema}/{table}/delete",
232
+ body=body
233
+ )
234
+
235
+ async def bulk_insert(
236
+ self,
237
+ table: str,
238
+ rows: List[Dict[str, Any]],
239
+ schema: str = "public",
240
+ reason: Optional[str] = None,
241
+ actor: Optional[str] = None
242
+ ) -> Dict[str, Any]:
243
+ """
244
+ Insert multiple rows at once.
245
+
246
+ Args:
247
+ table: Table name
248
+ rows: List of column:value dictionaries
249
+ schema: Schema name (default: "public")
250
+ reason: Access justification (required for secure tables)
251
+ actor: User ID or "machine" for audit trail
252
+
253
+ Returns:
254
+ Dict with "inserted_count" and optionally "rows"
255
+ """
256
+ body = {"rows": rows}
257
+
258
+ if reason:
259
+ body["reason"] = reason
260
+ if actor:
261
+ body["actor"] = actor
262
+
263
+ return await self._client._request(
264
+ endpoint=f"/api/scribe/data/{schema}/{table}/bulk-insert",
265
+ body=body
266
+ )
267
+