buildai-cli 0.3.67__tar.gz → 0.3.68__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.
Files changed (35) hide show
  1. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/.gitignore +1 -0
  2. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/PKG-INFO +1 -1
  3. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/commands/db/common.py +136 -0
  4. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/commands/db/migrate.py +19 -1
  5. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/pyproject.toml +1 -1
  6. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/AGENTS.md +0 -0
  7. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/CLAUDE.md +0 -0
  8. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/buildai_bootstrap.py +0 -0
  9. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/__init__.py +0 -0
  10. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/_has_core.py +0 -0
  11. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/auth_local.py +0 -0
  12. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/commands/__init__.py +0 -0
  13. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/commands/api_proxy.py +0 -0
  14. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/commands/auth.py +0 -0
  15. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/commands/db/__init__.py +0 -0
  16. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/commands/db/broker.py +0 -0
  17. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/commands/db/query.py +0 -0
  18. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/commands/db/schema.py +0 -0
  19. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/commands/db/status.py +0 -0
  20. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/commands/dev.py +0 -0
  21. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/commands/doctor.py +0 -0
  22. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/commands/gigcamera.py +0 -0
  23. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/commands/processing.py +0 -0
  24. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/config.py +0 -0
  25. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/console.py +0 -0
  26. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/context.py +0 -0
  27. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/db_broker.py +0 -0
  28. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/guard.py +0 -0
  29. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/internal_api.py +0 -0
  30. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/main.py +0 -0
  31. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/nl_query/__init__.py +0 -0
  32. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/nl_query/dataset_tools.py +0 -0
  33. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/ops_init.py +0 -0
  34. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/output.py +0 -0
  35. {buildai_cli-0.3.67 → buildai_cli-0.3.68}/cli/pagination.py +0 -0
@@ -54,6 +54,7 @@ Thumbs.db
54
54
  .vercel
55
55
 
56
56
  # Local tool state
57
+ .superpowers/
57
58
  .entire/
58
59
  .codex/config.toml
59
60
  .codex-bin/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: buildai-cli
3
- Version: 0.3.67
3
+ Version: 0.3.68
4
4
  Summary: Build AI CLI (Typer)
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: httpx>=0.27.0
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import re
5
6
  from dataclasses import dataclass
6
7
  from pathlib import Path
7
8
  from typing import Any
@@ -20,6 +21,11 @@ MIGRATIONS_SA_DB_USER = "buildai-migrations-sa@data-470400.iam"
20
21
  REPO_ROOT = Path(__file__).resolve().parents[5]
21
22
  MIGRATIONS_DIR = REPO_ROOT / "migrations"
22
23
  MIGRATIONS_TABLE = "public._migrations"
24
+ _NO_TRANSACTION_MARKER = "-- requires-no-transaction: true"
25
+ _CONCURRENT_DDL_PATTERN = re.compile(
26
+ r"\b(?:CREATE|DROP|REINDEX|REFRESH)\s+(?:MATERIALIZED\s+VIEW\s+)?(?:INDEX|TABLE|VIEW)?\s*CONCURRENTLY\b",
27
+ flags=re.IGNORECASE,
28
+ )
23
29
 
24
30
 
25
31
  @dataclass
@@ -140,3 +146,133 @@ def migration_requires_system_admin(path: Path) -> bool:
140
146
  """Detect migrations that must run with the DB admin connection."""
141
147
 
142
148
  return "requires-system-admin" in path.read_text(encoding="utf-8", errors="ignore")
