dominus-sdk-python-dev 2.1.6__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,590 @@
1
+ """
2
+ DDL Namespace - Smith schema and migration operations.
3
+
4
+ Provides DDL operations and Alembic migration management.
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 DdlNamespace:
13
+ """
14
+ Schema DDL and migration namespace.
15
+
16
+ All DDL operations go through /api/smith/* endpoints.
17
+
18
+ Usage:
19
+ # Table operations
20
+ await dominus.ddl.create_table("tenant_acme", "users", [
21
+ {"name": "id", "type": "UUID", "primary_key": True},
22
+ {"name": "email", "type": "VARCHAR(255)", "nullable": False}
23
+ ])
24
+
25
+ # Column operations
26
+ await dominus.ddl.add_column("tenant_acme", "users", "phone", "VARCHAR(20)")
27
+
28
+ # Migrations
29
+ migrations = await dominus.ddl.list_migrations("tenant_acme")
30
+
31
+ # Provisioning
32
+ await dominus.ddl.provision_tenant("acme")
33
+ await dominus.ddl.provision_from_category("acme", "healthcare")
34
+ """
35
+
36
+ def __init__(self, client: "Dominus"):
37
+ self._client = client
38
+
39
+ # ========================================
40
+ # SCHEMA DDL OPERATIONS
41
+ # ========================================
42
+
43
+ async def create_table(
44
+ self,
45
+ schema: str,
46
+ table: str,
47
+ columns: List[Dict[str, Any]]
48
+ ) -> Dict[str, Any]:
49
+ """
50
+ Create a new table.
51
+
52
+ Args:
53
+ schema: Schema name (e.g., "tenant_acme")
54
+ table: Table name
55
+ columns: List of column definitions:
56
+ [{"name": "id", "type": "UUID", "primary_key": True}]
57
+
58
+ Returns:
59
+ Creation confirmation with migration_version
60
+ """
61
+ return await self._client._request(
62
+ endpoint="/api/smith/schema/create-table",
63
+ body={
64
+ "schema": schema,
65
+ "table": table,
66
+ "columns": columns
67
+ }
68
+ )
69
+
70
+ async def drop_table(self, schema: str, table: str) -> Dict[str, Any]:
71
+ """Drop a table."""
72
+ return await self._client._request(
73
+ endpoint="/api/smith/schema/drop-table",
74
+ method="DELETE",
75
+ body={"schema": schema, "table": table}
76
+ )
77
+
78
+ async def truncate_table(self, schema: str, table: str) -> Dict[str, Any]:
79
+ """Truncate a table (delete all rows)."""
80
+ return await self._client._request(
81
+ endpoint="/api/smith/schema/truncate-table",
82
+ body={"schema": schema, "table": table}
83
+ )
84
+
85
+ # ========================================
86
+ # COLUMN OPERATIONS
87
+ # ========================================
88
+
89
+ async def add_column(
90
+ self,
91
+ schema: str,
92
+ table: str,
93
+ column: str,
94
+ column_type: str,
95
+ nullable: bool = True,
96
+ default: Optional[str] = None
97
+ ) -> Dict[str, Any]:
98
+ """
99
+ Add a column to a table.
100
+
101
+ Args:
102
+ schema: Schema name
103
+ table: Table name
104
+ column: Column name
105
+ column_type: PostgreSQL type (e.g., "VARCHAR(100)")
106
+ nullable: Allow NULL values (default: True)
107
+ default: Optional default value
108
+
109
+ Returns:
110
+ Alteration confirmation with migration_version
111
+ """
112
+ body = {
113
+ "schema": schema,
114
+ "table": table,
115
+ "column": column,
116
+ "type": column_type,
117
+ "nullable": nullable
118
+ }
119
+ if default is not None:
120
+ body["default"] = default
121
+
122
+ return await self._client._request(
123
+ endpoint="/api/smith/schema/add-column",
124
+ body=body
125
+ )
126
+
127
+ async def drop_column(
128
+ self,
129
+ schema: str,
130
+ table: str,
131
+ column: str
132
+ ) -> Dict[str, Any]:
133
+ """Drop a column from a table."""
134
+ return await self._client._request(
135
+ endpoint="/api/smith/schema/drop-column",
136
+ method="DELETE",
137
+ body={
138
+ "schema": schema,
139
+ "table": table,
140
+ "column": column
141
+ }
142
+ )
143
+
144
+ async def alter_column(
145
+ self,
146
+ schema: str,
147
+ table: str,
148
+ column: str,
149
+ new_type: Optional[str] = None,
150
+ nullable: Optional[bool] = None,
151
+ default: Optional[str] = None
152
+ ) -> Dict[str, Any]:
153
+ """
154
+ Alter a column's properties.
155
+
156
+ Args:
157
+ schema: Schema name
158
+ table: Table name
159
+ column: Column name
160
+ new_type: New column type
161
+ nullable: Set nullable status
162
+ default: New default value (use "DROP" to remove)
163
+
164
+ Returns:
165
+ Alteration confirmation
166
+ """
167
+ body = {
168
+ "schema": schema,
169
+ "table": table,
170
+ "column": column
171
+ }
172
+ if new_type:
173
+ body["type"] = new_type
174
+ if nullable is not None:
175
+ body["nullable"] = nullable
176
+ if default is not None:
177
+ body["default"] = default
178
+
179
+ return await self._client._request(
180
+ endpoint="/api/smith/schema/alter-column",
181
+ method="PUT",
182
+ body=body
183
+ )
184
+
185
+ async def rename_column(
186
+ self,
187
+ schema: str,
188
+ table: str,
189
+ old_name: str,
190
+ new_name: str
191
+ ) -> Dict[str, Any]:
192
+ """Rename a column."""
193
+ return await self._client._request(
194
+ endpoint="/api/smith/schema/rename-column",
195
+ body={
196
+ "schema": schema,
197
+ "table": table,
198
+ "old_name": old_name,
199
+ "new_name": new_name
200
+ }
201
+ )
202
+
203
+ # ========================================
204
+ # INDEX OPERATIONS
205
+ # ========================================
206
+
207
+ async def create_index(
208
+ self,
209
+ schema: str,
210
+ table: str,
211
+ index_name: str,
212
+ columns: List[str],
213
+ unique: bool = False
214
+ ) -> Dict[str, Any]:
215
+ """Create an index on columns."""
216
+ return await self._client._request(
217
+ endpoint="/api/smith/schema/create-index",
218
+ body={
219
+ "schema": schema,
220
+ "table": table,
221
+ "index_name": index_name,
222
+ "columns": columns,
223
+ "unique": unique
224
+ }
225
+ )
226
+
227
+ async def drop_index(
228
+ self,
229
+ schema: str,
230
+ index_name: str
231
+ ) -> Dict[str, Any]:
232
+ """Drop an index."""
233
+ return await self._client._request(
234
+ endpoint="/api/smith/schema/drop-index",
235
+ method="DELETE",
236
+ body={"schema": schema, "index_name": index_name}
237
+ )
238
+
239
+ # ========================================
240
+ # CONSTRAINT OPERATIONS
241
+ # ========================================
242
+
243
+ async def add_constraint(
244
+ self,
245
+ schema: str,
246
+ table: str,
247
+ constraint_name: str,
248
+ constraint_type: str,
249
+ definition: str
250
+ ) -> Dict[str, Any]:
251
+ """Add a constraint to a table."""
252
+ return await self._client._request(
253
+ endpoint="/api/smith/schema/add-constraint",
254
+ body={
255
+ "schema": schema,
256
+ "table": table,
257
+ "constraint_name": constraint_name,
258
+ "constraint_type": constraint_type,
259
+ "definition": definition
260
+ }
261
+ )
262
+
263
+ async def drop_constraint(
264
+ self,
265
+ schema: str,
266
+ table: str,
267
+ constraint_name: str
268
+ ) -> Dict[str, Any]:
269
+ """Drop a constraint from a table."""
270
+ return await self._client._request(
271
+ endpoint="/api/smith/schema/drop-constraint",
272
+ method="DELETE",
273
+ body={
274
+ "schema": schema,
275
+ "table": table,
276
+ "constraint_name": constraint_name
277
+ }
278
+ )
279
+
280
+ # ========================================
281
+ # RAW DDL (Admin Only)
282
+ # ========================================
283
+
284
+ async def execute_raw(self, schema: str, sql: str) -> Dict[str, Any]:
285
+ """
286
+ Execute raw DDL SQL (admin only).
287
+
288
+ Use with caution - this bypasses safety checks.
289
+ SQL must be base64 encoded.
290
+ """
291
+ import base64
292
+ sql_b64 = base64.b64encode(sql.encode('utf-8')).decode('utf-8')
293
+
294
+ return await self._client._request(
295
+ endpoint="/api/smith/schema/execute-raw-ddl",
296
+ body={"schema": schema, "sql": sql_b64}
297
+ )
298
+
299
+ # ========================================
300
+ # MIGRATIONS
301
+ # ========================================
302
+
303
+ async def list_migrations(self, schema: str) -> List[Dict[str, Any]]:
304
+ """List migrations for a schema."""
305
+ result = await self._client._request(
306
+ endpoint=f"/api/smith/migrations/list/{schema}",
307
+ method="GET"
308
+ )
309
+ return result if isinstance(result, list) else []
310
+
311
+ async def get_current_version(self, schema: str) -> Dict[str, Any]:
312
+ """Get current migration version for a schema."""
313
+ return await self._client._request(
314
+ endpoint=f"/api/smith/migrations/current/{schema}",
315
+ method="GET"
316
+ )
317
+
318
+ async def get_migration_file(self, schema: str, version: str) -> Dict[str, Any]:
319
+ """Get a specific migration file content."""
320
+ return await self._client._request(
321
+ endpoint=f"/api/smith/migrations/file/{schema}/{version}",
322
+ method="GET"
323
+ )
324
+
325
+ async def migration_history(self, schema: str) -> Dict[str, Any]:
326
+ """Get migration history for a schema."""
327
+ return await self._client._request(
328
+ endpoint=f"/api/smith/migrations/history/{schema}",
329
+ method="GET"
330
+ )
331
+
332
+ async def rollback_migration(self, schema: str, version: str) -> Dict[str, Any]:
333
+ """Rollback to a previous migration version."""
334
+ return await self._client._request(
335
+ endpoint=f"/api/smith/migrations/rollback/{schema}",
336
+ body={"version": version}
337
+ )
338
+
339
+ async def compare_versions(self, schemas: List[str]) -> Dict[str, Any]:
340
+ """Compare migration versions across multiple schemas."""
341
+ schemas_param = ",".join(schemas)
342
+ return await self._client._request(
343
+ endpoint=f"/api/smith/migrations/compare?schemas={schemas_param}",
344
+ method="GET"
345
+ )
346
+
347
+ # ========================================
348
+ # CATEGORY MIGRATIONS
349
+ # ========================================
350
+
351
+ async def list_category_migrations(self, category_slug: str) -> Dict[str, Any]:
352
+ """List migrations for a tenant category."""
353
+ return await self._client._request(
354
+ endpoint=f"/api/smith/migrations/category/{category_slug}/list",
355
+ method="GET"
356
+ )
357
+
358
+ async def get_category_migration_file(
359
+ self,
360
+ category_slug: str,
361
+ version: str
362
+ ) -> Dict[str, Any]:
363
+ """Get a specific category migration file."""
364
+ return await self._client._request(
365
+ endpoint=f"/api/smith/migrations/category/{category_slug}/file/{version}",
366
+ method="GET"
367
+ )
368
+
369
+ async def category_sync_status(self, category_slug: str) -> Dict[str, Any]:
370
+ """Get sync status for all tenants in a category."""
371
+ return await self._client._request(
372
+ endpoint=f"/api/smith/migrations/category/{category_slug}/sync-status",
373
+ method="GET"
374
+ )
375
+
376
+ async def list_public_migrations(self) -> Dict[str, Any]:
377
+ """List migrations for the public schema."""
378
+ return await self._client._request(
379
+ endpoint="/api/smith/migrations/public/list",
380
+ method="GET"
381
+ )
382
+
383
+ # ========================================
384
+ # CATEGORY DDL (Atomic across tenants)
385
+ # ========================================
386
+
387
+ async def category_create_table(
388
+ self,
389
+ category_slug: str,
390
+ table: str,
391
+ columns: List[Dict[str, Any]],
392
+ target: str = "category"
393
+ ) -> Dict[str, Any]:
394
+ """
395
+ Create table in all tenant schemas of a category (atomic).
396
+
397
+ Args:
398
+ category_slug: Category identifier
399
+ table: Table name
400
+ columns: Column definitions
401
+ target: "category" or "public"
402
+ """
403
+ return await self._client._request(
404
+ endpoint="/api/smith/category/create-table",
405
+ body={
406
+ "target": target,
407
+ "category_slug": category_slug,
408
+ "table": table,
409
+ "columns": columns
410
+ }
411
+ )
412
+
413
+ async def category_add_column(
414
+ self,
415
+ category_slug: str,
416
+ table: str,
417
+ column: str,
418
+ column_type: str,
419
+ nullable: bool = True,
420
+ target: str = "category"
421
+ ) -> Dict[str, Any]:
422
+ """Add column to all tenant schemas of a category (atomic)."""
423
+ return await self._client._request(
424
+ endpoint="/api/smith/category/add-column",
425
+ body={
426
+ "target": target,
427
+ "category_slug": category_slug,
428
+ "table": table,
429
+ "column": column,
430
+ "type": column_type,
431
+ "nullable": nullable
432
+ }
433
+ )
434
+
435
+ async def category_drop_table(
436
+ self,
437
+ category_slug: str,
438
+ table: str,
439
+ target: str = "category"
440
+ ) -> Dict[str, Any]:
441
+ """Drop table from all tenant schemas of a category (admin only)."""
442
+ return await self._client._request(
443
+ endpoint="/api/smith/category/drop-table",
444
+ method="DELETE",
445
+ body={
446
+ "target": target,
447
+ "category_slug": category_slug,
448
+ "table": table
449
+ }
450
+ )
451
+
452
+ async def category_drop_column(
453
+ self,
454
+ category_slug: str,
455
+ table: str,
456
+ column: str,
457
+ target: str = "category"
458
+ ) -> Dict[str, Any]:
459
+ """Drop column from all tenant schemas of a category (admin only)."""
460
+ return await self._client._request(
461
+ endpoint="/api/smith/category/drop-column",
462
+ method="DELETE",
463
+ body={
464
+ "target": target,
465
+ "category_slug": category_slug,
466
+ "table": table,
467
+ "column": column
468
+ }
469
+ )
470
+
471
+ async def category_create_index(
472
+ self,
473
+ category_slug: str,
474
+ table: str,
475
+ index_name: str,
476
+ columns: List[str],
477
+ unique: bool = False,
478
+ target: str = "category"
479
+ ) -> Dict[str, Any]:
480
+ """Create index in all tenant schemas of a category (atomic)."""
481
+ return await self._client._request(
482
+ endpoint="/api/smith/category/create-index",
483
+ body={
484
+ "target": target,
485
+ "category_slug": category_slug,
486
+ "table": table,
487
+ "index_name": index_name,
488
+ "columns": columns,
489
+ "unique": unique
490
+ }
491
+ )
492
+
493
+ # ========================================
494
+ # TENANT PROVISIONING
495
+ # ========================================
496
+
497
+ async def provision_tenant(self, tenant_slug: str) -> Dict[str, Any]:
498
+ """
499
+ Provision a new tenant schema.
500
+
501
+ Creates: tenant_{slug} schema with proper permissions.
502
+
503
+ Args:
504
+ tenant_slug: Tenant identifier (becomes schema name: tenant_{slug})
505
+
506
+ Returns:
507
+ Provisioning result with schema name and initial version
508
+ """
509
+ return await self._client._request(
510
+ endpoint="/api/smith/provision/tenant-schema",
511
+ body={"tenant_slug": tenant_slug}
512
+ )
513
+
514
+ async def deprovision_tenant(
515
+ self,
516
+ tenant_slug: str,
517
+ cascade: bool = False
518
+ ) -> Dict[str, Any]:
519
+ """
520
+ Drop a tenant schema (destructive).
521
+
522
+ Args:
523
+ tenant_slug: Tenant identifier
524
+ cascade: If True, drops all dependent objects
525
+ """
526
+ return await self._client._request(
527
+ endpoint="/api/smith/provision/tenant-schema",
528
+ method="DELETE",
529
+ body={"tenant_slug": tenant_slug, "cascade": cascade}
530
+ )
531
+
532
+ async def provision_from_category(
533
+ self,
534
+ tenant_slug: str,
535
+ category_slug: str
536
+ ) -> Dict[str, Any]:
537
+ """
538
+ Provision tenant with category-specific migrations.
539
+
540
+ Args:
541
+ tenant_slug: Tenant identifier
542
+ category_slug: Category for migrations to inherit
543
+
544
+ Returns:
545
+ Provisioning result with applied migrations
546
+ """
547
+ return await self._client._request(
548
+ endpoint="/api/smith/provision/tenant-from-category",
549
+ body={
550
+ "tenant_slug": tenant_slug,
551
+ "category_slug": category_slug
552
+ }
553
+ )
554
+
555
+ async def provision_status(self, tenant_slug: str) -> Dict[str, Any]:
556
+ """Get provision status for a tenant."""
557
+ return await self._client._request(
558
+ endpoint=f"/api/smith/provision/status/{tenant_slug}",
559
+ method="GET"
560
+ )
561
+
562
+ async def list_provisions(self) -> List[Dict[str, Any]]:
563
+ """List all provisioned tenant schemas."""
564
+ result = await self._client._request(
565
+ endpoint="/api/smith/provision/list",
566
+ method="GET"
567
+ )
568
+ return result if isinstance(result, list) else []
569
+
570
+ async def list_categories(self) -> List[Dict[str, Any]]:
571
+ """List all tenant categories."""
572
+ result = await self._client._request(
573
+ endpoint="/api/smith/provision/categories",
574
+ method="GET"
575
+ )
576
+ return result if isinstance(result, list) else []
577
+
578
+ async def category_status(self, category_slug: str) -> Dict[str, Any]:
579
+ """Get migration sync status for all tenants in a category."""
580
+ return await self._client._request(
581
+ endpoint=f"/api/smith/provision/category-status/{category_slug}",
582
+ method="GET"
583
+ )
584
+
585
+ async def tenants_by_category(self, category_slug: str) -> Dict[str, Any]:
586
+ """List all tenants in a category."""
587
+ return await self._client._request(
588
+ endpoint=f"/api/smith/provision/tenants-by-category/{category_slug}",
589
+ method="GET"
590
+ )