licos-platform-cli 0.2.7__tar.gz → 0.2.9__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.
@@ -17,9 +17,10 @@ packages/*/dist/
17
17
  .DS_Store
18
18
  Thumbs.db
19
19
 
20
- # Environment
21
- .env
22
- .env.local
20
+ # Environment
21
+ .env
22
+ .env.local
23
+ crates/industrial/industrial-stack.env
23
24
 
24
25
  # Workspace
25
26
  /workspace/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: licos-platform-cli
3
- Version: 0.2.7
3
+ Version: 0.2.9
4
4
  Summary: LICOS platform CLI
5
5
  Requires-Python: >=3.10
6
- Requires-Dist: licos-platform-sdk>=0.2.6
6
+ Requires-Dist: licos-platform-sdk>=0.2.8
@@ -4,11 +4,11 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "licos-platform-cli"
7
- version = "0.2.7"
7
+ version = "0.2.9"
8
8
  description = "LICOS platform CLI"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
11
- "licos-platform-sdk>=0.2.6",
11
+ "licos-platform-sdk>=0.2.8",
12
12
  ]
13
13
 
14
14
  [project.scripts]
@@ -0,0 +1,630 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ from pathlib import Path
7
+ from typing import Any, Callable
8
+
9
+ from licos_platform_sdk import database, studio_database
10
+
11
+
12
+ Handler = Callable[[argparse.Namespace], Any]
13
+
14
+
15
+ def _set_handler(parser: argparse.ArgumentParser, handler: Handler) -> None:
16
+ parser.set_defaults(handler=handler)
17
+
18
+
19
+ def _json_value(value: str) -> Any:
20
+ try:
21
+ return json.loads(value)
22
+ except json.JSONDecodeError:
23
+ return value
24
+
25
+
26
+ def _json_object(value: str | None) -> dict[str, Any] | None:
27
+ if not value:
28
+ return None
29
+ parsed = _json_value(value)
30
+ if not isinstance(parsed, dict):
31
+ raise argparse.ArgumentTypeError("value must be a JSON object")
32
+ return parsed
33
+
34
+
35
+ def _json_list(value: str | None) -> list[Any] | None:
36
+ if not value:
37
+ return None
38
+ parsed = _json_value(value)
39
+ if not isinstance(parsed, list):
40
+ raise argparse.ArgumentTypeError("value must be a JSON array")
41
+ return parsed
42
+
43
+
44
+ def _json_array(value: str | None) -> list[dict[str, Any]] | None:
45
+ parsed = _json_list(value)
46
+ if parsed is None:
47
+ return None
48
+ if not all(isinstance(item, dict) for item in parsed):
49
+ raise argparse.ArgumentTypeError("value must be a JSON array of objects")
50
+ return parsed
51
+
52
+
53
+ def _filters(args: argparse.Namespace) -> list[dict[str, Any]] | None:
54
+ raw_filters = getattr(args, "filter", None) or []
55
+ filters = []
56
+ for item in raw_filters:
57
+ field, op, raw_value = item
58
+ filters.append({"field": field, "op": op, "value": _json_value(raw_value)})
59
+ return filters or None
60
+
61
+
62
+ def _order_by(args: argparse.Namespace) -> list[dict[str, str]] | None:
63
+ raw_orders = getattr(args, "order", None) or []
64
+ orders = []
65
+ for item in raw_orders:
66
+ if ":" in item:
67
+ field, direction = item.rsplit(":", 1)
68
+ else:
69
+ field, direction = item, "asc"
70
+ orders.append({"field": field, "direction": direction})
71
+ return orders or None
72
+
73
+
74
+ def _range(args: argparse.Namespace) -> dict[str, int] | None:
75
+ if not getattr(args, "range", None):
76
+ return None
77
+ start, end = args.range
78
+ return {"from": start, "to": end}
79
+
80
+
81
+ def _query_options(args: argparse.Namespace) -> dict[str, Any]:
82
+ return {
83
+ "select": args.select,
84
+ "filters": _filters(args),
85
+ "order_by": _order_by(args),
86
+ "range": _range(args),
87
+ "limit": args.limit,
88
+ "offset": args.offset,
89
+ "count": args.count,
90
+ "head": args.head,
91
+ }
92
+
93
+
94
+ def _cmd_query(args: argparse.Namespace) -> Any:
95
+ return database.query(args.table, **_query_options(args))
96
+
97
+
98
+ def _cmd_single(args: argparse.Namespace) -> Any:
99
+ return database.single(
100
+ args.table,
101
+ maybe=args.maybe,
102
+ select=args.select,
103
+ filters=_filters(args),
104
+ order_by=_order_by(args),
105
+ )
106
+
107
+
108
+ def _cmd_aggregate(args: argparse.Namespace) -> Any:
109
+ return database.aggregate(
110
+ args.table,
111
+ args.aggregate,
112
+ field=args.field,
113
+ group_by=args.group_by,
114
+ filters=_filters(args),
115
+ )
116
+
117
+
118
+ def _cmd_insert(args: argparse.Namespace) -> Any:
119
+ if not args.row and not args.rows:
120
+ raise argparse.ArgumentTypeError("--row or --rows is required")
121
+ return database.insert(
122
+ args.table,
123
+ row=_json_object(args.row),
124
+ rows=_json_list(args.rows),
125
+ returning=not args.no_returning,
126
+ )
127
+
128
+
129
+ def _cmd_update(args: argparse.Namespace) -> Any:
130
+ if not args.values and not args.updates:
131
+ raise argparse.ArgumentTypeError("--values or --updates is required")
132
+ return database.update(
133
+ args.table,
134
+ values=_json_object(args.values),
135
+ filters=_filters(args),
136
+ updates=_json_list(args.updates),
137
+ returning=not args.no_returning,
138
+ )
139
+
140
+
141
+ def _cmd_delete_rows(args: argparse.Namespace) -> Any:
142
+ filters = _filters(args)
143
+ if not filters and not args.deletes:
144
+ raise argparse.ArgumentTypeError("--filter or --deletes is required")
145
+ return database.delete(
146
+ args.table,
147
+ filters=filters,
148
+ deletes=_json_list(args.deletes),
149
+ returning=not args.no_returning,
150
+ )
151
+
152
+
153
+ def _cmd_upsert(args: argparse.Namespace) -> Any:
154
+ if not args.row and not args.rows:
155
+ raise argparse.ArgumentTypeError("--row or --rows is required")
156
+ return database.upsert(
157
+ args.table,
158
+ row=_json_object(args.row),
159
+ rows=_json_list(args.rows),
160
+ conflict_keys=args.conflict_keys,
161
+ returning=not args.no_returning,
162
+ )
163
+
164
+
165
+ def _cmd_transaction(args: argparse.Namespace) -> Any:
166
+ operations = _json_list(args.operations)
167
+ if operations is None:
168
+ raise argparse.ArgumentTypeError("--operations is required")
169
+ return database.transaction(operations)
170
+
171
+
172
+ def _cmd_rpc(args: argparse.Namespace) -> Any:
173
+ return database.rpc(args.function_name, args=_json_object(args.args) or {})
174
+
175
+
176
+ def _cmd_schema(args: argparse.Namespace) -> Any:
177
+ return database.get_schema()
178
+
179
+
180
+ def _cmd_export_orm(args: argparse.Namespace) -> Any:
181
+ try:
182
+ content = database.export_orm(language=args.language, orm=args.orm)
183
+ resolved_orm = args.orm or database.default_orm(args.language)
184
+ except ValueError as exc:
185
+ raise argparse.ArgumentTypeError(str(exc)) from exc
186
+
187
+ output_path = _resolve_project_output(args.out)
188
+ output_path.parent.mkdir(parents=True, exist_ok=True)
189
+ output_path.write_text(content, encoding="utf-8")
190
+ return {
191
+ "language": args.language,
192
+ "orm": resolved_orm,
193
+ "path": str(output_path),
194
+ "bytes": len(content.encode("utf-8")),
195
+ }
196
+
197
+
198
+ def _cmd_studio_info(args: argparse.Namespace) -> Any:
199
+ return studio_database.get_database(env_scope=args.env_scope)
200
+
201
+
202
+ def _cmd_studio_ensure(args: argparse.Namespace) -> Any:
203
+ return studio_database.ensure_database(environment=args.env_scope)
204
+
205
+
206
+ def _cmd_studio_schema(args: argparse.Namespace) -> Any:
207
+ return studio_database.get_schema(environment=args.env_scope)
208
+
209
+
210
+ def _cmd_studio_config(args: argparse.Namespace) -> Any:
211
+ return studio_database.get_config(environment=args.env_scope)
212
+
213
+
214
+ def _cmd_studio_tables(args: argparse.Namespace) -> Any:
215
+ return studio_database.list_tables(env_scope=args.env_scope, keyword=args.keyword)
216
+
217
+
218
+ def _cmd_studio_create_table(args: argparse.Namespace) -> Any:
219
+ return studio_database.create_table(
220
+ args.table_name,
221
+ description=args.description,
222
+ columns=_json_array(args.columns),
223
+ env_scope=args.env_scope,
224
+ )
225
+
226
+
227
+ def _cmd_studio_update_table(args: argparse.Namespace) -> Any:
228
+ return studio_database.update_table(
229
+ args.table_name,
230
+ new_table_name=args.new_table_name,
231
+ description=args.description,
232
+ columns=_json_array(args.columns),
233
+ env_scope=args.env_scope,
234
+ schema=args.schema,
235
+ )
236
+
237
+
238
+ def _cmd_studio_delete_table(args: argparse.Namespace) -> Any:
239
+ return studio_database.delete_table(args.table_name, env_scope=args.env_scope, schema=args.schema)
240
+
241
+
242
+ def _cmd_studio_add_column(args: argparse.Namespace) -> Any:
243
+ return studio_database.add_column(
244
+ args.table_name,
245
+ name=args.name,
246
+ type=args.type,
247
+ description=args.description,
248
+ default_value=args.default_value,
249
+ array=args.array,
250
+ primary_key=args.primary_key,
251
+ nullable=args.nullable,
252
+ unique=args.unique,
253
+ check_expression=args.check_expression,
254
+ env_scope=args.env_scope,
255
+ schema=args.schema,
256
+ )
257
+
258
+
259
+ def _cmd_studio_table_data(args: argparse.Namespace) -> Any:
260
+ return studio_database.get_table_data(args.table_name, env_scope=args.env_scope, schema=args.schema)
261
+
262
+
263
+ def _cmd_studio_insert_row(args: argparse.Namespace) -> Any:
264
+ values = _json_object(args.values)
265
+ if values is None:
266
+ raise argparse.ArgumentTypeError("--values is required")
267
+ return studio_database.insert_row(args.table_name, values, env_scope=args.env_scope, schema=args.schema)
268
+
269
+
270
+ def _cmd_studio_update_row(args: argparse.Namespace) -> Any:
271
+ values = _json_object(args.values)
272
+ if values is None:
273
+ raise argparse.ArgumentTypeError("--values is required")
274
+ if not args.row_id and args.row_index is None:
275
+ raise argparse.ArgumentTypeError("--row-id or --row-index is required")
276
+ return studio_database.update_row(
277
+ args.table_name,
278
+ values,
279
+ row_id=args.row_id,
280
+ row_index=args.row_index,
281
+ env_scope=args.env_scope,
282
+ schema=args.schema,
283
+ )
284
+
285
+
286
+ def _cmd_studio_delete_rows(args: argparse.Namespace) -> Any:
287
+ if not args.row_id and args.row_index is None:
288
+ raise argparse.ArgumentTypeError("--row-id or --row-index is required")
289
+ return studio_database.delete_rows(
290
+ args.table_name,
291
+ row_ids=args.row_id,
292
+ row_indexes=args.row_index,
293
+ env_scope=args.env_scope,
294
+ schema=args.schema,
295
+ )
296
+
297
+
298
+ def _cmd_studio_sql(args: argparse.Namespace) -> Any:
299
+ return studio_database.execute_sql(args.sql_text, env_scope=args.env_scope)
300
+
301
+
302
+ def _cmd_studio_control_sql(args: argparse.Namespace) -> Any:
303
+ return studio_database.control_sql(args.sql_text, environment=args.env_scope)
304
+
305
+
306
+ def _cmd_studio_explain_sql(args: argparse.Namespace) -> Any:
307
+ return studio_database.explain_sql(args.sql_text, environment=args.env_scope)
308
+
309
+
310
+ def _cmd_studio_sql_history(args: argparse.Namespace) -> Any:
311
+ return studio_database.sql_history(env_scope=args.env_scope)
312
+
313
+
314
+ def _cmd_studio_migrations(args: argparse.Namespace) -> Any:
315
+ return studio_database.list_migrations(environment=args.env_scope)
316
+
317
+
318
+ def _cmd_studio_migration(args: argparse.Namespace) -> Any:
319
+ return studio_database.get_migration(args.migration_id)
320
+
321
+
322
+ def _cmd_studio_create_migration(args: argparse.Namespace) -> Any:
323
+ schema = _json_object(args.schema) if args.schema else None
324
+ return studio_database.create_migration(
325
+ title=args.title,
326
+ statements=args.statement or [],
327
+ schema=schema,
328
+ dry_run=not args.execute,
329
+ environment=args.env_scope,
330
+ )
331
+
332
+
333
+ def _cmd_studio_sync(args: argparse.Namespace) -> Any:
334
+ return studio_database.sync(
335
+ source_scope=args.source_scope,
336
+ target_scope=args.target_scope,
337
+ tables=args.table,
338
+ source_project_id=args.source_project_id,
339
+ target_project_id=args.target_project_id,
340
+ )
341
+
342
+
343
+ def _cmd_studio_sync_project(args: argparse.Namespace) -> Any:
344
+ return studio_database.sync_project(
345
+ args.source_project_id,
346
+ args.target_project_id,
347
+ source_scope=args.source_scope,
348
+ target_scope=args.target_scope,
349
+ tables=args.table,
350
+ )
351
+
352
+
353
+ def _cmd_studio_backup_export(args: argparse.Namespace) -> Any:
354
+ return studio_database.export_backup(env_scope=args.env_scope)
355
+
356
+
357
+ def _cmd_studio_backup_import(args: argparse.Namespace) -> Any:
358
+ if args.backup_file:
359
+ backup = json.loads(Path(args.backup_file).read_text(encoding="utf-8"))
360
+ else:
361
+ backup = _json_object(args.backup)
362
+ if backup is None:
363
+ raise argparse.ArgumentTypeError("--backup or --backup-file is required")
364
+ return studio_database.import_backup(backup, env_scope=args.env_scope)
365
+
366
+
367
+ def _resolve_project_output(value: str) -> Path:
368
+ output_path = Path(value)
369
+ project_root = os.environ.get("LICOS_PROJECT_PATH")
370
+ if not project_root:
371
+ return output_path
372
+ root = Path(project_root).resolve()
373
+ resolved = (output_path if output_path.is_absolute() else Path.cwd() / output_path).resolve()
374
+ try:
375
+ resolved.relative_to(root)
376
+ except ValueError as exc:
377
+ raise argparse.ArgumentTypeError("--out must stay inside LICOS_PROJECT_PATH") from exc
378
+ return resolved
379
+
380
+
381
+ def _add_filter_args(parser: argparse.ArgumentParser) -> None:
382
+ parser.add_argument(
383
+ "--filter",
384
+ nargs=3,
385
+ action="append",
386
+ metavar=("FIELD", "OP", "VALUE"),
387
+ help="Add a filter. VALUE is parsed as JSON when possible.",
388
+ )
389
+
390
+
391
+ def _add_query_args(parser: argparse.ArgumentParser) -> None:
392
+ parser.add_argument("--select", default=None, help="Comma-separated fields")
393
+ _add_filter_args(parser)
394
+ parser.add_argument("--order", action="append", default=[], help="Order by field or field:desc")
395
+ parser.add_argument("--range", nargs=2, type=int, metavar=("FROM", "TO"))
396
+ parser.add_argument("--limit", type=int, default=None)
397
+ parser.add_argument("--offset", type=int, default=None)
398
+ parser.add_argument("--count", action="store_true")
399
+ parser.add_argument("--head", action="store_true")
400
+
401
+
402
+ def _add_env_scope_arg(parser: argparse.ArgumentParser, *, default: str | None = None) -> None:
403
+ parser.add_argument("--env-scope", choices=["dev", "prod"], default=default, help="Studio database environment scope")
404
+
405
+
406
+ def _add_schema_arg(parser: argparse.ArgumentParser) -> None:
407
+ parser.add_argument("--schema", default=None, help="Schema name, defaults to public")
408
+
409
+
410
+ def add_database_parser(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) -> None:
411
+ database_parser = subparsers.add_parser("database", help="Call LICOS runtime database APIs")
412
+ database_subparsers = database_parser.add_subparsers(dest="database_command", required=True)
413
+
414
+ query = database_subparsers.add_parser("query", help="Query rows")
415
+ query.add_argument("table")
416
+ _add_query_args(query)
417
+ _set_handler(query, _cmd_query)
418
+
419
+ single = database_subparsers.add_parser("single", help="Query a single row")
420
+ single.add_argument("table")
421
+ single.add_argument("--maybe", action="store_true", help="Allow zero rows")
422
+ single.add_argument("--select", default=None, help="Comma-separated fields")
423
+ _add_filter_args(single)
424
+ single.add_argument("--order", action="append", default=[], help="Order by field or field:desc")
425
+ _set_handler(single, _cmd_single)
426
+
427
+ aggregate = database_subparsers.add_parser("aggregate", help="Run aggregate query")
428
+ aggregate.add_argument("table")
429
+ aggregate.add_argument("aggregate")
430
+ aggregate.add_argument("--field", default=None)
431
+ aggregate.add_argument("--group-by", action="append", default=None)
432
+ _add_filter_args(aggregate)
433
+ _set_handler(aggregate, _cmd_aggregate)
434
+
435
+ insert = database_subparsers.add_parser("insert", help="Insert row(s)")
436
+ insert.add_argument("table")
437
+ insert.add_argument("--row", default=None, help="JSON object")
438
+ insert.add_argument("--rows", default=None, help="JSON array")
439
+ insert.add_argument("--no-returning", action="store_true")
440
+ _set_handler(insert, _cmd_insert)
441
+
442
+ update = database_subparsers.add_parser("update", help="Update row(s)")
443
+ update.add_argument("table")
444
+ update.add_argument("--values", default=None, help="JSON object")
445
+ update.add_argument("--updates", default=None, help="JSON array of mutation batches")
446
+ update.add_argument("--no-returning", action="store_true")
447
+ _add_filter_args(update)
448
+ _set_handler(update, _cmd_update)
449
+
450
+ delete_rows = database_subparsers.add_parser("delete-rows", help="Delete row(s)")
451
+ delete_rows.add_argument("table")
452
+ delete_rows.add_argument("--deletes", default=None, help="JSON array of mutation batches")
453
+ delete_rows.add_argument("--no-returning", action="store_true")
454
+ _add_filter_args(delete_rows)
455
+ _set_handler(delete_rows, _cmd_delete_rows)
456
+
457
+ upsert = database_subparsers.add_parser("upsert", help="Upsert row(s)")
458
+ upsert.add_argument("table")
459
+ upsert.add_argument("--row", default=None, help="JSON object")
460
+ upsert.add_argument("--rows", default=None, help="JSON array")
461
+ upsert.add_argument("--conflict-keys", action="append", default=None)
462
+ upsert.add_argument("--no-returning", action="store_true")
463
+ _set_handler(upsert, _cmd_upsert)
464
+
465
+ transaction = database_subparsers.add_parser("transaction", help="Run transaction")
466
+ transaction.add_argument("--operations", required=True, help="JSON array")
467
+ _set_handler(transaction, _cmd_transaction)
468
+
469
+ rpc = database_subparsers.add_parser("rpc", help="Call database function")
470
+ rpc.add_argument("function_name")
471
+ rpc.add_argument("--args", default=None, help="JSON object")
472
+ _set_handler(rpc, _cmd_rpc)
473
+
474
+ schema = database_subparsers.add_parser("schema", help="Fetch database schema metadata")
475
+ _set_handler(schema, _cmd_schema)
476
+
477
+ export_orm = database_subparsers.add_parser("export-orm", help="Export ORM source from database schema metadata")
478
+ export_orm.add_argument("--language", required=True, choices=["typescript", "python"])
479
+ export_orm.add_argument("--orm", choices=["drizzle", "sqlalchemy"], default=None)
480
+ export_orm.add_argument("--out", required=True, help="Output source file path")
481
+ _set_handler(export_orm, _cmd_export_orm)
482
+
483
+ studio_info = database_subparsers.add_parser("studio-info", help="Fetch Studio project database metadata")
484
+ _add_env_scope_arg(studio_info)
485
+ _set_handler(studio_info, _cmd_studio_info)
486
+
487
+ studio_ensure = database_subparsers.add_parser("studio-ensure", help="Open or confirm the Studio project database")
488
+ _add_env_scope_arg(studio_ensure)
489
+ _set_handler(studio_ensure, _cmd_studio_ensure)
490
+
491
+ studio_schema = database_subparsers.add_parser("studio-schema", help="Fetch complete Studio database schema")
492
+ _add_env_scope_arg(studio_schema)
493
+ _set_handler(studio_schema, _cmd_studio_schema)
494
+
495
+ studio_config = database_subparsers.add_parser("studio-config", help="Fetch Studio database capability config")
496
+ _add_env_scope_arg(studio_config)
497
+ _set_handler(studio_config, _cmd_studio_config)
498
+
499
+ studio_tables = database_subparsers.add_parser("studio-tables", help="List Studio database tables")
500
+ _add_env_scope_arg(studio_tables)
501
+ studio_tables.add_argument("--keyword", default=None, help="Filter by table name keyword")
502
+ _set_handler(studio_tables, _cmd_studio_tables)
503
+
504
+ studio_create_table = database_subparsers.add_parser("studio-create-table", help="Create a Studio database table")
505
+ studio_create_table.add_argument("table_name")
506
+ studio_create_table.add_argument("--description", default=None)
507
+ studio_create_table.add_argument("--columns", default=None, help="JSON array of column definitions")
508
+ _add_env_scope_arg(studio_create_table)
509
+ _set_handler(studio_create_table, _cmd_studio_create_table)
510
+
511
+ studio_update_table = database_subparsers.add_parser("studio-update-table", help="Update a Studio database table")
512
+ studio_update_table.add_argument("table_name")
513
+ studio_update_table.add_argument("--new-table-name", default=None)
514
+ studio_update_table.add_argument("--description", default=None)
515
+ studio_update_table.add_argument("--columns", default=None, help="JSON array of final column definitions")
516
+ _add_env_scope_arg(studio_update_table)
517
+ _add_schema_arg(studio_update_table)
518
+ _set_handler(studio_update_table, _cmd_studio_update_table)
519
+
520
+ studio_delete_table = database_subparsers.add_parser("studio-delete-table", help="Delete a Studio database table")
521
+ studio_delete_table.add_argument("table_name")
522
+ _add_env_scope_arg(studio_delete_table)
523
+ _add_schema_arg(studio_delete_table)
524
+ _set_handler(studio_delete_table, _cmd_studio_delete_table)
525
+
526
+ studio_add_column = database_subparsers.add_parser("studio-add-column", help="Add a column to a Studio database table")
527
+ studio_add_column.add_argument("table_name")
528
+ studio_add_column.add_argument("name")
529
+ studio_add_column.add_argument("type")
530
+ studio_add_column.add_argument("--description", default=None)
531
+ studio_add_column.add_argument("--default-value", default=None)
532
+ studio_add_column.add_argument("--array", action="store_true", default=None)
533
+ studio_add_column.add_argument("--primary-key", action="store_true", default=None)
534
+ studio_add_column.add_argument("--nullable", action="store_true", default=None)
535
+ studio_add_column.add_argument("--unique", action="store_true", default=None)
536
+ studio_add_column.add_argument("--check-expression", default=None)
537
+ _add_env_scope_arg(studio_add_column)
538
+ _add_schema_arg(studio_add_column)
539
+ _set_handler(studio_add_column, _cmd_studio_add_column)
540
+
541
+ studio_table_data = database_subparsers.add_parser("studio-table-data", help="Fetch Studio database table data")
542
+ studio_table_data.add_argument("table_name")
543
+ _add_env_scope_arg(studio_table_data)
544
+ _add_schema_arg(studio_table_data)
545
+ _set_handler(studio_table_data, _cmd_studio_table_data)
546
+
547
+ studio_insert_row = database_subparsers.add_parser("studio-insert-row", help="Insert a Studio database table row")
548
+ studio_insert_row.add_argument("table_name")
549
+ studio_insert_row.add_argument("--values", required=True, help="JSON object of column values")
550
+ _add_env_scope_arg(studio_insert_row)
551
+ _add_schema_arg(studio_insert_row)
552
+ _set_handler(studio_insert_row, _cmd_studio_insert_row)
553
+
554
+ studio_update_row = database_subparsers.add_parser("studio-update-row", help="Update a Studio database table row")
555
+ studio_update_row.add_argument("table_name")
556
+ studio_update_row.add_argument("--values", required=True, help="JSON object of column values")
557
+ studio_update_row.add_argument("--row-id", default=None)
558
+ studio_update_row.add_argument("--row-index", type=int, default=None)
559
+ _add_env_scope_arg(studio_update_row)
560
+ _add_schema_arg(studio_update_row)
561
+ _set_handler(studio_update_row, _cmd_studio_update_row)
562
+
563
+ studio_delete_rows = database_subparsers.add_parser("studio-delete-rows", help="Delete Studio database table rows")
564
+ studio_delete_rows.add_argument("table_name")
565
+ studio_delete_rows.add_argument("--row-id", action="append", default=None)
566
+ studio_delete_rows.add_argument("--row-index", action="append", type=int, default=None)
567
+ _add_env_scope_arg(studio_delete_rows)
568
+ _add_schema_arg(studio_delete_rows)
569
+ _set_handler(studio_delete_rows, _cmd_studio_delete_rows)
570
+
571
+ studio_sql = database_subparsers.add_parser("studio-sql", help="Execute Studio database SQL through the managed Studio endpoint")
572
+ studio_sql.add_argument("sql_text")
573
+ _add_env_scope_arg(studio_sql)
574
+ _set_handler(studio_sql, _cmd_studio_sql)
575
+
576
+ studio_control_sql = database_subparsers.add_parser("studio-control-sql", help="Execute controlled Studio database SQL")
577
+ studio_control_sql.add_argument("sql_text")
578
+ _add_env_scope_arg(studio_control_sql)
579
+ _set_handler(studio_control_sql, _cmd_studio_control_sql)
580
+
581
+ studio_explain_sql = database_subparsers.add_parser("studio-explain-sql", help="Explain a Studio database SQL query")
582
+ studio_explain_sql.add_argument("sql_text")
583
+ _add_env_scope_arg(studio_explain_sql)
584
+ _set_handler(studio_explain_sql, _cmd_studio_explain_sql)
585
+
586
+ studio_sql_history = database_subparsers.add_parser("studio-sql-history", help="List Studio database SQL history")
587
+ _add_env_scope_arg(studio_sql_history)
588
+ _set_handler(studio_sql_history, _cmd_studio_sql_history)
589
+
590
+ studio_migrations = database_subparsers.add_parser("studio-migrations", help="List Studio database migration history")
591
+ _add_env_scope_arg(studio_migrations)
592
+ _set_handler(studio_migrations, _cmd_studio_migrations)
593
+
594
+ studio_migration = database_subparsers.add_parser("studio-migration", help="Fetch a Studio database migration")
595
+ studio_migration.add_argument("migration_id")
596
+ _set_handler(studio_migration, _cmd_studio_migration)
597
+
598
+ studio_create_migration = database_subparsers.add_parser("studio-create-migration", help="Create a Studio database migration task")
599
+ studio_create_migration.add_argument("--title", required=True)
600
+ studio_create_migration.add_argument("--statement", action="append", default=None, help="SQL statement. Can be repeated.")
601
+ studio_create_migration.add_argument("--schema", default=None, help="Target schema JSON object")
602
+ studio_create_migration.add_argument("--execute", action="store_true", help="Run migration instead of dry-run")
603
+ _add_env_scope_arg(studio_create_migration)
604
+ _set_handler(studio_create_migration, _cmd_studio_create_migration)
605
+
606
+ studio_sync = database_subparsers.add_parser("studio-sync", help="Sync current Studio project database between environments")
607
+ studio_sync.add_argument("--source-scope", choices=["dev", "prod"], default="dev")
608
+ studio_sync.add_argument("--target-scope", choices=["dev", "prod"], default="prod")
609
+ studio_sync.add_argument("--table", action="append", default=None, help="schema.table to sync. Can be repeated.")
610
+ studio_sync.add_argument("--source-project-id", default=None)
611
+ studio_sync.add_argument("--target-project-id", default=None)
612
+ _set_handler(studio_sync, _cmd_studio_sync)
613
+
614
+ studio_sync_project = database_subparsers.add_parser("studio-sync-project", help="Sync Studio database across projects")
615
+ studio_sync_project.add_argument("source_project_id")
616
+ studio_sync_project.add_argument("target_project_id")
617
+ studio_sync_project.add_argument("--source-scope", choices=["dev", "prod"], default="dev")
618
+ studio_sync_project.add_argument("--target-scope", choices=["dev", "prod"], default="prod")
619
+ studio_sync_project.add_argument("--table", action="append", default=None, help="schema.table to sync. Can be repeated.")
620
+ _set_handler(studio_sync_project, _cmd_studio_sync_project)
621
+
622
+ studio_backup_export = database_subparsers.add_parser("studio-backup-export", help="Export Studio production database backup")
623
+ _add_env_scope_arg(studio_backup_export, default="prod")
624
+ _set_handler(studio_backup_export, _cmd_studio_backup_export)
625
+
626
+ studio_backup_import = database_subparsers.add_parser("studio-backup-import", help="Import Studio production database backup")
627
+ studio_backup_import.add_argument("--backup", default=None, help="Backup JSON object")
628
+ studio_backup_import.add_argument("--backup-file", default=None, help="Path to backup JSON file")
629
+ _add_env_scope_arg(studio_backup_import, default="prod")
630
+ _set_handler(studio_backup_import, _cmd_studio_backup_import)