velocity-python 0.0.138__py3-none-any.whl → 0.0.152__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.

Potentially problematic release.


This version of velocity-python might be problematic. Click here for more details.

@@ -18,6 +18,18 @@ TableHelper.reserved = reserved_words
18
18
  TableHelper.operators = OPERATORS
19
19
 
20
20
 
21
+ system_fields = [
22
+ "sys_id",
23
+ "sys_created",
24
+ "sys_modified",
25
+ "sys_modified_by",
26
+ "sys_dirty",
27
+ "sys_table",
28
+ "sys_modified_count",
29
+ "description",
30
+ ]
31
+
32
+
21
33
  def quote(data):
22
34
  """Quote SQLite identifiers."""
23
35
  if isinstance(data, list):
@@ -344,11 +356,122 @@ class SQL(BaseSQLDialect):
344
356
 
345
357
  @classmethod
346
358
  def create_table(cls, name, columns=None, drop=False):
359
+ if not name or not isinstance(name, str):
360
+ raise ValueError("Table name must be a non-empty string")
361
+
362
+ columns = columns or {}
363
+ table_identifier = quote(name)
364
+ base_name = name.split(".")[-1].replace('"', "")
365
+ base_name_sql = base_name.replace("'", "''")
366
+ trigger_prefix = re.sub(r"[^0-9A-Za-z_]+", "_", f"cc_sysmod_{base_name}")
367
+
368
+ statements = []
347
369
  if drop:
348
- return f"DROP TABLE IF EXISTS {quote(name)}"
349
-
350
- # Basic CREATE TABLE
351
- return f"CREATE TABLE {quote(name)} (id INTEGER PRIMARY KEY AUTOINCREMENT)"
370
+ statements.append(f"DROP TABLE IF EXISTS {table_identifier};")
371
+
372
+ statements.append(
373
+ f"""
374
+ CREATE TABLE {table_identifier} (
375
+ "sys_id" INTEGER PRIMARY KEY AUTOINCREMENT,
376
+ "sys_table" TEXT,
377
+ "sys_created" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
378
+ "sys_modified" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
379
+ "sys_modified_by" TEXT,
380
+ "sys_modified_count" INTEGER NOT NULL DEFAULT 0,
381
+ "sys_dirty" INTEGER NOT NULL DEFAULT 0,
382
+ "description" TEXT
383
+ );
384
+ """.strip()
385
+ )
386
+
387
+ for key, val in columns.items():
388
+ clean_key = re.sub("<>!=%", "", key)
389
+ if clean_key in system_fields:
390
+ continue
391
+ col_type = TYPES.get_type(val)
392
+ statements.append(
393
+ f"ALTER TABLE {table_identifier} ADD COLUMN {quote(clean_key)} {col_type};"
394
+ )
395
+
396
+ statements.extend(
397
+ [
398
+ f"DROP TRIGGER IF EXISTS {trigger_prefix}_ai;",
399
+ f"DROP TRIGGER IF EXISTS {trigger_prefix}_au;",
400
+ f"""
401
+ CREATE TRIGGER {trigger_prefix}_ai
402
+ AFTER INSERT ON {table_identifier}
403
+ FOR EACH ROW
404
+ BEGIN
405
+ UPDATE {table_identifier}
406
+ SET sys_created = COALESCE(NEW.sys_created, CURRENT_TIMESTAMP),
407
+ sys_modified = CURRENT_TIMESTAMP,
408
+ sys_modified_count = 0,
409
+ sys_dirty = COALESCE(NEW.sys_dirty, 0),
410
+ sys_table = '{base_name_sql}'
411
+ WHERE rowid = NEW.rowid;
412
+ END;
413
+ """.strip(),
414
+ f"""
415
+ CREATE TRIGGER {trigger_prefix}_au
416
+ AFTER UPDATE ON {table_identifier}
417
+ FOR EACH ROW
418
+ BEGIN
419
+ UPDATE {table_identifier}
420
+ SET sys_created = OLD.sys_created,
421
+ sys_modified = CURRENT_TIMESTAMP,
422
+ sys_table = '{base_name_sql}',
423
+ sys_dirty = CASE WHEN OLD.sys_dirty = 1 AND NEW.sys_dirty = 0 THEN 0 ELSE 1 END,
424
+ sys_modified_count = CASE WHEN OLD.sys_dirty = 1 AND NEW.sys_dirty = 0 THEN COALESCE(OLD.sys_modified_count, 0) ELSE COALESCE(OLD.sys_modified_count, 0) + 1 END
425
+ WHERE rowid = NEW.rowid;
426
+ END;
427
+ """.strip(),
428
+ ]
429
+ )
430
+
431
+ return "\n".join(statements), tuple()
432
+
433
+ @classmethod
434
+ def ensure_sys_modified_count(cls, name, has_column=False, has_row_column=False):
435
+ """Ensure sys_modified_count exists for SQLite tables."""
436
+ table_identifier = quote(name)
437
+ base_name = name.split(".")[-1].replace('"', "")
438
+ base_name_sql = base_name.replace("'", "''")
439
+ trigger_prefix = re.sub(r"[^0-9A-Za-z_]+", "_", f"cc_sysmod_{base_name}")
440
+ statements = [
441
+ f"ALTER TABLE {table_identifier} ADD COLUMN sys_modified_count INTEGER NOT NULL DEFAULT 0;",
442
+ f"UPDATE {table_identifier} SET sys_modified_count = 0 WHERE sys_modified_count IS NULL;",
443
+ f"DROP TRIGGER IF EXISTS {trigger_prefix}_ai;",
444
+ f"DROP TRIGGER IF EXISTS {trigger_prefix}_au;",
445
+ f"""
446
+ CREATE TRIGGER {trigger_prefix}_ai
447
+ AFTER INSERT ON {table_identifier}
448
+ FOR EACH ROW
449
+ BEGIN
450
+ UPDATE {table_identifier}
451
+ SET sys_created = COALESCE(NEW.sys_created, CURRENT_TIMESTAMP),
452
+ sys_modified = CURRENT_TIMESTAMP,
453
+ sys_modified_count = 0,
454
+ sys_dirty = COALESCE(NEW.sys_dirty, 0),
455
+ sys_table = '{base_name_sql}'
456
+ WHERE rowid = NEW.rowid;
457
+ END;
458
+ """.strip(),
459
+ f"""
460
+ CREATE TRIGGER {trigger_prefix}_au
461
+ AFTER UPDATE ON {table_identifier}
462
+ FOR EACH ROW
463
+ BEGIN
464
+ UPDATE {table_identifier}
465
+ SET sys_created = OLD.sys_created,
466
+ sys_modified = CURRENT_TIMESTAMP,
467
+ sys_table = '{base_name_sql}',
468
+ sys_dirty = CASE WHEN OLD.sys_dirty = 1 AND NEW.sys_dirty = 0 THEN 0 ELSE 1 END,
469
+ sys_modified_count = CASE WHEN OLD.sys_dirty = 1 AND NEW.sys_dirty = 0 THEN COALESCE(OLD.sys_modified_count, 0) ELSE COALESCE(OLD.sys_modified_count, 0) + 1 END
470
+ WHERE rowid = NEW.rowid;
471
+ END;
472
+ """.strip(),
473
+ ]
474
+ return "\n".join(statements), tuple()
352
475
 
