velocity-python 0.0.35__py3-none-any.whl → 0.0.65__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.
- velocity/__init__.py +1 -1
- velocity/db/core/column.py +25 -105
- velocity/db/core/database.py +79 -23
- velocity/db/core/decorators.py +84 -47
- velocity/db/core/engine.py +190 -189
- velocity/db/core/result.py +94 -49
- velocity/db/core/row.py +81 -46
- velocity/db/core/sequence.py +112 -22
- velocity/db/core/table.py +660 -243
- velocity/db/core/transaction.py +75 -77
- velocity/db/servers/mysql.py +4 -0
- velocity/db/servers/postgres/__init__.py +19 -0
- velocity/db/servers/postgres/operators.py +23 -0
- velocity/db/servers/{postgres.py → postgres/sql.py} +508 -589
- velocity/db/servers/postgres/types.py +109 -0
- velocity/db/servers/tablehelper.py +277 -0
- velocity/misc/conv/iconv.py +277 -91
- velocity/misc/conv/oconv.py +5 -4
- velocity/misc/db.py +2 -2
- velocity/misc/format.py +2 -2
- {velocity_python-0.0.35.dist-info → velocity_python-0.0.65.dist-info}/METADATA +7 -6
- velocity_python-0.0.65.dist-info/RECORD +47 -0
- {velocity_python-0.0.35.dist-info → velocity_python-0.0.65.dist-info}/WHEEL +1 -1
- velocity_python-0.0.35.dist-info/RECORD +0 -43
- /velocity/db/servers/{postgres_reserved.py → postgres/reserved.py} +0 -0
- {velocity_python-0.0.35.dist-info → velocity_python-0.0.65.dist-info/licenses}/LICENSE +0 -0
- {velocity_python-0.0.35.dist-info → velocity_python-0.0.65.dist-info}/top_level.txt +0 -0
velocity/__init__.py
CHANGED
velocity/db/core/column.py
CHANGED
|
@@ -2,64 +2,34 @@ from velocity.db import exceptions
|
|
|
2
2
|
from velocity.db.core.decorators import return_default
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
class Column
|
|
5
|
+
class Column:
|
|
6
6
|
"""
|
|
7
7
|
Represents a column in a database table.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
def __init__(self, table, name):
|
|
11
|
-
"""
|
|
12
|
-
Initializes a column object with the specified table and name.
|
|
13
|
-
|
|
14
|
-
Args:
|
|
15
|
-
table (table): The table object that the column belongs to.
|
|
16
|
-
name (str): The name of the column.
|
|
17
|
-
|
|
18
|
-
Raises:
|
|
19
|
-
Exception: If the table parameter is not of type 'table'.
|
|
20
|
-
"""
|
|
21
11
|
if isinstance(table, str):
|
|
22
|
-
raise Exception("
|
|
12
|
+
raise Exception("Column 'table' parameter must be a Table instance.")
|
|
23
13
|
self.tx = table.tx
|
|
24
14
|
self.sql = table.tx.engine.sql
|
|
25
15
|
self.name = name
|
|
26
16
|
self.table = table
|
|
27
17
|
|
|
28
18
|
def __str__(self):
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Column: %s
|
|
38
|
-
Column Exists: %s
|
|
39
|
-
Py Type: %s
|
|
40
|
-
SQL Type: %s
|
|
41
|
-
NULL OK: %s
|
|
42
|
-
Foreign Key: %s
|
|
43
|
-
""" % (
|
|
44
|
-
self.table.name,
|
|
45
|
-
self.name,
|
|
46
|
-
self.exists(),
|
|
47
|
-
self.py_type,
|
|
48
|
-
self.sql_type,
|
|
49
|
-
self.is_nullok,
|
|
50
|
-
self.foreign_key_to,
|
|
19
|
+
return (
|
|
20
|
+
f"Table: {self.table.name}\n"
|
|
21
|
+
f"Column: {self.name}\n"
|
|
22
|
+
f"Column Exists: {self.exists()}\n"
|
|
23
|
+
f"Py Type: {self.py_type}\n"
|
|
24
|
+
f"SQL Type: {self.sql_type}\n"
|
|
25
|
+
f"NULL OK: {self.is_nullok}\n"
|
|
26
|
+
f"Foreign Key: {self.foreign_key_to}\n"
|
|
51
27
|
)
|
|
52
28
|
|
|
53
29
|
@property
|
|
54
30
|
def info(self):
|
|
55
31
|
"""
|
|
56
|
-
Retrieves information about the column from the database.
|
|
57
|
-
|
|
58
|
-
Returns:
|
|
59
|
-
dict: A dictionary containing information about the column.
|
|
60
|
-
|
|
61
|
-
Raises:
|
|
62
|
-
DbColumnMissingError: If the column does not exist in the database.
|
|
32
|
+
Retrieves information about the column from the database, raising DbColumnMissingError if not found.
|
|
63
33
|
"""
|
|
64
34
|
sql, vals = self.sql.column_info(self.table.name, self.name)
|
|
65
35
|
result = self.tx.execute(sql, vals).one()
|
|
@@ -70,13 +40,7 @@ class Column(object):
|
|
|
70
40
|
@property
|
|
71
41
|
def foreign_key_info(self):
|
|
72
42
|
"""
|
|
73
|
-
Retrieves information about
|
|
74
|
-
|
|
75
|
-
Returns:
|
|
76
|
-
dict: A dictionary containing information about the foreign key constraint.
|
|
77
|
-
|
|
78
|
-
Raises:
|
|
79
|
-
DbColumnMissingError: If the column does not exist in the database.
|
|
43
|
+
Retrieves information about any foreign key constraint on this column.
|
|
80
44
|
"""
|
|
81
45
|
sql, vals = self.sql.foreign_key_info(table=self.table.name, column=self.name)
|
|
82
46
|
result = self.tx.execute(sql, vals).one()
|
|
@@ -87,13 +51,7 @@ class Column(object):
|
|
|
87
51
|
@property
|
|
88
52
|
def foreign_key_to(self):
|
|
89
53
|
"""
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Returns:
|
|
93
|
-
str: The name of the referenced table and column in the format 'referenced_table_name.referenced_column_name'.
|
|
94
|
-
|
|
95
|
-
Raises:
|
|
96
|
-
DbColumnMissingError: If the column does not exist in the database.
|
|
54
|
+
Returns a string 'referenced_table_name.referenced_column_name' or None if no foreign key.
|
|
97
55
|
"""
|
|
98
56
|
try:
|
|
99
57
|
return "{referenced_table_name}.{referenced_column_name}".format(
|
|
@@ -105,13 +63,7 @@ class Column(object):
|
|
|
105
63
|
@property
|
|
106
64
|
def foreign_key_table(self):
|
|
107
65
|
"""
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
Returns:
|
|
111
|
-
str: The name of the referenced table.
|
|
112
|
-
|
|
113
|
-
Raises:
|
|
114
|
-
DbColumnMissingError: If the column does not exist in the database.
|
|
66
|
+
Returns the name of the referenced table for the foreign key, or None if none.
|
|
115
67
|
"""
|
|
116
68
|
try:
|
|
117
69
|
return self.foreign_key_info["referenced_table_name"]
|
|
@@ -120,40 +72,28 @@ class Column(object):
|
|
|
120
72
|
|
|
121
73
|
def exists(self):
|
|
122
74
|
"""
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
Returns:
|
|
126
|
-
bool: True if the column exists, False otherwise.
|
|
75
|
+
True if this column name is in self.table.columns().
|
|
127
76
|
"""
|
|
128
|
-
return self.name in self.table.columns
|
|
77
|
+
return self.name in self.table.columns()
|
|
129
78
|
|
|
130
79
|
@property
|
|
131
80
|
def py_type(self):
|
|
132
81
|
"""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
Returns:
|
|
136
|
-
type: The Python data type of the column.
|
|
82
|
+
Returns the Python data type that corresponds to this column's SQL type.
|
|
137
83
|
"""
|
|
138
|
-
return self.sql.py_type(self.sql_type)
|
|
84
|
+
return self.sql.types.py_type(self.sql_type)
|
|
139
85
|
|
|
140
86
|
@property
|
|
141
87
|
def sql_type(self):
|
|
142
88
|
"""
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
Returns:
|
|
146
|
-
str: The SQL data type of the column.
|
|
89
|
+
Returns the underlying SQL type name (e.g. 'TEXT', 'INTEGER', etc.).
|
|
147
90
|
"""
|
|
148
91
|
return self.info[self.sql.type_column_identifier]
|
|
149
92
|
|
|
150
93
|
@property
|
|
151
94
|
def is_nullable(self):
|
|
152
95
|
"""
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
Returns:
|
|
156
|
-
bool: True if the column is nullable, False otherwise.
|
|
96
|
+
True if column is nullable.
|
|
157
97
|
"""
|
|
158
98
|
return self.info[self.sql.is_nullable]
|
|
159
99
|
|
|
@@ -162,9 +102,6 @@ class Column(object):
|
|
|
162
102
|
def rename(self, name):
|
|
163
103
|
"""
|
|
164
104
|
Renames the column.
|
|
165
|
-
|
|
166
|
-
Args:
|
|
167
|
-
name (str): The new name for the column.
|
|
168
105
|
"""
|
|
169
106
|
sql, vals = self.sql.rename_column(self.table.name, self.name, name)
|
|
170
107
|
self.tx.execute(sql, vals)
|
|
@@ -173,40 +110,23 @@ class Column(object):
|
|
|
173
110
|
@return_default([])
|
|
174
111
|
def distinct(self, order="asc", qty=None):
|
|
175
112
|
"""
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
Args:
|
|
179
|
-
order (str, optional): The order in which the values should be sorted. Defaults to 'asc'.
|
|
180
|
-
qty (int, optional): The maximum number of distinct values to retrieve. Defaults to None.
|
|
181
|
-
|
|
182
|
-
Returns:
|
|
183
|
-
list: A list of distinct values from the column.
|
|
113
|
+
Returns the distinct values in this column, optionally ordered and/or limited in quantity.
|
|
184
114
|
"""
|
|
185
115
|
sql, vals = self.sql.select(
|
|
186
|
-
columns="distinct {
|
|
116
|
+
columns=f"distinct {self.name}",
|
|
187
117
|
table=self.table.name,
|
|
188
|
-
orderby="{} {}"
|
|
118
|
+
orderby=f"{self.name} {order}",
|
|
189
119
|
qty=qty,
|
|
190
120
|
)
|
|
191
121
|
return self.tx.execute(sql, vals).as_simple_list().all()
|
|
192
122
|
|
|
193
123
|
def max(self, where=None):
|
|
194
124
|
"""
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
Args:
|
|
198
|
-
where (str, optional): The WHERE clause to filter the rows. Defaults to None.
|
|
199
|
-
|
|
200
|
-
Returns:
|
|
201
|
-
int: The maximum value from the column.
|
|
202
|
-
|
|
203
|
-
Raises:
|
|
204
|
-
DbTableMissingError: If the table does not exist in the database.
|
|
205
|
-
DbColumnMissingError: If the column does not exist in the database.
|
|
125
|
+
Returns the MAX() of this column, or 0 if table/column is missing.
|
|
206
126
|
"""
|
|
207
127
|
try:
|
|
208
128
|
sql, vals = self.sql.select(
|
|
209
|
-
columns="max({
|
|
129
|
+
columns=f"max({self.name})", table=self.table.name, where=where
|
|
210
130
|
)
|
|
211
131
|
return self.tx.execute(sql, vals).scalar()
|
|
212
132
|
except (exceptions.DbTableMissingError, exceptions.DbColumnMissingError):
|
velocity/db/core/database.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
class Database
|
|
1
|
+
class Database:
|
|
2
|
+
"""
|
|
3
|
+
Represents a database within a transaction context.
|
|
4
|
+
"""
|
|
2
5
|
|
|
3
6
|
def __init__(self, tx, name=None):
|
|
4
7
|
self.tx = tx
|
|
@@ -6,16 +9,11 @@ class Database(object):
|
|
|
6
9
|
self.sql = tx.engine.sql
|
|
7
10
|
|
|
8
11
|
def __str__(self):
|
|
9
|
-
return
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
""" % (
|
|
15
|
-
self.tx.engine.sql.server,
|
|
16
|
-
self.name,
|
|
17
|
-
self.exists(),
|
|
18
|
-
len(self.tables),
|
|
12
|
+
return (
|
|
13
|
+
f"Engine: {self.tx.engine.sql.server}\n"
|
|
14
|
+
f"Database: {self.name}\n"
|
|
15
|
+
f"(db exists) {self.exists()}\n"
|
|
16
|
+
f"Tables: {len(self.tables)}\n"
|
|
19
17
|
)
|
|
20
18
|
|
|
21
19
|
def __enter__(self):
|
|
@@ -26,40 +24,98 @@ class Database(object):
|
|
|
26
24
|
self.close()
|
|
27
25
|
|
|
28
26
|
def close(self):
|
|
27
|
+
"""
|
|
28
|
+
Closes the cursor if it exists.
|
|
29
|
+
"""
|
|
29
30
|
try:
|
|
30
31
|
self._cursor.close()
|
|
31
|
-
# print("*** database('{}').cursor.close()".format(self.name))
|
|
32
32
|
except AttributeError:
|
|
33
33
|
pass
|
|
34
34
|
|
|
35
|
-
@property
|
|
36
35
|
def cursor(self):
|
|
36
|
+
"""
|
|
37
|
+
Lazy-initialize the cursor on first use.
|
|
38
|
+
"""
|
|
37
39
|
try:
|
|
38
40
|
return self._cursor
|
|
39
41
|
except AttributeError:
|
|
40
|
-
# print("*** database('{}').cursor.open()".format(self.name))
|
|
41
42
|
self._cursor = self.tx.cursor()
|
|
42
43
|
return self._cursor
|
|
43
44
|
|
|
44
45
|
def drop(self):
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
"""
|
|
47
|
+
Drops this database.
|
|
48
|
+
"""
|
|
49
|
+
sql, vals = self.tx.engine.sql.drop_database(self.name)
|
|
50
|
+
self.tx.execute(sql, vals, single=True, cursor=self.cursor())
|
|
47
51
|
|
|
48
52
|
def create(self):
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
"""
|
|
54
|
+
Creates this database.
|
|
55
|
+
"""
|
|
56
|
+
sql, vals = self.tx.engine.sql.create_database(self.name)
|
|
57
|
+
self.tx.execute(sql, vals, single=True, cursor=self.cursor())
|
|
51
58
|
|
|
52
59
|
def exists(self):
|
|
60
|
+
"""
|
|
61
|
+
Returns True if the database exists, else False.
|
|
62
|
+
"""
|
|
53
63
|
sql, vals = self.sql.databases()
|
|
54
|
-
result = self.tx.execute(sql, vals, cursor=self.cursor)
|
|
64
|
+
result = self.tx.execute(sql, vals, cursor=self.cursor())
|
|
55
65
|
return bool(self.name in [x[0] for x in result.as_tuple()])
|
|
56
66
|
|
|
57
67
|
@property
|
|
58
68
|
def tables(self):
|
|
69
|
+
"""
|
|
70
|
+
Returns a list of 'schema.table' strings representing tables in this database.
|
|
71
|
+
"""
|
|
59
72
|
sql, vals = self.sql.tables()
|
|
60
|
-
result = self.tx.execute(sql, vals, cursor=self.cursor)
|
|
61
|
-
return ["
|
|
73
|
+
result = self.tx.execute(sql, vals, cursor=self.cursor())
|
|
74
|
+
return [f"{x[0]}.{x[1]}" for x in result.as_tuple()]
|
|
62
75
|
|
|
63
76
|
def reindex(self):
|
|
64
|
-
|
|
65
|
-
|
|
77
|
+
"""
|
|
78
|
+
Re-indexes this database.
|
|
79
|
+
"""
|
|
80
|
+
sql = f"REINDEX DATABASE {self.name}"
|
|
81
|
+
vals = ()
|
|
82
|
+
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
83
|
+
|
|
84
|
+
def switch(self):
|
|
85
|
+
"""
|
|
86
|
+
Switches the parent transaction to this database.
|
|
87
|
+
"""
|
|
88
|
+
self.tx.switch_to_database(self.name)
|
|
89
|
+
return self
|
|
90
|
+
|
|
91
|
+
def vacuum(self, analyze=True, full=False, reindex=True):
|
|
92
|
+
"""
|
|
93
|
+
Performs VACUUM on this database, optionally FULL, optionally ANALYZE,
|
|
94
|
+
optionally REINDEX.
|
|
95
|
+
"""
|
|
96
|
+
# Manually open a separate connection to run VACUUM in isolation_level=0
|
|
97
|
+
conn = self.tx.engine.connect()
|
|
98
|
+
old_isolation_level = conn.isolation_level
|
|
99
|
+
try:
|
|
100
|
+
# Postgres requires VACUUM to run outside a normal transaction block
|
|
101
|
+
conn.set_isolation_level(0)
|
|
102
|
+
|
|
103
|
+
# Build up the VACUUM command
|
|
104
|
+
parts = ["VACUUM"]
|
|
105
|
+
if full:
|
|
106
|
+
parts.append("FULL")
|
|
107
|
+
if analyze:
|
|
108
|
+
parts.append("ANALYZE")
|
|
109
|
+
|
|
110
|
+
# Execute VACUUM
|
|
111
|
+
with conn.cursor() as cur:
|
|
112
|
+
cur.execute(" ".join(parts))
|
|
113
|
+
|
|
114
|
+
# Optionally REINDEX the database
|
|
115
|
+
if reindex:
|
|
116
|
+
cur.execute(f"REINDEX DATABASE {self.name}")
|
|
117
|
+
|
|
118
|
+
finally:
|
|
119
|
+
# Restore isolation level and close the connection
|
|
120
|
+
conn.set_isolation_level(old_isolation_level)
|
|
121
|
+
conn.close()
|
velocity/db/core/decorators.py
CHANGED
|
@@ -1,34 +1,61 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import random
|
|
3
|
+
import traceback
|
|
1
4
|
from functools import wraps
|
|
2
5
|
from velocity.db import exceptions
|
|
3
|
-
import traceback
|
|
4
6
|
|
|
5
7
|
|
|
6
|
-
def retry_on_dup_key(
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
def retry_on_dup_key(func):
|
|
9
|
+
"""
|
|
10
|
+
Retries a function call if it raises DbDuplicateKeyError, up to max_retries.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@wraps(func)
|
|
9
14
|
def retry_decorator(self, *args, **kwds):
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
max_retries = 10
|
|
16
|
+
retries = 0
|
|
17
|
+
while retries < max_retries:
|
|
18
|
+
sp = self.tx.create_savepoint(cursor=self.cursor())
|
|
12
19
|
try:
|
|
13
|
-
|
|
20
|
+
result = func(self, *args, **kwds)
|
|
21
|
+
self.tx.release_savepoint(sp, cursor=self.cursor())
|
|
22
|
+
return result
|
|
14
23
|
except exceptions.DbDuplicateKeyError:
|
|
15
|
-
self.tx.rollback_savepoint(sp, cursor=self.cursor)
|
|
16
|
-
|
|
24
|
+
self.tx.rollback_savepoint(sp, cursor=self.cursor())
|
|
25
|
+
if "sys_id" in kwds.get("data", {}):
|
|
26
|
+
raise
|
|
27
|
+
retries += 1
|
|
28
|
+
if retries >= max_retries:
|
|
29
|
+
raise
|
|
30
|
+
backoff_time = (2**retries) * 0.01 + random.uniform(0, 0.02)
|
|
31
|
+
time.sleep(backoff_time)
|
|
32
|
+
raise exceptions.DbDuplicateKeyError("Max retries reached.")
|
|
17
33
|
|
|
18
34
|
return retry_decorator
|
|
19
35
|
|
|
20
36
|
|
|
21
|
-
def reset_id_on_dup_key(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
def reset_id_on_dup_key(func):
|
|
38
|
+
"""
|
|
39
|
+
Wraps an INSERT/UPSERT to reset the sys_id sequence on duplicate key collisions.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
@wraps(func)
|
|
43
|
+
def reset_decorator(self, *args, retries=0, **kwds):
|
|
44
|
+
sp = self.tx.create_savepoint(cursor=self.cursor())
|
|
26
45
|
try:
|
|
27
|
-
|
|
46
|
+
result = func(self, *args, **kwds)
|
|
47
|
+
self.tx.release_savepoint(sp, cursor=self.cursor())
|
|
48
|
+
return result
|
|
28
49
|
except exceptions.DbDuplicateKeyError:
|
|
29
|
-
self.tx.rollback_savepoint(sp, cursor=self.cursor)
|
|
30
|
-
|
|
31
|
-
|
|
50
|
+
self.tx.rollback_savepoint(sp, cursor=self.cursor())
|
|
51
|
+
if "sys_id" in kwds.get("data", {}):
|
|
52
|
+
raise
|
|
53
|
+
if retries < 3:
|
|
54
|
+
backoff_time = (2**retries) * 0.01 + random.uniform(0, 0.02)
|
|
55
|
+
time.sleep(backoff_time)
|
|
56
|
+
self.set_sequence(self.max("sys_id") + 1)
|
|
57
|
+
return reset_decorator(self, *args, retries=retries + 1, **kwds)
|
|
58
|
+
raise exceptions.DbDuplicateKeyError("Max retries reached.")
|
|
32
59
|
|
|
33
60
|
return reset_decorator
|
|
34
61
|
|
|
@@ -44,59 +71,69 @@ def return_default(
|
|
|
44
71
|
exceptions.DbObjectExistsError,
|
|
45
72
|
),
|
|
46
73
|
):
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
74
|
+
"""
|
|
75
|
+
If the wrapped function raises one of the specified exceptions, or returns None,
|
|
76
|
+
this decorator returns the `default` value instead.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def decorator(func):
|
|
80
|
+
func.default = default
|
|
81
|
+
func.exceptions = exceptions
|
|
50
82
|
|
|
51
|
-
@wraps(
|
|
52
|
-
def
|
|
53
|
-
sp = self.tx.create_savepoint(cursor=self.cursor)
|
|
83
|
+
@wraps(func)
|
|
84
|
+
def wrapper(self, *args, **kwds):
|
|
85
|
+
sp = self.tx.create_savepoint(cursor=self.cursor())
|
|
54
86
|
try:
|
|
55
|
-
result =
|
|
87
|
+
result = func(self, *args, **kwds)
|
|
56
88
|
if result is None:
|
|
57
89
|
result = default
|
|
58
|
-
except
|
|
90
|
+
except func.exceptions:
|
|
59
91
|
traceback.print_exc()
|
|
60
|
-
self.tx.rollback_savepoint(sp, cursor=self.cursor)
|
|
61
|
-
return
|
|
62
|
-
self.tx.release_savepoint(sp, cursor=self.cursor)
|
|
92
|
+
self.tx.rollback_savepoint(sp, cursor=self.cursor())
|
|
93
|
+
return default
|
|
94
|
+
self.tx.release_savepoint(sp, cursor=self.cursor())
|
|
63
95
|
return result
|
|
64
96
|
|
|
65
|
-
return
|
|
97
|
+
return wrapper
|
|
66
98
|
|
|
67
99
|
return decorator
|
|
68
100
|
|
|
69
101
|
|
|
70
|
-
def create_missing(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
102
|
+
def create_missing(func):
|
|
103
|
+
"""
|
|
104
|
+
If the function call fails with DbColumnMissingError or DbTableMissingError, tries to create them and re-run.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
@wraps(func)
|
|
108
|
+
def wrapper(self, *args, **kwds):
|
|
109
|
+
sp = self.tx.create_savepoint(cursor=self.cursor())
|
|
74
110
|
try:
|
|
75
|
-
result =
|
|
76
|
-
self.tx.release_savepoint(sp, cursor=self.cursor)
|
|
111
|
+
result = func(self, *args, **kwds)
|
|
112
|
+
self.tx.release_savepoint(sp, cursor=self.cursor())
|
|
113
|
+
return result
|
|
77
114
|
except exceptions.DbColumnMissingError:
|
|
78
|
-
self.tx.rollback_savepoint(sp, cursor=self.cursor)
|
|
115
|
+
self.tx.rollback_savepoint(sp, cursor=self.cursor())
|
|
79
116
|
data = {}
|
|
80
117
|
if "pk" in kwds:
|
|
81
118
|
data.update(kwds["pk"])
|
|
82
119
|
if "data" in kwds:
|
|
83
120
|
data.update(kwds["data"])
|
|
84
|
-
for i in
|
|
85
|
-
if
|
|
86
|
-
data.update(
|
|
121
|
+
for i, arg in enumerate(args):
|
|
122
|
+
if isinstance(arg, dict):
|
|
123
|
+
data.update(arg)
|
|
87
124
|
self.alter(data)
|
|
88
|
-
|
|
125
|
+
return func(self, *args, **kwds)
|
|
89
126
|
except exceptions.DbTableMissingError:
|
|
90
|
-
self.tx.rollback_savepoint(sp, cursor=self.cursor)
|
|
127
|
+
self.tx.rollback_savepoint(sp, cursor=self.cursor())
|
|
91
128
|
data = {}
|
|
92
129
|
if "pk" in kwds:
|
|
93
130
|
data.update(kwds["pk"])
|
|
94
131
|
if "data" in kwds:
|
|
95
132
|
data.update(kwds["data"])
|
|
96
|
-
for i in
|
|
97
|
-
|
|
133
|
+
for i, arg in enumerate(args):
|
|
134
|
+
if isinstance(arg, dict):
|
|
135
|
+
data.update(arg)
|
|
98
136
|
self.create(data)
|
|
99
|
-
|
|
100
|
-
return result
|
|
137
|
+
return func(self, *args, **kwds)
|
|
101
138
|
|
|
102
|
-
return
|
|
139
|
+
return wrapper
|