macrostrat.database 3.5.0__tar.gz → 3.5.2__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.
- {macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/PKG-INFO +1 -1
- {macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/__init__.py +26 -2
- {macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/transfer/dump_database.py +1 -0
- {macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/transfer/move_tables.py +1 -0
- {macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/transfer/restore_database.py +1 -0
- {macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/transfer/stream_utils.py +1 -0
- {macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/transfer/utils.py +1 -1
- {macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/utils.py +90 -48
- {macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/pyproject.toml +1 -1
- {macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/mapper/__init__.py +0 -0
- {macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/mapper/base.py +0 -0
- {macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/mapper/cache.py +0 -0
- {macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/mapper/utils.py +0 -0
- {macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/postgresql.py +0 -0
- {macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/transfer/__init__.py +0 -0
|
@@ -5,13 +5,14 @@ from typing import Optional, Union
|
|
|
5
5
|
|
|
6
6
|
from psycopg2.errors import InvalidSavepointSpecification
|
|
7
7
|
from psycopg2.sql import Identifier
|
|
8
|
-
from sqlalchemy import URL, MetaData, create_engine, inspect
|
|
8
|
+
from sqlalchemy import URL, Engine, MetaData, create_engine, inspect
|
|
9
9
|
from sqlalchemy.exc import IntegrityError, InternalError
|
|
10
10
|
from sqlalchemy.ext.compiler import compiles
|
|
11
11
|
from sqlalchemy.orm import Session, scoped_session, sessionmaker
|
|
12
12
|
from sqlalchemy.sql.expression import Insert
|
|
13
13
|
|
|
14
14
|
from macrostrat.utils import get_logger
|
|
15
|
+
|
|
15
16
|
from .mapper import DatabaseMapper
|
|
16
17
|
from .postgresql import on_conflict, prefix_inserts # noqa
|
|
17
18
|
from .utils import ( # noqa
|
|
@@ -128,7 +129,7 @@ class Database(object):
|
|
|
128
129
|
Returns: Iterator of results from the query.
|
|
129
130
|
"""
|
|
130
131
|
params = self._setup_params(params, kwargs)
|
|
131
|
-
return
|
|
132
|
+
return run_sql(self.session, fn, params, **kwargs)
|
|
132
133
|
|
|
133
134
|
def run_query(self, sql, params=None, **kwargs):
|
|
134
135
|
"""Run a single query on the database object, returning the result.
|
|
@@ -184,6 +185,29 @@ class Database(object):
|
|
|
184
185
|
self.__inspector__ = inspect(self.engine)
|
|
185
186
|
return self.__inspector__
|
|
186
187
|
|
|
188
|
+
def refresh_schema(self, *, automap=None):
|
|
189
|
+
"""
|
|
190
|
+
Refresh the current database connection
|
|
191
|
+
|
|
192
|
+
- closes the session and flushes
|
|
193
|
+
- removes the inspector
|
|
194
|
+
|
|
195
|
+
If automap is True, will automap the database after refreshing.
|
|
196
|
+
If automap is False, will not automap the database after refreshing.
|
|
197
|
+
If automap is None, it will re-map the database if it was previously mapped.
|
|
198
|
+
"""
|
|
199
|
+
# Close the session
|
|
200
|
+
self.session.flush()
|
|
201
|
+
self.session.close()
|
|
202
|
+
# Remove the inspector
|
|
203
|
+
self.__inspector__ = None
|
|
204
|
+
|
|
205
|
+
if automap is None:
|
|
206
|
+
automap = self.mapper is not None
|
|
207
|
+
|
|
208
|
+
if automap:
|
|
209
|
+
self.automap()
|
|
210
|
+
|
|
187
211
|
def entity_names(self, **kwargs):
|
|
188
212
|
"""
|
|
189
213
|
Returns an iterator of names of *schema objects*
|
{macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/transfer/utils.py
RENAMED
|
@@ -5,7 +5,7 @@ from sqlalchemy.engine import Engine
|
|
|
5
5
|
from sqlalchemy.engine.url import URL
|
|
6
6
|
from sqlalchemy_utils import create_database, database_exists, drop_database
|
|
7
7
|
|
|
8
|
-
from macrostrat.utils import
|
|
8
|
+
from macrostrat.utils import ApplicationError, get_logger
|
|
9
9
|
|
|
10
10
|
console = Console()
|
|
11
11
|
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
from contextlib import contextmanager
|
|
2
|
+
from enum import Enum
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from re import search
|
|
5
|
+
from sys import stderr
|
|
4
6
|
from time import sleep
|
|
5
7
|
from typing import IO, Union
|
|
6
8
|
from warnings import warn
|
|
7
9
|
|
|
10
|
+
import psycopg2.errors
|
|
8
11
|
from click import echo, secho
|
|
9
12
|
from psycopg2.extensions import set_wait_callback
|
|
10
13
|
from psycopg2.extras import wait_select
|
|
11
14
|
from psycopg2.sql import SQL, Composable, Composed
|
|
12
|
-
import psycopg2.errors
|
|
13
15
|
from rich.console import Console
|
|
14
16
|
from sqlalchemy import MetaData, create_engine, text
|
|
15
17
|
from sqlalchemy.engine import Connection, Engine
|
|
@@ -17,8 +19,8 @@ from sqlalchemy.exc import (
|
|
|
17
19
|
IntegrityError,
|
|
18
20
|
InternalError,
|
|
19
21
|
InvalidRequestError,
|
|
22
|
+
OperationalError,
|
|
20
23
|
ProgrammingError,
|
|
21
|
-
OperationalError
|
|
22
24
|
)
|
|
23
25
|
from sqlalchemy.orm import sessionmaker
|
|
24
26
|
from sqlalchemy.schema import Table
|
|
@@ -46,23 +48,11 @@ def infer_is_sql_text(_string: str) -> bool:
|
|
|
46
48
|
if isinstance(_string, bytes):
|
|
47
49
|
_string = _string.decode("utf-8")
|
|
48
50
|
|
|
49
|
-
keywords = [
|
|
50
|
-
"SELECT",
|
|
51
|
-
"INSERT",
|
|
52
|
-
"UPDATE",
|
|
53
|
-
"CREATE",
|
|
54
|
-
"DROP",
|
|
55
|
-
"DELETE",
|
|
56
|
-
"ALTER",
|
|
57
|
-
"SET",
|
|
58
|
-
"GRANT",
|
|
59
|
-
"WITH",
|
|
60
|
-
]
|
|
61
51
|
lines = _string.split("\n")
|
|
62
52
|
if len(lines) > 1:
|
|
63
53
|
return True
|
|
64
54
|
_string = _string.lower()
|
|
65
|
-
for i in
|
|
55
|
+
for i in _sql_keywords:
|
|
66
56
|
if _string.strip().startswith(i.lower() + " "):
|
|
67
57
|
return True
|
|
68
58
|
return False
|
|
@@ -97,26 +87,40 @@ def get_dataframe(connectable, filename_or_query, **kwargs):
|
|
|
97
87
|
|
|
98
88
|
|
|
99
89
|
def pretty_print(sql, **kwargs):
|
|
90
|
+
"""Print and optionally summarize an SQL query"""
|
|
91
|
+
summarize = kwargs.pop("summarize", True)
|
|
92
|
+
if summarize:
|
|
93
|
+
sql = summarize_statement(sql)
|
|
94
|
+
secho(sql, **kwargs)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
_sql_keywords = [
|
|
98
|
+
"SELECT",
|
|
99
|
+
"INSERT",
|
|
100
|
+
"UPDATE",
|
|
101
|
+
"CREATE",
|
|
102
|
+
"DROP",
|
|
103
|
+
"DELETE",
|
|
104
|
+
"ALTER",
|
|
105
|
+
"SET",
|
|
106
|
+
"GRANT",
|
|
107
|
+
"WITH",
|
|
108
|
+
"NOTIFY",
|
|
109
|
+
"COPY",
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def summarize_statement(sql):
|
|
100
114
|
for line in sql.split("\n"):
|
|
101
|
-
for i in
|
|
102
|
-
"SELECT",
|
|
103
|
-
"INSERT",
|
|
104
|
-
"UPDATE",
|
|
105
|
-
"CREATE",
|
|
106
|
-
"DROP",
|
|
107
|
-
"DELETE",
|
|
108
|
-
"ALTER",
|
|
109
|
-
"SET",
|
|
110
|
-
"GRANT",
|
|
111
|
-
"WITH",
|
|
112
|
-
"NOTIFY",
|
|
113
|
-
"COPY",
|
|
114
|
-
]:
|
|
115
|
+
for i in _sql_keywords:
|
|
115
116
|
if not line.startswith(i):
|
|
116
117
|
continue
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
return line.split("(")[0].strip().rstrip(";").replace(" AS", "")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class DevNull(object):
|
|
122
|
+
def write(self, *_):
|
|
123
|
+
pass
|
|
120
124
|
|
|
121
125
|
|
|
122
126
|
def get_sql_text(sql, interpret_as_file=None, echo_file_name=True):
|
|
@@ -232,6 +236,16 @@ def infer_has_server_binds(sql):
|
|
|
232
236
|
return "%s" in sql or search(r"%\(\w+\)s", sql)
|
|
233
237
|
|
|
234
238
|
|
|
239
|
+
_default_statement_filter = lambda sql_text, params: True
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class PrintMode(Enum):
|
|
243
|
+
NONE = "none"
|
|
244
|
+
ERRORS = "errors"
|
|
245
|
+
SUMMARY = "summary"
|
|
246
|
+
ALL = "all"
|
|
247
|
+
|
|
248
|
+
|
|
235
249
|
def _run_sql(connectable, sql, params=None, **kwargs):
|
|
236
250
|
"""
|
|
237
251
|
Internal function for running a query on a SQLAlchemy connectable,
|
|
@@ -247,6 +261,12 @@ def _run_sql(connectable, sql, params=None, **kwargs):
|
|
|
247
261
|
raise_errors = kwargs.pop("raise_errors", False)
|
|
248
262
|
has_server_binds = kwargs.pop("has_server_binds", None)
|
|
249
263
|
ensure_single_query = kwargs.pop("ensure_single_query", False)
|
|
264
|
+
statement_filter = kwargs.pop("statement_filter", _default_statement_filter)
|
|
265
|
+
output_mode = kwargs.pop("output_mode", PrintMode.SUMMARY)
|
|
266
|
+
output_file = kwargs.pop("output_file", stderr)
|
|
267
|
+
|
|
268
|
+
if output_mode == PrintMode.NONE:
|
|
269
|
+
output_file = DevNull()
|
|
250
270
|
|
|
251
271
|
if stop_on_error:
|
|
252
272
|
raise_errors = True
|
|
@@ -279,6 +299,7 @@ def _run_sql(connectable, sql, params=None, **kwargs):
|
|
|
279
299
|
query = _render_query(query, connectable)
|
|
280
300
|
|
|
281
301
|
sql_text = str(query)
|
|
302
|
+
|
|
282
303
|
if isinstance(query, str):
|
|
283
304
|
sql_text = format(query, strip_comments=True).strip()
|
|
284
305
|
if sql_text == "":
|
|
@@ -288,6 +309,21 @@ def _run_sql(connectable, sql, params=None, **kwargs):
|
|
|
288
309
|
if has_server_binds is None:
|
|
289
310
|
has_server_binds = infer_has_server_binds(sql_text)
|
|
290
311
|
|
|
312
|
+
should_run = statement_filter(sql_text, params)
|
|
313
|
+
|
|
314
|
+
# Shorten summary text for printing
|
|
315
|
+
if output_mode != PrintMode.ALL:
|
|
316
|
+
sql_text = summarize_statement(sql_text)
|
|
317
|
+
|
|
318
|
+
if not should_run:
|
|
319
|
+
secho(
|
|
320
|
+
sql_text,
|
|
321
|
+
dim=True,
|
|
322
|
+
strikethrough=True,
|
|
323
|
+
file=output_file,
|
|
324
|
+
)
|
|
325
|
+
continue
|
|
326
|
+
|
|
291
327
|
# This only does something for postgresql, but it's harmless to run it for other engines
|
|
292
328
|
set_wait_callback(wait_select)
|
|
293
329
|
|
|
@@ -309,7 +345,7 @@ def _run_sql(connectable, sql, params=None, **kwargs):
|
|
|
309
345
|
trans.commit()
|
|
310
346
|
elif hasattr(connectable, "commit"):
|
|
311
347
|
connectable.commit()
|
|
312
|
-
|
|
348
|
+
secho(sql_text, dim=True, file=output_file)
|
|
313
349
|
except Exception as err:
|
|
314
350
|
if trans is not None:
|
|
315
351
|
trans.rollback()
|
|
@@ -318,14 +354,16 @@ def _run_sql(connectable, sql, params=None, **kwargs):
|
|
|
318
354
|
if raise_errors or _should_raise_query_error(err):
|
|
319
355
|
raise err
|
|
320
356
|
|
|
321
|
-
_print_error(sql_text, err)
|
|
357
|
+
_print_error(sql_text, err, file=output_file)
|
|
322
358
|
finally:
|
|
323
359
|
set_wait_callback(None)
|
|
324
360
|
|
|
325
361
|
|
|
326
362
|
def _should_raise_query_error(err):
|
|
327
363
|
"""Determine if an error should be raised for a query or not."""
|
|
328
|
-
if not isinstance(
|
|
364
|
+
if not isinstance(
|
|
365
|
+
err, (ProgrammingError, IntegrityError, InternalError, OperationalError)
|
|
366
|
+
):
|
|
329
367
|
return True
|
|
330
368
|
|
|
331
369
|
orig_err = getattr(err, "orig", None)
|
|
@@ -336,23 +374,27 @@ def _should_raise_query_error(err):
|
|
|
336
374
|
# We might want to change this behavior in the future, or support more graceful handling of errors from other
|
|
337
375
|
# database backends.
|
|
338
376
|
# Ideally we could handle operational errors more gracefully
|
|
339
|
-
if
|
|
377
|
+
if (
|
|
378
|
+
isinstance(orig_err, psycopg2.errors.QueryCanceled)
|
|
379
|
+
or getattr(orig_err, "pgcode", None) == "57014"
|
|
380
|
+
):
|
|
340
381
|
return True
|
|
341
382
|
|
|
342
383
|
return False
|
|
343
384
|
|
|
344
385
|
|
|
345
|
-
def _print_error(sql_text, err):
|
|
386
|
+
def _print_error(sql_text, err, **kwargs):
|
|
346
387
|
if orig := getattr(err, "orig", None):
|
|
347
388
|
_err = str(orig)
|
|
348
389
|
else:
|
|
349
390
|
_err = str(err)
|
|
350
391
|
_err = _err.strip()
|
|
351
|
-
|
|
352
|
-
|
|
392
|
+
# Decide whether error should be dimmed
|
|
393
|
+
dim = kwargs.pop("dim", "already exists" in _err)
|
|
394
|
+
secho(sql_text, fg=None if dim else "red", dim=True, **kwargs)
|
|
353
395
|
if dim:
|
|
354
396
|
_err = " " + _err
|
|
355
|
-
secho(_err, fg="red", dim=dim)
|
|
397
|
+
secho(_err, fg="red", dim=dim, **kwargs)
|
|
356
398
|
log.error(err)
|
|
357
399
|
|
|
358
400
|
|
|
@@ -444,6 +486,9 @@ def run_sql(*args, **kwargs):
|
|
|
444
486
|
returning a list after completion.
|
|
445
487
|
ensure_single_query : bool
|
|
446
488
|
If True, raise an error if multiple queries are passed when only one is expected.
|
|
489
|
+
statement_filter : Callable
|
|
490
|
+
A function that takes a SQL statement and parameters and returns True if the statement
|
|
491
|
+
should be run, and False if it should be skipped.
|
|
447
492
|
"""
|
|
448
493
|
res = _run_sql(*args, **kwargs)
|
|
449
494
|
if kwargs.pop("yield_results", False):
|
|
@@ -451,7 +496,9 @@ def run_sql(*args, **kwargs):
|
|
|
451
496
|
return list(res)
|
|
452
497
|
|
|
453
498
|
|
|
454
|
-
def execute(connectable, sql, params=None, stop_on_error=False):
|
|
499
|
+
def execute(connectable, sql, params=None, stop_on_error=False, **kwargs):
|
|
500
|
+
output_file = kwargs.pop("output_file", None)
|
|
501
|
+
output_mode = kwargs.pop("output_mode", None)
|
|
455
502
|
sql = format(sql, strip_comments=True).strip()
|
|
456
503
|
if sql == "":
|
|
457
504
|
return
|
|
@@ -460,17 +507,12 @@ def execute(connectable, sql, params=None, stop_on_error=False):
|
|
|
460
507
|
res = connectable.execute(text(sql), params=params)
|
|
461
508
|
if hasattr(connectable, "commit"):
|
|
462
509
|
connectable.commit()
|
|
463
|
-
pretty_print(sql, dim=True)
|
|
510
|
+
pretty_print(sql, dim=True, file=output_file, mode=output_mode)
|
|
464
511
|
return res
|
|
465
512
|
except (ProgrammingError, IntegrityError) as err:
|
|
466
|
-
err = str(err.orig).strip()
|
|
467
|
-
dim = "already exists" in err
|
|
468
513
|
if hasattr(connectable, "rollback"):
|
|
469
514
|
connectable.rollback()
|
|
470
|
-
|
|
471
|
-
if dim:
|
|
472
|
-
err = " " + err
|
|
473
|
-
secho(err, fg="red", dim=dim)
|
|
515
|
+
_print_error(sql, dim=True, file=output_file, mode=output_mode)
|
|
474
516
|
if stop_on_error:
|
|
475
517
|
return
|
|
476
518
|
finally:
|
|
@@ -3,7 +3,7 @@ authors = ["Daven Quinn <dev@davenquinn.com>"]
|
|
|
3
3
|
description = "A SQLAlchemy-based database toolkit."
|
|
4
4
|
name = "macrostrat.database"
|
|
5
5
|
packages = [{ include = "macrostrat" }]
|
|
6
|
-
version = "3.5.
|
|
6
|
+
version = "3.5.2"
|
|
7
7
|
|
|
8
8
|
[tool.poetry.dependencies]
|
|
9
9
|
GeoAlchemy2 = "^0.15.2"
|
{macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/mapper/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{macrostrat_database-3.5.0 → macrostrat_database-3.5.2}/macrostrat/database/transfer/__init__.py
RENAMED
|
File without changes
|