353
476
  @classmethod
354
477
  def drop_table(cls, name):
@@ -18,6 +18,18 @@ TableHelper.reserved = reserved_words
18
18
  TableHelper.operators = OPERATORS
19
19
 
20
20
 
21
+ system_fields = [
22
+ "sys_id",
23
+ "sys_created",
24
+ "sys_modified",
25
+ "sys_modified_by",
26
+ "sys_dirty",
27
+ "sys_table",
28
+ "sys_modified_count",
29
+ "description",
30
+ ]
31
+
32
+
21
33
  def quote(data):
22
34
  """Quote SQL Server identifiers."""
23
35
  if isinstance(data, list):
@@ -383,11 +395,160 @@ class SQL(BaseSQLDialect):
383
395
 
384
396
  @classmethod
385
397
  def create_table(cls, name, columns=None, drop=False):
398
+ if not name or not isinstance(name, str):
399
+ raise ValueError("Table name must be a non-empty string")
400
+
401
+ columns = columns or {}
402
+
403
+ if "." in name:
404
+ schema_part, table_part = name.split(".", 1)
405
+ else:
406
+ schema_part = cls.default_schema or "dbo"
407
+ table_part = name
408
+
409
+ schema_identifier = quote(schema_part)
410
+ table_identifier = quote(name if "." in name else f"{schema_part}.{table_part}")
411
+ base_name = table_part.replace("[", "").replace("]", "")
412
+ base_name_sql = base_name.replace("'", "''")
413
+ trigger_prefix = re.sub(r"[^0-9A-Za-z_]+", "_", f"CC_SYS_MOD_{base_name}")
414
+
415
+ statements = []
386
416
  if drop:
387
- return f"DROP TABLE IF EXISTS {quote(name)}"
388
-
389
- # Basic CREATE TABLE
390
- return f"CREATE TABLE {quote(name)} (id INT IDENTITY(1,1) PRIMARY KEY)"
417
+ statements.append(f"IF OBJECT_ID(N'{table_identifier}', N'U') IS NOT NULL DROP TABLE {table_identifier};")
418
+
419
+ statements.append(
420
+ f"""
421
+ CREATE TABLE {table_identifier} (
422
+ [sys_id] BIGINT IDENTITY(1,1) PRIMARY KEY,
423
+ [sys_table] NVARCHAR(255),
424
+ [sys_created] DATETIME2 NOT NULL DEFAULT SYSDATETIME(),
425
+ [sys_modified] DATETIME2 NOT NULL DEFAULT SYSDATETIME(),
426
+ [sys_modified_by] NVARCHAR(255),
427
+ [sys_modified_count] INT NOT NULL DEFAULT 0,
428
+ [sys_dirty] BIT NOT NULL DEFAULT 0,
429
+ [description] NVARCHAR(MAX)
430
+ );
431
+ """.strip()
432
+ )
433
+
434
+ for key, val in columns.items():
435
+ clean_key = re.sub("<>!=%", "", key)
436
+ if clean_key in system_fields:
437
+ continue
438
+ col_type = TYPES.get_type(val)
439
+ statements.append(
440
+ f"ALTER TABLE {table_identifier} ADD {quote(clean_key)} {col_type};"
441
+ )
442
+
443
+ statements.extend(
444
+ [
445
+ f"IF OBJECT_ID(N'{schema_identifier}.{trigger_prefix}_insert', N'TR') IS NOT NULL DROP TRIGGER {schema_identifier}.{trigger_prefix}_insert;",
446
+ f"IF OBJECT_ID(N'{schema_identifier}.{trigger_prefix}_update', N'TR') IS NOT NULL DROP TRIGGER {schema_identifier}.{trigger_prefix}_update;",
447
+ f"""
448
+ CREATE TRIGGER {schema_identifier}.{trigger_prefix}_insert
449
+ ON {table_identifier}
450
+ AFTER INSERT
451
+ AS
452
+ BEGIN
453
+ SET NOCOUNT ON;
454
+ UPDATE t
455
+ SET sys_created = ISNULL(i.sys_created, SYSDATETIME()),
456
+ sys_modified = SYSDATETIME(),
457
+ sys_modified_count = 0,
458
+ sys_dirty = ISNULL(i.sys_dirty, 0),
459
+ sys_table = '{base_name_sql}'
460
+ FROM {table_identifier} AS t
461
+ INNER JOIN inserted AS i ON t.sys_id = i.sys_id;
462
+ END;
463
+ """.strip(),
464
+ f"""
465
+ CREATE TRIGGER {schema_identifier}.{trigger_prefix}_update
466
+ ON {table_identifier}
467
+ AFTER UPDATE
468
+ AS
469
+ BEGIN
470
+ SET NOCOUNT ON;
471
+ UPDATE t
472
+ SET sys_created = d.sys_created,
473
+ sys_modified = SYSDATETIME(),
474
+ sys_table = '{base_name_sql}',
475
+ sys_dirty = CASE WHEN d.sys_dirty = 1 AND i.sys_dirty = 0 THEN 0 ELSE 1 END,
476
+ sys_modified_count = CASE WHEN d.sys_dirty = 1 AND i.sys_dirty = 0 THEN ISNULL(d.sys_modified_count, 0) ELSE ISNULL(d.sys_modified_count, 0) + 1 END
477
+ FROM {table_identifier} AS t
478
+ INNER JOIN inserted AS i ON t.sys_id = i.sys_id
479
+ INNER JOIN deleted AS d ON d.sys_id = i.sys_id;
480
+ END;
481
+ """.strip(),
482
+ ]
483
+ )
484
+
485
+ return "\n".join(statements), tuple()
486
+
487
+ @classmethod
488
+ def ensure_sys_modified_count(
489
+ cls, name, has_column=False, has_row_column=False
490
+ ):
491
+ """Ensure sys_modified_count exists for SQL Server tables along with maintenance triggers."""
492
+ if "." in name:
493
+ schema, table_name = name.split(".", 1)
494
+ else:
495
+ schema = cls.default_schema or "dbo"
496
+ table_name = name
497
+
498
+ schema_identifier = quote(schema)
499
+ table_identifier = quote(name if "." in name else f"{schema}.{table_name}")
500
+ object_name = f"[{schema}].[{table_name}]"
501
+ table_name_sql = table_name.replace("'", "''")
502
+ trigger_prefix = re.sub(r"[^0-9A-Za-z_]+", "_", f"CC_SYS_MOD_{table_name}")
503
+
504
+ statements = []
505
+ if not has_column:
506
+ statements.append(
507
+ f"IF COL_LENGTH(N'{object_name}', 'sys_modified_count') IS NULL BEGIN ALTER TABLE {table_identifier} ADD sys_modified_count INT NOT NULL CONSTRAINT DF_{trigger_prefix}_COUNT DEFAULT (0); END;"
508
+ )
509
+
510
+ statements.extend([
511
+ f"UPDATE {table_identifier} SET sys_modified_count = 0 WHERE sys_modified_count IS NULL;",
512
+ f"IF OBJECT_ID(N'{schema_identifier}.{trigger_prefix}_insert', N'TR') IS NOT NULL DROP TRIGGER {schema_identifier}.{trigger_prefix}_insert;",
513
+ f"IF OBJECT_ID(N'{schema_identifier}.{trigger_prefix}_update', N'TR') IS NOT NULL DROP TRIGGER {schema_identifier}.{trigger_prefix}_update;",
514
+ f"""
515
+ CREATE TRIGGER {schema_identifier}.{trigger_prefix}_insert
516
+ ON {table_identifier}
517
+ AFTER INSERT
518
+ AS
519
+ BEGIN
520
+ SET NOCOUNT ON;
521
+ UPDATE t
522
+ SET sys_created = ISNULL(i.sys_created, SYSDATETIME()),
523
+ sys_modified = SYSDATETIME(),
524
+ sys_modified_count = 0,
525
+ sys_dirty = ISNULL(i.sys_dirty, 0),
526
+ sys_table = '{table_name_sql}'
527
+ FROM {table_identifier} AS t
528
+ INNER JOIN inserted AS i ON t.sys_id = i.sys_id;
529
+ END;
530
+ """.strip(),
531
+ f"""
532
+ CREATE TRIGGER {schema_identifier}.{trigger_prefix}_update
533
+ ON {table_identifier}
534
+ AFTER UPDATE
535
+ AS
536
+ BEGIN
537
+ SET NOCOUNT ON;
538
+ UPDATE t
539
+ SET sys_created = d.sys_created,
540
+ sys_modified = SYSDATETIME(),
541
+ sys_table = '{table_name_sql}',
542
+ sys_dirty = CASE WHEN d.sys_dirty = 1 AND i.sys_dirty = 0 THEN 0 ELSE 1 END,
543
+ sys_modified_count = CASE WHEN d.sys_dirty = 1 AND i.sys_dirty = 0 THEN ISNULL(d.sys_modified_count, 0) ELSE ISNULL(d.sys_modified_count, 0) + 1 END
544
+ FROM {table_identifier} AS t
545
+ INNER JOIN inserted AS i ON t.sys_id = i.sys_id
546
+ INNER JOIN deleted AS d ON d.sys_id = i.sys_id;
547
+ END;
548
+ """.strip(),
549
+ ])
550
+
551
+ return "\n".join(statements), tuple()
391
552
 