149
+
150
+
151
+ def migration_requires_manual_apply(path: Path) -> bool:
152
+ """Detect migrations that must be explicitly selected by an operator."""
153
+
154
+ for line in path.read_text(encoding="utf-8", errors="ignore").splitlines()[:10]:
155
+ if line.strip().lower() == "-- requires-manual-apply: true":
156
+ return True
157
+ return False
158
+
159
+
160
+ def migration_requires_no_transaction(path: Path, sql: str) -> bool:
161
+ """Return whether a migration must be executed statement-by-statement."""
162
+
163
+ header = "\n".join(sql.splitlines()[:10]).lower()
164
+ if _NO_TRANSACTION_MARKER in header:
165
+ return True
166
+ return _CONCURRENT_DDL_PATTERN.search(sql) is not None
167
+
168
+
169
+ def split_sql_statements(sql: str) -> list[str]:
170
+ """Split SQL into executable statements while respecting quotes and DO blocks."""
171
+
172
+ statements: list[str] = []
173
+ chunk: list[str] = []
174
+ in_single_quote = False
175
+ in_double_quote = False
176
+ in_line_comment = False
177
+ in_block_comment = False
178
+ dollar_tag: str | None = None
179
+ i = 0
180
+
181
+ while i < len(sql):
182
+ char = sql[i]
183
+ next_char = sql[i + 1] if i + 1 < len(sql) else ""
184
+
185
+ if in_line_comment:
186
+ chunk.append(char)
187
+ if char == "\n":
188
+ in_line_comment = False
189
+ i += 1
190
+ continue
191
+
192
+ if in_block_comment:
193
+ chunk.append(char)
194
+ if char == "*" and next_char == "/":
195
+ chunk.append(next_char)
196
+ in_block_comment = False
197
+ i += 2
198
+ else:
199
+ i += 1
200
+ continue
201
+
202
+ if dollar_tag is not None:
203
+ if sql.startswith(dollar_tag, i):
204
+ chunk.append(dollar_tag)
205
+ i += len(dollar_tag)
206
+ dollar_tag = None
207
+ else:
208
+ chunk.append(char)
209
+ i += 1
210
+ continue
211
+
212
+ if in_single_quote:
213
+ chunk.append(char)
214
+ if char == "'" and next_char == "'":
215
+ chunk.append(next_char)
216
+ i += 2
217
+ continue
218
+ if char == "'":
219
+ in_single_quote = False
220
+ i += 1
221
+ continue
222
+
223
+ if in_double_quote:
224
+ chunk.append(char)
225
+ if char == '"':
226
+ in_double_quote = False
227
+ i += 1
228
+ continue
229
+
230
+ if char == "-" and next_char == "-":
231
+ chunk.append(char)
232
+ chunk.append(next_char)
233
+ in_line_comment = True
234
+ i += 2
235
+ continue
236
+
237
+ if char == "/" and next_char == "*":
238
+ chunk.append(char)
239
+ chunk.append(next_char)
240
+ in_block_comment = True
241
+ i += 2
242
+ continue
243
+
244
+ if char == "'":
245
+ chunk.append(char)
246
+ in_single_quote = True
247
+ i += 1
248
+ continue
249
+
250
+ if char == '"':
251
+ chunk.append(char)
252
+ in_double_quote = True
253
+ i += 1
254
+ continue
255
+
256
+ if char == "$":
257
+ match = re.match(r"\$[A-Za-z_][A-Za-z0-9_]*\$|\$\$", sql[i:])
258
+ if match:
259
+ dollar_tag = match.group(0)
260
+ chunk.append(dollar_tag)
261
+ i += len(dollar_tag)
262
+ continue
263
+
264
+ if char == ";":
265
+ statement = "".join(chunk).strip()
266
+ if statement:
267
+ statements.append(statement)
268
+ chunk = []
269
+ i += 1
270
+ continue
271
+
272
+ chunk.append(char)
273
+ i += 1
274
+
275
+ trailing = "".join(chunk).strip()
276
+ if trailing:
277
+ statements.append(trailing)
278
+ return statements
@@ -20,8 +20,11 @@ from .common import (
20
20
  get_migration_status,
21
21
  get_migrations_connection,
22
22
  migration_label,
23
+ migration_requires_manual_apply,
24
+ migration_requires_no_transaction,
23
25
  migration_requires_system_admin,
24
26
  set_owner_role,
27
+ split_sql_statements,
25
28
  )
26
29
 
27
30
  _MIGRATION_WRITE_PROFILES = frozenset(
@@ -125,6 +128,17 @@ def migrate(
125
128
  if target == "all"
126
129
  else [f for f in migration_files if target and target in f.name]
127
130
  )
131
+ if target == "all":
132
+ manual_apply = [f for f in to_run if migration_requires_manual_apply(f)]
133
+ if manual_apply:
134
+ warning(
135
+ "Skipping manual-apply migration(s): "
136
+ + ", ".join(item.name for item in manual_apply)
137
+ )
138
+ to_run = [f for f in to_run if f not in manual_apply]
139
+ elif any(migration_requires_manual_apply(f) for f in to_run):
140
+ warning("Selected manual-apply migration explicitly; proceeding.")
141
+
128
142
  if not to_run:
129
143
  success("No migrations to run")
130
144
  return
@@ -157,7 +171,11 @@ def migrate(
157
171
  await conn.execute("SET statement_timeout = '0'")
158
172
  else:
159
173
  await conn.execute(f"SET statement_timeout = '{timeout}s'")
160
- await conn.execute(sql, timeout=None)
174
+ if migration_requires_no_transaction(migration, sql):
175
+ for statement in split_sql_statements(sql):
176
+ await conn.execute(statement, timeout=None)
177
+ else:
178
+ await conn.execute(sql, timeout=None)
161
179
  await conn.execute(
162
180
  f"INSERT INTO {MIGRATIONS_TABLE} (filename) VALUES ($1) ON CONFLICT DO NOTHING",
163
181
  migration.name,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "buildai-cli"
7
- version = "0.3.67"
7
+ version = "0.3.68"
8
8
  description = "Build AI CLI (Typer)"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = [
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes