velocity-python 0.0.35__py3-none-any.whl → 0.0.64__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.
- 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 +179 -184
- 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.64.dist-info}/METADATA +6 -6
- velocity_python-0.0.64.dist-info/RECORD +47 -0
- {velocity_python-0.0.35.dist-info → velocity_python-0.0.64.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.64.dist-info}/LICENSE +0 -0
- {velocity_python-0.0.35.dist-info → velocity_python-0.0.64.dist-info}/top_level.txt +0 -0
velocity/db/core/result.py
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import datetime
|
|
2
|
-
from velocity.misc.format import to_json
|
|
3
2
|
import decimal
|
|
3
|
+
from velocity.misc.format import to_json
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
class Result
|
|
6
|
+
class Result:
|
|
7
|
+
"""
|
|
8
|
+
Wraps a database cursor to provide various convenience transformations
|
|
9
|
+
(dict, list, tuple, etc.) and helps iterate over query results.
|
|
10
|
+
"""
|
|
11
|
+
|
|
7
12
|
def __init__(self, cursor=None, tx=None, sql=None, params=None):
|
|
8
13
|
self._cursor = cursor
|
|
9
|
-
|
|
10
|
-
self._headers = [x[0].lower() for x in cursor.description]
|
|
11
|
-
else:
|
|
12
|
-
self._headers = []
|
|
13
|
-
self.as_dict()
|
|
14
|
+
self._headers = [x[0].lower() for x in getattr(cursor, "description", []) or []]
|
|
14
15
|
self.__as_strings = False
|
|
15
16
|
self.__enumerate = False
|
|
16
17
|
self.__count = -1
|
|
@@ -18,32 +19,7 @@ class Result(object):
|
|
|
18
19
|
self.__tx = tx
|
|
19
20
|
self.__sql = sql
|
|
20
21
|
self.__params = params
|
|
21
|
-
|
|
22
|
-
@property
|
|
23
|
-
def headers(self):
|
|
24
|
-
if not self._headers:
|
|
25
|
-
if self._cursor and hasattr(self._cursor, "description"):
|
|
26
|
-
self._headers = [x[0].lower() for x in self._cursor.description]
|
|
27
|
-
return self._headers
|
|
28
|
-
|
|
29
|
-
@property
|
|
30
|
-
def columns(self):
|
|
31
|
-
if not self.__columns:
|
|
32
|
-
if self._cursor and hasattr(self._cursor, "description"):
|
|
33
|
-
for column in self._cursor.description:
|
|
34
|
-
data = {
|
|
35
|
-
"type_name": self.__tx.pg_types[column.type_code],
|
|
36
|
-
# TBD This can be implemented if needed but turning off
|
|
37
|
-
# since it is not complete set of all codes and could raise
|
|
38
|
-
# exception
|
|
39
|
-
#'pytype': types[self.__tx.pg_types[column.type_code]]
|
|
40
|
-
}
|
|
41
|
-
for key in dir(column):
|
|
42
|
-
if "__" in key:
|
|
43
|
-
continue
|
|
44
|
-
data[key] = getattr(column, key)
|
|
45
|
-
self.__columns[column.name] = data
|
|
46
|
-
return self.__columns
|
|
22
|
+
self.transform = lambda row: dict(zip(self.headers, row)) # Default transform
|
|
47
23
|
|
|
48
24
|
def __str__(self):
|
|
49
25
|
return repr(self.all())
|
|
@@ -56,6 +32,9 @@ class Result(object):
|
|
|
56
32
|
self.close()
|
|
57
33
|
|
|
58
34
|
def __next__(self):
|
|
35
|
+
"""
|
|
36
|
+
Iterator interface to retrieve the next row.
|
|
37
|
+
"""
|
|
59
38
|
if self._cursor:
|
|
60
39
|
row = self._cursor.fetchone()
|
|
61
40
|
if row:
|
|
@@ -64,11 +43,13 @@ class Result(object):
|
|
|
64
43
|
if self.__enumerate:
|
|
65
44
|
self.__count += 1
|
|
66
45
|
return (self.__count, self.transform(row))
|
|
67
|
-
|
|
68
|
-
return self.transform(row)
|
|
46
|
+
return self.transform(row)
|
|
69
47
|
raise StopIteration
|
|
70
48
|
|
|
71
49
|
def batch(self, qty=1):
|
|
50
|
+
"""
|
|
51
|
+
Yields lists (batches) of rows with size = qty until no rows remain.
|
|
52
|
+
"""
|
|
72
53
|
results = []
|
|
73
54
|
while True:
|
|
74
55
|
try:
|
|
@@ -76,14 +57,15 @@ class Result(object):
|
|
|
76
57
|
except StopIteration:
|
|
77
58
|
if results:
|
|
78
59
|
yield results
|
|
79
|
-
|
|
80
|
-
continue
|
|
81
|
-
raise
|
|
60
|
+
break
|
|
82
61
|
if len(results) == qty:
|
|
83
62
|
yield results
|
|
84
63
|
results = []
|
|
85
64
|
|
|
86
65
|
def all(self):
|
|
66
|
+
"""
|
|
67
|
+
Retrieves all rows at once into a list.
|
|
68
|
+
"""
|
|
87
69
|
results = []
|
|
88
70
|
while True:
|
|
89
71
|
try:
|
|
@@ -95,62 +77,124 @@ class Result(object):
|
|
|
95
77
|
def __iter__(self):
|
|
96
78
|
return self
|
|
97
79
|
|
|
80
|
+
@property
|
|
81
|
+
def headers(self):
|
|
82
|
+
"""
|
|
83
|
+
Retrieves column headers from the cursor if not already set.
|
|
84
|
+
"""
|
|
85
|
+
if not self._headers and self._cursor and hasattr(self._cursor, "description"):
|
|
86
|
+
self._headers = [x[0].lower() for x in self._cursor.description]
|
|
87
|
+
return self._headers
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def columns(self):
|
|
91
|
+
"""
|
|
92
|
+
Retrieves detailed column information from the cursor.
|
|
93
|
+
"""
|
|
94
|
+
if not self.__columns and self._cursor and hasattr(self._cursor, "description"):
|
|
95
|
+
for column in self._cursor.description:
|
|
96
|
+
data = {
|
|
97
|
+
"type_name": self.__tx.pg_types[column.type_code],
|
|
98
|
+
}
|
|
99
|
+
for key in dir(column):
|
|
100
|
+
if "__" not in key:
|
|
101
|
+
data[key] = getattr(column, key)
|
|
102
|
+
self.__columns[column.name] = data
|
|
103
|
+
return self.__columns
|
|
104
|
+
|
|
98
105
|
@property
|
|
99
106
|
def cursor(self):
|
|
100
107
|
return self._cursor
|
|
101
108
|
|
|
102
109
|
def close(self):
|
|
103
|
-
|
|
110
|
+
"""
|
|
111
|
+
Closes the underlying cursor if it exists.
|
|
112
|
+
"""
|
|
113
|
+
if self._cursor:
|
|
114
|
+
self._cursor.close()
|
|
104
115
|
|
|
105
116
|
def as_dict(self):
|
|
106
|
-
|
|
117
|
+
"""
|
|
118
|
+
Transform each row into a dictionary keyed by column names.
|
|
119
|
+
"""
|
|
120
|
+
self.transform = lambda row: dict(zip(self.headers, row))
|
|
107
121
|
return self
|
|
108
122
|
|
|
109
123
|
def as_json(self):
|
|
110
|
-
|
|
124
|
+
"""
|
|
125
|
+
Transform each row into JSON (string).
|
|
126
|
+
"""
|
|
127
|
+
self.transform = lambda row: to_json(dict(zip(self.headers, row)))
|
|
111
128
|
return self
|
|
112
129
|
|
|
113
130
|
def as_named_tuple(self):
|
|
131
|
+
"""
|
|
132
|
+
Transform each row into a list of (column_name, value) pairs.
|
|
133
|
+
"""
|
|
114
134
|
self.transform = lambda row: list(zip(self.headers, row))
|
|
115
135
|
return self
|
|
116
136
|
|
|
117
137
|
def as_list(self):
|
|
138
|
+
"""
|
|
139
|
+
Transform each row into a list of values.
|
|
140
|
+
"""
|
|
118
141
|
self.transform = lambda row: list(row)
|
|
119
142
|
return self
|
|
120
143
|
|
|
121
144
|
def as_tuple(self):
|
|
145
|
+
"""
|
|
146
|
+
Transform each row into a tuple of values.
|
|
147
|
+
"""
|
|
122
148
|
self.transform = lambda row: row
|
|
123
149
|
return self
|
|
124
150
|
|
|
125
151
|
def as_simple_list(self, pos=0):
|
|
152
|
+
"""
|
|
153
|
+
Transform each row into the single value at position `pos`.
|
|
154
|
+
"""
|
|
126
155
|
self.transform = lambda row: row[pos]
|
|
127
156
|
return self
|
|
128
157
|
|
|
129
158
|
def strings(self, as_strings=True):
|
|
159
|
+
"""
|
|
160
|
+
Indicate whether retrieved rows should be coerced to string form.
|
|
161
|
+
"""
|
|
130
162
|
self.__as_strings = as_strings
|
|
131
163
|
return self
|
|
132
164
|
|
|
133
165
|
def scalar(self, default=None):
|
|
166
|
+
"""
|
|
167
|
+
Return the first column of the first row, or `default` if no rows.
|
|
168
|
+
"""
|
|
134
169
|
if not self._cursor:
|
|
135
170
|
return None
|
|
136
171
|
val = self._cursor.fetchone()
|
|
172
|
+
# Drain any remaining rows.
|
|
137
173
|
self._cursor.fetchall()
|
|
138
174
|
return val[0] if val else default
|
|
139
175
|
|
|
140
176
|
def one(self, default=None):
|
|
177
|
+
"""
|
|
178
|
+
Return the first row or `default` if no rows.
|
|
179
|
+
"""
|
|
141
180
|
try:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return default
|
|
145
|
-
finally:
|
|
181
|
+
row = next(self)
|
|
182
|
+
# Drain remaining.
|
|
146
183
|
if self._cursor:
|
|
147
184
|
self._cursor.fetchall()
|
|
185
|
+
return row
|
|
186
|
+
except StopIteration:
|
|
187
|
+
return default
|
|
148
188
|
|
|
149
|
-
def get_table_data(self, headers=True
|
|
189
|
+
def get_table_data(self, headers=True):
|
|
190
|
+
"""
|
|
191
|
+
Builds a two-dimensional list: first row is column headers, subsequent rows are data.
|
|
192
|
+
"""
|
|
150
193
|
self.as_list()
|
|
151
194
|
rows = []
|
|
152
195
|
for row in self:
|
|
153
|
-
|
|
196
|
+
row = ["" if x is None else str(x) for x in row]
|
|
197
|
+
rows.append(row)
|
|
154
198
|
if isinstance(headers, list):
|
|
155
199
|
rows.insert(0, [x.replace("_", " ").title() for x in headers])
|
|
156
200
|
elif headers:
|
|
@@ -158,11 +202,12 @@ class Result(object):
|
|
|
158
202
|
return rows
|
|
159
203
|
|
|
160
204
|
def enum(self):
|
|
205
|
+
"""
|
|
206
|
+
Yields each row as (row_index, transformed_row).
|
|
207
|
+
"""
|
|
161
208
|
self.__enumerate = True
|
|
162
209
|
return self
|
|
163
210
|
|
|
164
|
-
enumerate = enum
|
|
165
|
-
|
|
166
211
|
@property
|
|
167
212
|
def sql(self):
|
|
168
213
|
return self.__sql
|
velocity/db/core/row.py
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import pprint
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
class Row
|
|
4
|
+
class Row:
|
|
5
|
+
"""
|
|
6
|
+
Represents a single row in a given table, identified by a primary key or a dictionary of conditions.
|
|
7
|
+
"""
|
|
8
|
+
|
|
5
9
|
def __init__(self, table, key, lock=None):
|
|
6
10
|
if isinstance(table, str):
|
|
7
|
-
raise Exception("
|
|
11
|
+
raise Exception("Table parameter must be a `table` instance.")
|
|
8
12
|
self.table = table
|
|
13
|
+
|
|
9
14
|
if isinstance(key, (dict, Row)):
|
|
10
15
|
pk = {}
|
|
11
16
|
try:
|
|
@@ -15,6 +20,7 @@ class Row(object):
|
|
|
15
20
|
pk = key
|
|
16
21
|
else:
|
|
17
22
|
pk = {self.key_cols[0]: key}
|
|
23
|
+
|
|
18
24
|
self.pk = pk
|
|
19
25
|
self.cache = key
|
|
20
26
|
if lock:
|
|
@@ -36,12 +42,12 @@ class Row(object):
|
|
|
36
42
|
|
|
37
43
|
def __setitem__(self, key, val):
|
|
38
44
|
if key in self.pk:
|
|
39
|
-
raise Exception("
|
|
45
|
+
raise Exception("Cannot update a primary key.")
|
|
40
46
|
self.table.upsert({key: val}, self.pk)
|
|
41
47
|
|
|
42
48
|
def __delitem__(self, key):
|
|
43
49
|
if key in self.pk:
|
|
44
|
-
raise Exception("
|
|
50
|
+
raise Exception("Cannot delete a primary key.")
|
|
45
51
|
if key not in self:
|
|
46
52
|
return
|
|
47
53
|
self[key] = None
|
|
@@ -50,60 +56,64 @@ class Row(object):
|
|
|
50
56
|
return key.lower() in [x.lower() for x in self.keys()]
|
|
51
57
|
|
|
52
58
|
def clear(self):
|
|
59
|
+
"""
|
|
60
|
+
Deletes this row from the database.
|
|
61
|
+
"""
|
|
53
62
|
self.table.delete(where=self.pk)
|
|
54
63
|
return self
|
|
55
64
|
|
|
56
65
|
def keys(self):
|
|
57
|
-
|
|
66
|
+
"""
|
|
67
|
+
Returns the column names in the table (including sys_ columns).
|
|
68
|
+
"""
|
|
69
|
+
return self.table.sys_columns()
|
|
58
70
|
|
|
59
71
|
def values(self, *args):
|
|
72
|
+
"""
|
|
73
|
+
Returns values from this row, optionally restricted to columns in `args`.
|
|
74
|
+
"""
|
|
60
75
|
d = self.table.select(where=self.pk).as_dict().one()
|
|
61
76
|
if args:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
values.append(d[arg])
|
|
65
|
-
return values
|
|
66
|
-
else:
|
|
67
|
-
return list(d.values())
|
|
77
|
+
return [d[arg] for arg in args]
|
|
78
|
+
return list(d.values())
|
|
68
79
|
|
|
69
80
|
def items(self):
|
|
81
|
+
"""
|
|
82
|
+
Returns (key, value) pairs for all columns.
|
|
83
|
+
"""
|
|
70
84
|
d = self.table.select(where=self.pk).as_dict().one()
|
|
71
85
|
return list(d.items())
|
|
72
86
|
|
|
73
87
|
def get(self, key, failobj=None):
|
|
74
88
|
data = self[key]
|
|
75
|
-
if data
|
|
89
|
+
if data is None:
|
|
76
90
|
return failobj
|
|
77
91
|
return data
|
|
78
92
|
|
|
79
93
|
def setdefault(self, key, default=None):
|
|
80
94
|
data = self[key]
|
|
81
|
-
if data
|
|
95
|
+
if data is None:
|
|
82
96
|
self[key] = default
|
|
83
97
|
return default
|
|
84
98
|
return data
|
|
85
99
|
|
|
86
|
-
def update(self,
|
|
100
|
+
def update(self, dict_=None, **kwds):
|
|
101
|
+
"""
|
|
102
|
+
Updates columns in this row.
|
|
103
|
+
"""
|
|
87
104
|
data = {}
|
|
88
|
-
if
|
|
89
|
-
data.update(
|
|
105
|
+
if dict_:
|
|
106
|
+
data.update(dict_)
|
|
90
107
|
if kwds:
|
|
91
108
|
data.update(kwds)
|
|
92
109
|
if data:
|
|
93
110
|
self.table.upsert(data, self.pk)
|
|
94
111
|
return self
|
|
95
112
|
|
|
96
|
-
def iterkeys(self):
|
|
97
|
-
return list(self.keys())
|
|
98
|
-
|
|
99
|
-
def itervalues(self):
|
|
100
|
-
return list(self.values())
|
|
101
|
-
|
|
102
|
-
def iteritems(self):
|
|
103
|
-
return list(self.items())
|
|
104
|
-
|
|
105
113
|
def __cmp__(self, other):
|
|
106
|
-
|
|
114
|
+
"""
|
|
115
|
+
Legacy comparison method; returns 0 if self and other share keys/values, else -1.
|
|
116
|
+
"""
|
|
107
117
|
diff = -1
|
|
108
118
|
if hasattr(other, "keys"):
|
|
109
119
|
k1 = list(self.keys())
|
|
@@ -117,18 +127,18 @@ class Row(object):
|
|
|
117
127
|
return diff
|
|
118
128
|
|
|
119
129
|
def __bool__(self):
|
|
120
|
-
return bool(self
|
|
130
|
+
return bool(len(self))
|
|
121
131
|
|
|
122
132
|
def copy(self, lock=None):
|
|
133
|
+
"""
|
|
134
|
+
Makes a copy of this row with a new sys_id, dropping sys_-prefixed columns from the new dict.
|
|
135
|
+
"""
|
|
123
136
|
old = self.to_dict()
|
|
124
|
-
for
|
|
125
|
-
if "sys_" in
|
|
126
|
-
old.pop(
|
|
137
|
+
for k in list(old.keys()):
|
|
138
|
+
if "sys_" in k:
|
|
139
|
+
old.pop(k)
|
|
127
140
|
return self.table.new(old, lock=lock)
|
|
128
141
|
|
|
129
|
-
# ================================================================
|
|
130
|
-
# This stuff is not implemented
|
|
131
|
-
|
|
132
142
|
def pop(self):
|
|
133
143
|
raise NotImplementedError
|
|
134
144
|
|
|
@@ -152,9 +162,15 @@ class Row(object):
|
|
|
152
162
|
raise NotImplementedError
|
|
153
163
|
|
|
154
164
|
def to_dict(self):
|
|
165
|
+
"""
|
|
166
|
+
Returns the row as a dictionary via a SELECT on self.pk.
|
|
167
|
+
"""
|
|
155
168
|
return self.table.select(where=self.pk).as_dict().one()
|
|
156
169
|
|
|
157
170
|
def extract(self, *args):
|
|
171
|
+
"""
|
|
172
|
+
Returns a dict containing only the specified columns from this row.
|
|
173
|
+
"""
|
|
158
174
|
data = {}
|
|
159
175
|
for key in args:
|
|
160
176
|
if isinstance(key, (tuple, list)):
|
|
@@ -165,54 +181,73 @@ class Row(object):
|
|
|
165
181
|
|
|
166
182
|
@property
|
|
167
183
|
def key_cols(self):
|
|
168
|
-
|
|
169
|
-
|
|
184
|
+
"""
|
|
185
|
+
Returns the primary key columns for the underlying table, defaulting to ['sys_id'] if missing.
|
|
186
|
+
"""
|
|
170
187
|
return ["sys_id"]
|
|
171
188
|
|
|
172
189
|
def split(self):
|
|
190
|
+
"""
|
|
191
|
+
Splits data from PK references into (non_sys_data, pk).
|
|
192
|
+
"""
|
|
173
193
|
old = self.to_dict()
|
|
174
|
-
for
|
|
175
|
-
if "sys_" in
|
|
176
|
-
old.pop(
|
|
194
|
+
for k in list(old.keys()):
|
|
195
|
+
if "sys_" in k:
|
|
196
|
+
old.pop(k)
|
|
177
197
|
return old, self.pk
|
|
178
198
|
|
|
179
199
|
@property
|
|
180
200
|
def data(self):
|
|
201
|
+
"""
|
|
202
|
+
Returns the 'non-sys' data dictionary for this row.
|
|
203
|
+
"""
|
|
181
204
|
return self.split()[0]
|
|
182
205
|
|
|
183
206
|
def row(self, key, lock=None):
|
|
184
|
-
|
|
207
|
+
"""
|
|
208
|
+
Retrieve a row from a foreign key column if present. E.g. row.row('fk_column').
|
|
209
|
+
"""
|
|
185
210
|
value = self[key]
|
|
186
211
|
if value is None:
|
|
187
212
|
return None
|
|
188
213
|
fk = self.table.foreign_key_info(key)
|
|
189
214
|
if not fk:
|
|
190
215
|
raise Exception(
|
|
191
|
-
"Column `{}` is not a foreign key in `{
|
|
216
|
+
f"Column `{key}` is not a foreign key in `{self.table.name}`"
|
|
192
217
|
)
|
|
193
|
-
return tx.Row(fk["referenced_table_name"], value, lock=lock)
|
|
218
|
+
return self.table.tx.Row(fk["referenced_table_name"], value, lock=lock)
|
|
194
219
|
|
|
195
220
|
def match(self, other):
|
|
196
|
-
|
|
197
|
-
|
|
221
|
+
"""
|
|
222
|
+
Returns True if the columns in 'other' match this row's columns for the same keys.
|
|
223
|
+
"""
|
|
224
|
+
for k in other:
|
|
225
|
+
if self[k] != other[k]:
|
|
198
226
|
return False
|
|
199
227
|
return True
|
|
200
228
|
|
|
201
229
|
def touch(self):
|
|
230
|
+
"""
|
|
231
|
+
Update sys_modified to current timestamp.
|
|
232
|
+
"""
|
|
202
233
|
self["sys_modified"] = "@@CURRENT_TIMESTAMP"
|
|
203
234
|
return self
|
|
204
235
|
|
|
205
236
|
delete = clear
|
|
206
237
|
|
|
207
238
|
def lock(self):
|
|
239
|
+
"""
|
|
240
|
+
SELECT ... FOR UPDATE on this row.
|
|
241
|
+
"""
|
|
208
242
|
self.table.select(where=self.pk, lock=True)
|
|
209
243
|
return self
|
|
210
244
|
|
|
211
245
|
def notBlank(self, key, failobj=None):
|
|
246
|
+
"""
|
|
247
|
+
Returns the value if it is not blank, else failobj.
|
|
248
|
+
"""
|
|
212
249
|
data = self[key]
|
|
213
|
-
if
|
|
214
|
-
return failobj
|
|
215
|
-
return data
|
|
250
|
+
return data if data else failobj
|
|
216
251
|
|
|
217
252
|
getBlank = notBlank
|
|
218
253
|
|
velocity/db/core/sequence.py
CHANGED
|
@@ -1,41 +1,131 @@
|
|
|
1
|
-
|
|
1
|
+
import psycopg2
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Sequence:
|
|
5
|
+
"""
|
|
6
|
+
Represents a database sequence in PostgreSQL.
|
|
7
|
+
"""
|
|
8
|
+
|
|
2
9
|
def __init__(self, tx, name, start=1000):
|
|
3
10
|
self.tx = tx
|
|
4
11
|
self.name = name.lower()
|
|
5
12
|
self.sql = tx.engine.sql
|
|
6
13
|
self.start = start
|
|
7
14
|
|
|
8
|
-
self.create()
|
|
9
|
-
|
|
10
15
|
def __str__(self):
|
|
11
|
-
return ""
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
sql
|
|
19
|
-
|
|
20
|
-
tuple(),
|
|
21
|
-
)
|
|
16
|
+
return f"Sequence: {self.name} (current val: {self.current()})"
|
|
17
|
+
|
|
18
|
+
def create(self, start=None):
|
|
19
|
+
"""
|
|
20
|
+
Creates the sequence if it does not already exist.
|
|
21
|
+
"""
|
|
22
|
+
val = start if start is not None else self.start
|
|
23
|
+
sql = f"CREATE SEQUENCE IF NOT EXISTS {self.name} START {val};"
|
|
24
|
+
vals = ()
|
|
22
25
|
return self.tx.execute(sql, vals)
|
|
23
26
|
|
|
24
27
|
def next(self):
|
|
25
|
-
|
|
28
|
+
"""
|
|
29
|
+
Retrieves the next value in this sequence.
|
|
30
|
+
"""
|
|
31
|
+
sql = f"SELECT nextval('{self.name}');"
|
|
32
|
+
vals = ()
|
|
26
33
|
return self.tx.execute(sql, vals).scalar()
|
|
27
34
|
|
|
28
35
|
def current(self):
|
|
29
|
-
|
|
36
|
+
"""
|
|
37
|
+
Retrieves the current value of the sequence.
|
|
38
|
+
"""
|
|
39
|
+
sql = f"SELECT currval('{self.name}');"
|
|
40
|
+
vals = ()
|
|
30
41
|
return self.tx.execute(sql, vals).scalar()
|
|
31
42
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
def safe_current(self):
|
|
44
|
+
"""
|
|
45
|
+
Returns the current value of the sequence if one has been generated
|
|
46
|
+
in this session, or None otherwise.
|
|
47
|
+
"""
|
|
48
|
+
sql = f"SELECT currval('{self.name}');"
|
|
49
|
+
try:
|
|
50
|
+
return self.tx.execute(sql, ()).scalar()
|
|
51
|
+
except psycopg2.ProgrammingError:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
def set_value(self, start=None):
|
|
55
|
+
"""
|
|
56
|
+
Resets the sequence to the given start value (defaults to initial `self.start`).
|
|
57
|
+
"""
|
|
58
|
+
val = start if start is not None else self.start
|
|
59
|
+
sql = f"ALTER SEQUENCE {self.name} RESTART WITH {val};"
|
|
60
|
+
vals = ()
|
|
37
61
|
return self.tx.execute(sql, vals).scalar()
|
|
38
62
|
|
|
63
|
+
reset = set_value
|
|
64
|
+
|
|
39
65
|
def drop(self):
|
|
40
|
-
|
|
66
|
+
"""
|
|
67
|
+
Drops the sequence if it exists.
|
|
68
|
+
"""
|
|
69
|
+
sql = f"DROP SEQUENCE IF EXISTS {self.name};"
|
|
70
|
+
vals = ()
|
|
41
71
|
return self.tx.execute(sql, vals)
|
|
72
|
+
|
|
73
|
+
def exists(self):
|
|
74
|
+
"""
|
|
75
|
+
Checks whether the sequence exists in the database.
|
|
76
|
+
Returns True if it exists, False otherwise.
|
|
77
|
+
"""
|
|
78
|
+
sql = f"""
|
|
79
|
+
SELECT EXISTS (
|
|
80
|
+
SELECT 1
|
|
81
|
+
FROM pg_class
|
|
82
|
+
WHERE relname = '{self.name}'
|
|
83
|
+
AND relkind = 'S'
|
|
84
|
+
);
|
|
85
|
+
"""
|
|
86
|
+
return self.tx.execute(sql, ()).scalar()
|
|
87
|
+
|
|
88
|
+
def configure(self, increment=None, minvalue=None, maxvalue=None, cycle=None):
|
|
89
|
+
"""
|
|
90
|
+
Alter the sequence with the given settings (any of which may be None to skip).
|
|
91
|
+
"""
|
|
92
|
+
parts = []
|
|
93
|
+
if increment is not None:
|
|
94
|
+
parts.append(f"INCREMENT BY {increment}")
|
|
95
|
+
if minvalue is not None:
|
|
96
|
+
parts.append(f"MINVALUE {minvalue}")
|
|
97
|
+
if maxvalue is not None:
|
|
98
|
+
parts.append(f"MAXVALUE {maxvalue}")
|
|
99
|
+
if cycle is True:
|
|
100
|
+
parts.append("CYCLE")
|
|
101
|
+
elif cycle is False:
|
|
102
|
+
parts.append("NO CYCLE")
|
|
103
|
+
|
|
104
|
+
if not parts:
|
|
105
|
+
return None # no-op
|
|
106
|
+
|
|
107
|
+
sql = f"ALTER SEQUENCE {self.name} {' '.join(parts)};"
|
|
108
|
+
return self.tx.execute(sql, ()).scalar()
|
|
109
|
+
|
|
110
|
+
def info(self):
|
|
111
|
+
"""
|
|
112
|
+
Returns a dictionary of metadata about the sequence, or None if it doesn't exist.
|
|
113
|
+
"""
|
|
114
|
+
sql = f"""
|
|
115
|
+
SELECT *
|
|
116
|
+
FROM pg_sequences
|
|
117
|
+
WHERE schemaname = current_schema()
|
|
118
|
+
AND sequencename = '{self.name}'
|
|
119
|
+
"""
|
|
120
|
+
row = self.tx.execute(sql, ()).fetchone()
|
|
121
|
+
if row is None:
|
|
122
|
+
return None
|
|
123
|
+
return dict(row)
|
|
124
|
+
|
|
125
|
+
def rename(self, new_name):
|
|
126
|
+
"""
|
|
127
|
+
Renames this sequence to `new_name`. Updates self.name to the new name.
|
|
128
|
+
"""
|
|
129
|
+
sql = f"ALTER SEQUENCE {self.name} RENAME TO {new_name.lower()};"
|
|
130
|
+
self.tx.execute(sql, ())
|
|
131
|
+
self.name = new_name.lower()
|