392
553
  @classmethod
393
554
  def drop_table(cls, name):
@@ -1,8 +1,11 @@
1
1
  import unittest
2
2
  import decimal
3
+ from types import SimpleNamespace
4
+ from unittest import mock
3
5
  from velocity.db.servers.postgres.sql import SQL
4
6
  from velocity.db.servers.tablehelper import TableHelper
5
7
  from velocity.db.servers.postgres.types import TYPES
8
+ from velocity.db.core.table import Table
6
9
 
7
10
 
8
11
  class MockTx:
@@ -21,6 +24,51 @@ class MockTable:
21
24
  def column(self, column_name):
22
25
  return MockColumn()
23
26
 
27
+ def primary_keys(self):
28
+ return ["id"]
29
+
30
+
31
+ class DummyCursor:
32
+ def __init__(self, rowcount=0):
33
+ self.rowcount = rowcount
34
+
35
+
36
+ class DummyResult:
37
+ def __init__(self, rowcount=0):
38
+ self.cursor = DummyCursor(rowcount)
39
+
40
+
41
+ class DummyTx:
42
+ def __init__(self):
43
+ self.engine = SimpleNamespace(sql=SimpleNamespace(), schema_locked=False)
44
+ self.executed = []
45
+ self.next_results = []
46
+
47
+ def cursor(self):
48
+ return DummyCursor()
49
+
50
+ def create_savepoint(self, cursor=None):
51
+ sp_id = f"sp_{len(self.executed)}"
52
+ return sp_id
53
+
54
+ def release_savepoint(self, sp, cursor=None):
55
+ return None
56
+
57
+ def rollback_savepoint(self, sp, cursor=None):
58
+ return None
59
+
60
+ def execute(self, sql, params, cursor=None):
61
+ self.executed.append((sql, params))
62
+ if self.next_results:
63
+ return self.next_results.pop(0)
64
+ return DummyResult(0)
65
+
66
+ def table(self, table_name):
67
+ return MockTable()
68
+
69
+ def primary_keys(self):
70
+ return ["id"]
71
+
24
72
  class MockColumn:
25
73
  def __init__(self):
26
74
  self.py_type = str
@@ -218,6 +266,147 @@ class TestSQLModule(unittest.TestCase):
218
266
  self.assertIn("SET", sql_query)
219
267
  self.assertEqual(params, ("value1", 1))
220
268
 
269
+ def test_sql_insnx_with_explicit_where(self):
270
+ mock_tx = MockTx()
271
+ sql_query, params = SQL.insnx(
272
+ mock_tx,
273
+ table="my_table",
274
+ data={"id": 1, "column1": "value1"},
275
+ where={"column1": "value1"},
276
+ )
277
+ self.assertIn("INSERT INTO", sql_query)
278
+ self.assertIn("WHERE NOT EXISTS", sql_query)
279
+ self.assertIn("SELECT 1 FROM my_table", sql_query)
280
+ self.assertEqual(params, (1, "value1", "value1"))
281
+
282
+ def test_sql_insert_if_not_exists_alias(self):
283
+ mock_tx = MockTx()
284
+ sql_alias, params_alias = SQL.insert_if_not_exists(
285
+ mock_tx,
286
+ table="my_table",
287
+ data={"id": 1, "column1": "value1"},
288
+ where={"column1": "value1"},
289
+ )
290
+ sql_main, params_main = SQL.insnx(
291
+ mock_tx,
292
+ table="my_table",
293
+ data={"id": 1, "column1": "value1"},
294
+ where={"column1": "value1"},
295
+ )
296
+ self.assertEqual(sql_alias, sql_main)
297
+ self.assertEqual(params_alias, params_main)
298
+
299
+ def test_table_update_or_insert_updates_only(self):
300
+ tx = DummyTx()
301
+ table = Table(tx, "my_table")
302
+ table.cursor = mock.MagicMock(return_value=None)
303
+ table.update = mock.MagicMock(return_value=1)
304
+ ins_builder = mock.MagicMock()
305
+ table.sql = SimpleNamespace(insnx=ins_builder, insert_if_not_exists=ins_builder)
306
+
307
+ affected = table.update_or_insert(
308
+ update_data={"value": "new"},
309
+ insert_data={"id": 1, "value": "new"},
310
+ where={"id": 1},
311
+ )
312
+
313
+ self.assertEqual(affected, 1)
314
+ table.update.assert_called_once()
315
+ ins_builder.assert_not_called()
316
+
317
+ def test_table_update_or_insert_falls_back_to_insert(self):
318
+ tx = DummyTx()
319
+ table = Table(tx, "my_table")
320
+ table.cursor = mock.MagicMock(return_value=None)
321
+ table.update = mock.MagicMock(return_value=0)
322
+
323
+ captured = {}
324
+
325
+ def fake_insnx(tx_ctx, table_name, data, where):
326
+ captured["tx"] = tx_ctx
327
+ captured["table"] = table_name
328
+ captured["data"] = dict(data)
329
+ captured["where"] = where
330
+ return ("INSERT", ("a", "b"))
331
+
332
+ ins_builder = mock.MagicMock(side_effect=fake_insnx)
333
+ table.sql = SimpleNamespace(insnx=ins_builder, insert_if_not_exists=ins_builder)
334
+ tx.next_results.append(DummyResult(1))
335
+
336
+ affected = table.update_or_insert(
337
+ update_data={"value": "new"},
338
+ where={"id": 1},
339
+ pk={"id": 1},
340
+ )
341
+
342
+ self.assertEqual(affected, 1)
343
+ table.update.assert_called_once()
344
+ ins_builder.assert_called_once()
345
+ self.assertEqual(captured["table"], "my_table")
346
+ self.assertEqual(captured["data"], {"value": "new", "id": 1})
347
+ self.assertEqual(captured["where"], {"id": 1})
348
+
349
+ def test_table_update_or_insert_sql_only(self):
350
+ tx = DummyTx()
351
+ table = Table(tx, "my_table")
352
+ table.cursor = mock.MagicMock(return_value=None)
353
+ table.update = mock.MagicMock(return_value=("UPDATE sql", ("u",)))
354
+
355
+ ins_builder = mock.MagicMock(return_value=("INSERT sql", ("i",)))
356
+ table.sql = SimpleNamespace(insnx=ins_builder, insert_if_not_exists=ins_builder)
357
+
358
+ result = table.update_or_insert(
359
+ update_data={"value": "new"},
360
+ where={"id": 1},
361
+ pk={"id": 1},
362
+ sql_only=True,
363
+ )
364
+
365
+ self.assertEqual(result["update"], ("UPDATE sql", ("u",)))
366
+ self.assertEqual(result["insert"], ("INSERT sql", ("i",)))
367
+ table.update.assert_called_once_with({"value": "new"}, where={"id": 1}, pk={"id": 1}, sql_only=True)
368
+ ins_builder.assert_called_once()
369
+
370
+ def test_sql_merge_conflict_columns_are_quoted(self):
371
+ mock_tx = MockTx()
372
+ sql_query, _ = SQL.merge(
373
+ mock_tx,
374
+ table="my_table",
375
+ data={"payload": "value"},
376
+ pk={"select": 1},
377
+ on_conflict_do_nothing=False,
378
+ on_conflict_update=True,
379
+ )
380
+ self.assertIn('on conflict ("select")'.upper(), sql_query.upper())
381
+
382
+ def test_sql_merge_missing_auto_pk_values(self):
383
+ mock_tx = MockTx()
384
+ with self.assertRaisesRegex(
385
+ ValueError, "Primary key values missing from data for merge"
386
+ ):
387
+ SQL.merge(
388
+ mock_tx,
389
+ table="my_table",
390
+ data={"column1": "value1"},
391
+ pk=None,
392
+ on_conflict_do_nothing=False,
393
+ on_conflict_update=True,
394
+ )
395
+
396
+ def test_sql_merge_auto_pk_without_update_columns_falls_back_to_do_nothing(self):
397
+ mock_tx = MockTx()
398
+ sql_query, params = SQL.merge(
399
+ mock_tx,
400
+ table="my_table",
401
+ data={"id": 1},
402
+ pk=None,
403
+ on_conflict_do_nothing=False,
404
+ on_conflict_update=True,
405
+ )
406
+ self.assertIn("DO NOTHING", sql_query)
407
+ self.assertNotIn(" DO UPDATE", sql_query)
408
+ self.assertEqual(params, (1,))
409
+
221
410
  def test_get_type_mapping(self):
222
411
  self.assertEqual(TYPES.get_type("string"), "TEXT")
223
412
  self.assertEqual(TYPES.get_type(123), "BIGINT")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.0.138
3
+ Version: 0.0.152
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Velocity Team <info@codeclubs.org>
6
6
  License-Expression: MIT
@@ -497,7 +497,7 @@ def update_user(tx):
497
497
  # Find and update using dictionary syntax
498
498
  user = users.find(123) # Returns a row that behaves like a dict
499
499
  user['name'] = 'Updated Name' # Direct assignment like a dict
500
- user['updated_at'] = datetime.now() # No special methods needed
500
+ user['important_date'] = datetime.now() # No special methods needed
501
501
 
502
502
  # Check if columns exist before updating
503
503
  if 'phone' in user:
@@ -1,7 +1,7 @@
1
- velocity/__init__.py,sha256=4cK9msUzcPYo3tFiZ2-Ww6__hxYNmQ_B876ClZvAKwY,147
1
+ velocity/__init__.py,sha256=RyxrnzBhzGp5qsnb1PU0iJPVNsvSTg5ZIlnrnL5P3m4,147
2
2
  velocity/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  velocity/app/invoices.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- velocity/app/orders.py,sha256=fr1oTBjSFfyeMBUXRG06LV4jgwrlwYNL5mbEBleFwf0,6328
4
+ velocity/app/orders.py,sha256=C7ewngMpO8nD3ul_82o4FhZBdRkWvJtnuEbEJUKDCno,6151
5
5
  velocity/app/payments.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  velocity/app/purchase_orders.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  velocity/app/tests/__init__.py,sha256=mqbNes8CjWTYLgCEgmu3EZudF6HZj671friAsAa4m_E,19
@@ -18,7 +18,8 @@ velocity/aws/handlers/lambda_handler.py,sha256=0wa_CHyJOaI5RsHqB0Ae83-B-SwlKR0qk
18
18
  velocity/aws/handlers/response.py,sha256=s2Kw7yv5zAir1mEmfv6yBVIvRcRQ__xyryf1SrvtiRc,9317
19
19
  velocity/aws/handlers/sqs_handler.py,sha256=azuV8DrFOh0hM13EnPzyYVBS-3fLe2fn9OPc4ho7sGc,3375
20
20
  velocity/aws/handlers/mixins/__init__.py,sha256=_zyEpsnKikF7D7X-F0GA4cyIrQ6wBq7k5j6Vhp17vaQ,623
21
- velocity/aws/handlers/mixins/activity_tracker.py,sha256=b2Cu46FM0fLS2-FkWXXAPw6yvMV_Viu5-IlpGkVAlmk,5254
21
+ velocity/aws/handlers/mixins/activity_tracker.py,sha256=vyQ_8kpSprjzLoALDv7g2rVkfstn89Tbsg6Zb9GmVOk,6579
22
+ velocity/aws/handlers/mixins/aws_session_mixin.py,sha256=yTa2-n4zgv23wbW3uZUp-L4CUJy8vSL8IMMNjMlYFVg,6806
22
23
  velocity/aws/handlers/mixins/error_handler.py,sha256=uN2YF9v-3LzS3o_HdVpO-XMcPy3sS7SHjUg_LfbsG7Q,6803
23
24
  velocity/aws/handlers/mixins/legacy_mixin.py,sha256=_YhiPU-zzXQjGNSAKhoUwfTFlnczmU-3BkwNFqr0hYE,2117
24
25
  velocity/aws/handlers/mixins/standard_mixin.py,sha256=-wBX0PFlZAnxQsaMDEWr-xmU8TcRbQ4BZD3wmAKR2d0,2489
@@ -34,10 +35,10 @@ velocity/db/core/database.py,sha256=3zNGItklu9tZCKsbx2T2vCcU1so8AL9PPL0DLjvaz6s,
34
35
  velocity/db/core/decorators.py,sha256=quhjMoEmK_l2jF7jXyL5Fgv8uisIpBz34Au5d3U6UHs,5276
35
36
  velocity/db/core/engine.py,sha256=mNlaFPruHO935phKPVrsxZprGYUvxW-zp2sBcBZ-KCg,20666
36
37
  velocity/db/core/result.py,sha256=b0ie3yZAOj9S57x32uFFGKZ95zhImmZ0iXl0X1qYszc,12813
37
- velocity/db/core/row.py,sha256=yqxm03uEDy3oSbnkCtKyiqFdSqG3zXTq2HIHYKOvPY4,7291
38
+ velocity/db/core/row.py,sha256=GOWm-HEBPCBwdqMHMBRc41m0Hoht4vRVQLkvdogX1fU,7729
38
39
  velocity/db/core/sequence.py,sha256=VMBc0ZjGnOaWTwKW6xMNTdP8rZ2umQ8ml4fHTTwuGq4,3904
39
- velocity/db/core/table.py,sha256=EVoZZ6s21ZVqqT7VgH9Rr0SsFbsLLk6gtwvKwgbcWTQ,34708
40
- velocity/db/core/transaction.py,sha256=unjmVkkfb7D8Wow6V8V8aLaxUZo316i--ksZxc4-I1Q,6613
40
+ velocity/db/core/table.py,sha256=GyD4quWUGKXOiyZyE_bSESVscSGiCCQOVtixf7snfZ0,41940
41
+ velocity/db/core/transaction.py,sha256=VbB6GSdTT1Puy_j1tQnx9Ia3L3GZZFWaGw4xYWzpKAg,6733
41
42
  velocity/db/servers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
43
  velocity/db/servers/tablehelper.py,sha256=Q48ObN5KD_U2sBP0GUcjaQjKeE4Hr351sPQirwQ0_1s,22163
43
44
  velocity/db/servers/base/__init__.py,sha256=5--XJUeEAm7O6Ns2C_ODCr5TjFhdAge-zApZCT0LGTQ,285
@@ -48,28 +49,28 @@ velocity/db/servers/base/types.py,sha256=3LBxFCD35eeIsIqftpAJh0JjUVonDYemz2n6AMt
48
49
  velocity/db/servers/mysql/__init__.py,sha256=mASO5JB0xkzYngwx2X79yyKifYRqxIdfKFWutIHuw7k,2661
49
50
  velocity/db/servers/mysql/operators.py,sha256=wHmVSPxlPGbOdvQEmsfKhD25H8djovSbNcmacLHDVkI,1273
50
51
  velocity/db/servers/mysql/reserved.py,sha256=s-aFMwYJpZ_1FBcCMU8fOdhml2ET58-59ZnUm7iw5OU,3312
51
- velocity/db/servers/mysql/sql.py,sha256=OdfpoOOeJip5_O2HRNSA7_gtiQfXgELGbrKpu_NP878,18115
52
+ velocity/db/servers/mysql/sql.py,sha256=CxskGe86I-8idLNZmcG7IPqlc-BQM9cpJU6WS5KdCA0,22210
52
53
  velocity/db/servers/mysql/types.py,sha256=BMQf4TpsRo1JN-yOl1nSItTO-Juu2piSTNy5o_djBeM,3486
53
54
  velocity/db/servers/postgres/__init__.py,sha256=6YcTLXposmsrEaJgdUAM_QgD1TZDSILQrGcwWZ-dibk,2457
54
55
  velocity/db/servers/postgres/operators.py,sha256=y9k6enReeR5hJxU_lYYR2epoaw4qCxEqmYJJ5jjaVWA,1166
55
56
  velocity/db/servers/postgres/reserved.py,sha256=5tKLaqFV-HrWRj-nsrxl5KGbmeM3ukn_bPZK36XEu8M,3648
56
- velocity/db/servers/postgres/sql.py,sha256=tC-_kl2gYRZKvW5zxOxvdMv0ZXJrO4zpnjcXVuro9q0,47246
57
+ velocity/db/servers/postgres/sql.py,sha256=oF0Bll75sTOrRQhBNo3dklRpUFhLixil4i09eVC9Y8Y,54450
57
58
  velocity/db/servers/postgres/types.py,sha256=W71x8iRx-IIJkQSjb29k-KGkqp-QS6SxB0BHYXd4k8w,6955
58
59
  velocity/db/servers/sqlite/__init__.py,sha256=EIx09YN1-Vm-4CXVcEf9DBgvd8FhIN9rEqIaSRrEcIk,2293
59
60
  velocity/db/servers/sqlite/operators.py,sha256=VzZgph8RrnHkIVqqWGqnJwcafgBzc_8ZQp-M8tMl-mw,1221
60
61
  velocity/db/servers/sqlite/reserved.py,sha256=4vOI06bjt8wg9KxdzDTF-iOd-ewY23NvSzthpdty2fA,1298
61
- velocity/db/servers/sqlite/sql.py,sha256=VvI2KBLivSyd42zYg221DVRjrSSOEDjXT9SM2zbFAEM,16701
62
+ velocity/db/servers/sqlite/sql.py,sha256=iAENHbN8mfVsQHoqnEppynVMP_PdqXJX8jZQDNzr0ro,20948
62
63
  velocity/db/servers/sqlite/types.py,sha256=jpCJeV25x4Iytf6D6GXgK3hVYFAAFV4WKJC-d-m4kdU,3102
63
64
  velocity/db/servers/sqlserver/__init__.py,sha256=LN8OycN7W8da_ZPRYnPQ-O3Bv_xjret9qV1ZCitZlOU,2708
64
65
  velocity/db/servers/sqlserver/operators.py,sha256=xK8_doDLssS38SRs1NoAI7XTO0-gNGMDS76nTVru4kE,1104
65
66
  velocity/db/servers/sqlserver/reserved.py,sha256=Gn5n9DjxcjM-7PsIZPYigD6XLvMAYGnz1IrPuN7Dp2Y,2120
66
- velocity/db/servers/sqlserver/sql.py,sha256=b9UgFGma_FeY0S4VK9Yqn9QACS1SAZhJsEpA9muoHQI,20559
67
+ velocity/db/servers/sqlserver/sql.py,sha256=h4fnVuNWaQE2c2sEEkLSIlBGf3xZP-lDtwILhF2-g3c,26368
67
68
  velocity/db/servers/sqlserver/types.py,sha256=FAODYEO137m-WugpM89f9bQN9q6S2cjjUaz0a9gfE6M,3745
68
69
  velocity/db/tests/__init__.py,sha256=7-hilWb43cKnSnCeXcjFG-6LpziN5k443IpsIvuevP0,24
69
70
  velocity/db/tests/common_db_test.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
70
71
  velocity/db/tests/test_cursor_rowcount_fix.py,sha256=mZRL1SBb9Knh67CSFyvfwj_LAarE_ilfVwpQHW18Yy8,5507
71
72
  velocity/db/tests/test_db_utils.py,sha256=mSbEQXYKpWidX1FEnjrmt3q3K4ra0YTtQclrS46ufEE,8426
72
- velocity/db/tests/test_postgres.py,sha256=xp2gqsefCsvBZaG3ADT0lCPI-I2FbZeZ7GvXr77XvWc,9315
73
+ velocity/db/tests/test_postgres.py,sha256=NoBydNkGmXn8olXwva4C4sYV3cKzERd6Df0wHixxoyE,15554
73
74
  velocity/db/tests/test_postgres_unchanged.py,sha256=rNcy7S_HXazi_MjU8QjRZO4q8dULMeG4tg6eN-rPPz8,2998
74
75
  velocity/db/tests/test_process_error_robustness.py,sha256=CZr_co_o6PK7dejOr_gwdn0iKTzjWPTY5k-PwJ6oh9s,11361
75
76
  velocity/db/tests/test_result_caching.py,sha256=DgsGXWL4G79MZOslCjq_t8qtdhCcXkHjQqV5zsF6i6M,8960
@@ -121,8 +122,8 @@ velocity/misc/tests/test_merge.py,sha256=Vm5_jY5cVczw0hZF-3TYzmxFw81heJOJB-dvhCg
121
122
  velocity/misc/tests/test_oconv.py,sha256=fy4DwWGn_v486r2d_3ACpuBD-K1oOngNq1HJCGH7X-M,4694
122
123
  velocity/misc/tests/test_original_error.py,sha256=iWSd18tckOA54LoPQOGV5j9LAz2W-3_ZOwmyZ8-4YQc,1742
123
124
  velocity/misc/tests/test_timer.py,sha256=l9nrF84kHaFofvQYKInJmfoqC01wBhsUB18lVBgXCoo,2758
124
- velocity_python-0.0.138.dist-info/licenses/LICENSE,sha256=aoN245GG8s9oRUU89KNiGTU4_4OtnNmVi4hQeChg6rM,1076
125
- velocity_python-0.0.138.dist-info/METADATA,sha256=Rt_XjAFeisvsIMrNAx1j4whaIjqHk8aHcFKg7IOGIxk,34262
126
- velocity_python-0.0.138.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
127
- velocity_python-0.0.138.dist-info/top_level.txt,sha256=JW2vJPmodgdgSz7H6yoZvnxF8S3fTMIv-YJWCT1sNW0,9
128
- velocity_python-0.0.138.dist-info/RECORD,,
125
+ velocity_python-0.0.152.dist-info/licenses/LICENSE,sha256=aoN245GG8s9oRUU89KNiGTU4_4OtnNmVi4hQeChg6rM,1076
126
+ velocity_python-0.0.152.dist-info/METADATA,sha256=eMxsOZlWyZS4ziuZ2_P0T-vC6vr5lcBEXRHA8Yk8w_M,34266
127
+ velocity_python-0.0.152.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
128
+ velocity_python-0.0.152.dist-info/top_level.txt,sha256=JW2vJPmodgdgSz7H6yoZvnxF8S3fTMIv-YJWCT1sNW0,9
129
+ velocity_python-0.0.152.dist-info/RECORD,,