macrostrat.database 3.5.3__tar.gz → 4.0.0__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.3 → macrostrat_database-4.0.0}/PKG-INFO +5 -3
- {macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/__init__.py +9 -12
- {macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/postgresql.py +5 -4
- {macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/utils.py +54 -19
- {macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/pyproject.toml +4 -3
- {macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/mapper/__init__.py +0 -0
- {macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/mapper/base.py +0 -0
- {macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/mapper/cache.py +0 -0
- {macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/mapper/utils.py +0 -0
- {macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/transfer/__init__.py +0 -0
- {macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/transfer/dump_database.py +0 -0
- {macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/transfer/move_tables.py +0 -0
- {macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/transfer/restore_database.py +0 -0
- {macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/transfer/stream_utils.py +0 -0
- {macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/transfer/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: macrostrat.database
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.0
|
|
4
4
|
Summary: A SQLAlchemy-based database toolkit.
|
|
5
5
|
Author: Daven Quinn
|
|
6
6
|
Author-email: dev@davenquinn.com
|
|
@@ -9,12 +9,14 @@ Classifier: Programming Language :: Python :: 3
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.10
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
13
|
Requires-Dist: GeoAlchemy2 (>=0.15.2,<0.16.0)
|
|
13
14
|
Requires-Dist: SQLAlchemy (>=2.0.18,<3.0.0)
|
|
14
15
|
Requires-Dist: SQLAlchemy-Utils (>=0.41.1,<0.42.0)
|
|
15
16
|
Requires-Dist: aiofiles (>=23.2.1,<24.0.0)
|
|
16
17
|
Requires-Dist: click (>=8.1.3,<9.0.0)
|
|
17
18
|
Requires-Dist: macrostrat.utils (>=1.3.0,<2.0.0)
|
|
18
|
-
Requires-Dist:
|
|
19
|
+
Requires-Dist: psycopg (>=3.2.1,<4.0.0)
|
|
20
|
+
Requires-Dist: psycopg2 (>=2.9.11,<3.0.0)
|
|
19
21
|
Requires-Dist: rich (>=13.7.1,<14.0.0)
|
|
20
22
|
Requires-Dist: sqlparse (>=0.5.1,<0.6.0)
|
|
@@ -3,16 +3,15 @@ from contextlib import contextmanager
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Optional, Union
|
|
5
5
|
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from sqlalchemy import URL, Engine, MetaData,
|
|
9
|
-
from sqlalchemy.exc import IntegrityError,
|
|
6
|
+
from psycopg.errors import InvalidSavepointSpecification
|
|
7
|
+
from psycopg.sql import Identifier
|
|
8
|
+
from sqlalchemy import URL, Engine, MetaData, inspect
|
|
9
|
+
from sqlalchemy.exc import IntegrityError, OperationalError
|
|
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
|
-
|
|
16
15
|
from .mapper import DatabaseMapper
|
|
17
16
|
from .postgresql import on_conflict, prefix_inserts # noqa
|
|
18
17
|
from .utils import ( # noqa
|
|
@@ -25,6 +24,7 @@ from .utils import ( # noqa
|
|
|
25
24
|
run_fixtures,
|
|
26
25
|
run_query,
|
|
27
26
|
run_sql,
|
|
27
|
+
create_engine,
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
metadata = MetaData()
|
|
@@ -60,12 +60,8 @@ class Database(object):
|
|
|
60
60
|
|
|
61
61
|
self.instance_params = kwargs.pop("instance_params", {})
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
self.engine = db_conn
|
|
66
|
-
else:
|
|
67
|
-
log.info(f"Setting up database connection with URL '{db_conn}'")
|
|
68
|
-
self.engine = create_engine(db_conn, echo=echo_sql, **kwargs)
|
|
63
|
+
self.engine = create_engine(db_conn, echo=echo_sql, **kwargs)
|
|
64
|
+
|
|
69
65
|
self.metadata = kwargs.get("metadata", metadata)
|
|
70
66
|
|
|
71
67
|
# Scoped session for database
|
|
@@ -334,6 +330,7 @@ class Database(object):
|
|
|
334
330
|
|
|
335
331
|
if connection is None:
|
|
336
332
|
connection = self.session.connection()
|
|
333
|
+
|
|
337
334
|
params = {"name": Identifier(name)}
|
|
338
335
|
run_query(connection, "SAVEPOINT {name}", params)
|
|
339
336
|
should_rollback = rollback == "always"
|
|
@@ -356,7 +353,7 @@ def _clear_savepoint(connection, name, rollback=True):
|
|
|
356
353
|
run_query(connection, "ROLLBACK TO SAVEPOINT {name}", params)
|
|
357
354
|
else:
|
|
358
355
|
run_query(connection, "RELEASE SAVEPOINT {name}", params)
|
|
359
|
-
except
|
|
356
|
+
except OperationalError as err:
|
|
360
357
|
if isinstance(err.orig, InvalidSavepointSpecification):
|
|
361
358
|
log.warning(
|
|
362
359
|
f"Savepoint {name} does not exist; we may have already rolled back."
|
|
@@ -4,11 +4,11 @@ from contextlib import contextmanager
|
|
|
4
4
|
from contextvars import ContextVar
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
|
-
import psycopg2
|
|
8
7
|
from sqlalchemy.dialects import postgresql
|
|
9
8
|
from sqlalchemy.exc import CompileError
|
|
10
9
|
from sqlalchemy.ext.compiler import compiles
|
|
11
|
-
from sqlalchemy.sql.
|
|
10
|
+
from sqlalchemy.sql.dml import Insert
|
|
11
|
+
from sqlalchemy.sql.expression import text
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from ..database import Database
|
|
@@ -26,9 +26,10 @@ def on_conflict(action="restrict"):
|
|
|
26
26
|
_insert_mode.reset(token)
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
@compiles(Insert, "postgresql")
|
|
30
30
|
def prefix_inserts(insert, compiler, **kw):
|
|
31
31
|
"""Conditionally adapt insert statements to use on-conflict resolution (a PostgreSQL feature)"""
|
|
32
|
+
|
|
32
33
|
if insert._post_values_clause is not None:
|
|
33
34
|
return compiler.visit_insert(insert, **kw)
|
|
34
35
|
|
|
@@ -63,7 +64,7 @@ def prefix_inserts(insert, compiler, **kw):
|
|
|
63
64
|
def table_exists(db: Database, table_name: str, schema: str = "public") -> bool:
|
|
64
65
|
"""Check if a table exists in a PostgreSQL database."""
|
|
65
66
|
sql = """SELECT EXISTS (
|
|
66
|
-
SELECT FROM information_schema.tables
|
|
67
|
+
SELECT FROM information_schema.tables
|
|
67
68
|
WHERE table_schema = :schema
|
|
68
69
|
AND table_name = :table_name
|
|
69
70
|
);"""
|
|
@@ -4,18 +4,17 @@ from enum import Enum
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from re import search
|
|
6
6
|
from sys import stderr
|
|
7
|
-
from time import sleep
|
|
8
7
|
from typing import IO, Union
|
|
9
8
|
from warnings import warn
|
|
10
9
|
|
|
11
|
-
import psycopg2.errors
|
|
12
10
|
from click import echo, secho
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
from psycopg2.sql import SQL, Composable, Composed
|
|
11
|
+
from psycopg.errors import QueryCanceled
|
|
12
|
+
from psycopg.sql import SQL, Composable, Composed
|
|
16
13
|
from rich.console import Console
|
|
17
|
-
from sqlalchemy import MetaData,
|
|
14
|
+
from sqlalchemy import MetaData, text
|
|
15
|
+
from sqlalchemy import create_engine as base_create_engine
|
|
18
16
|
from sqlalchemy.engine import Connection, Engine
|
|
17
|
+
from sqlalchemy.engine.url import make_url
|
|
19
18
|
from sqlalchemy.exc import (
|
|
20
19
|
IntegrityError,
|
|
21
20
|
InternalError,
|
|
@@ -29,6 +28,7 @@ from sqlalchemy.sql.elements import ClauseElement, TextClause
|
|
|
29
28
|
from sqlalchemy_utils import create_database as _create_database
|
|
30
29
|
from sqlalchemy_utils import database_exists, drop_database
|
|
31
30
|
from sqlparse import format, split
|
|
31
|
+
from time import sleep
|
|
32
32
|
|
|
33
33
|
from macrostrat.utils import cmd, get_logger
|
|
34
34
|
|
|
@@ -196,6 +196,8 @@ def _get_cursor(connectable):
|
|
|
196
196
|
while hasattr(conn, "driver_connection") or hasattr(conn, "connection"):
|
|
197
197
|
if hasattr(conn, "driver_connection"):
|
|
198
198
|
conn = conn.driver_connection
|
|
199
|
+
elif conn.connection == conn:
|
|
200
|
+
break
|
|
199
201
|
else:
|
|
200
202
|
conn = conn.connection
|
|
201
203
|
if callable(conn):
|
|
@@ -296,7 +298,7 @@ def _run_sql(connectable, sql, params=None, **kwargs):
|
|
|
296
298
|
if pre_bind_params is not None:
|
|
297
299
|
if not isinstance(query, SQL):
|
|
298
300
|
query = SQL(query)
|
|
299
|
-
# Pre-bind the parameters using
|
|
301
|
+
# Pre-bind the parameters using psycopg
|
|
300
302
|
query = query.format(**pre_bind_params)
|
|
301
303
|
|
|
302
304
|
if isinstance(query, (SQL, Composed)):
|
|
@@ -328,9 +330,6 @@ def _run_sql(connectable, sql, params=None, **kwargs):
|
|
|
328
330
|
)
|
|
329
331
|
continue
|
|
330
332
|
|
|
331
|
-
# This only does something for postgresql, but it's harmless to run it for other engines
|
|
332
|
-
set_wait_callback(wait_select)
|
|
333
|
-
|
|
334
333
|
try:
|
|
335
334
|
trans = connectable.begin()
|
|
336
335
|
except InvalidRequestError:
|
|
@@ -360,7 +359,7 @@ def _run_sql(connectable, sql, params=None, **kwargs):
|
|
|
360
359
|
|
|
361
360
|
_print_error(sql_text, err, file=output_file)
|
|
362
361
|
finally:
|
|
363
|
-
|
|
362
|
+
pass
|
|
364
363
|
|
|
365
364
|
|
|
366
365
|
def _should_raise_query_error(err):
|
|
@@ -379,7 +378,7 @@ def _should_raise_query_error(err):
|
|
|
379
378
|
# database backends.
|
|
380
379
|
# Ideally we could handle operational errors more gracefully
|
|
381
380
|
if (
|
|
382
|
-
isinstance(orig_err,
|
|
381
|
+
isinstance(orig_err, QueryCanceled)
|
|
383
382
|
or getattr(orig_err, "pgcode", None) == "57014"
|
|
384
383
|
):
|
|
385
384
|
return True
|
|
@@ -463,7 +462,14 @@ def run_fixtures(connectable, fixtures: Union[Path, list[Path]], params=None, **
|
|
|
463
462
|
for fixture in files:
|
|
464
463
|
fn = fixture.relative_to(prefix)
|
|
465
464
|
console.print(f"[cyan bold]{fn}[/]")
|
|
466
|
-
run_sql_file(
|
|
465
|
+
run_sql_file(
|
|
466
|
+
connectable,
|
|
467
|
+
fixture,
|
|
468
|
+
params,
|
|
469
|
+
output_mode=output_mode,
|
|
470
|
+
output_file=output_file,
|
|
471
|
+
**kwargs,
|
|
472
|
+
)
|
|
467
473
|
console.print()
|
|
468
474
|
|
|
469
475
|
|
|
@@ -595,6 +601,26 @@ def create_database(url, **kwargs):
|
|
|
595
601
|
_create_database(url, **kwargs)
|
|
596
602
|
|
|
597
603
|
|
|
604
|
+
def create_engine(db_conn, **kwargs):
|
|
605
|
+
if isinstance(db_conn, Engine):
|
|
606
|
+
log.info(f"Set up database connection with engine {db_conn.url}")
|
|
607
|
+
if db_conn.driver == "psycopg2":
|
|
608
|
+
log.warning(
|
|
609
|
+
"The psycopg2 driver is deprecated. Please use psycopg3 instead."
|
|
610
|
+
)
|
|
611
|
+
return db_conn
|
|
612
|
+
else:
|
|
613
|
+
log.info(f"Setting up database connection with URL '{db_conn}'")
|
|
614
|
+
url = db_conn
|
|
615
|
+
if isinstance(url, str):
|
|
616
|
+
url = make_url(url)
|
|
617
|
+
# Set the driver to psycopg if not already set
|
|
618
|
+
if url.drivername != "postgresql+psycopg":
|
|
619
|
+
url = url.set(drivername="postgresql+psycopg")
|
|
620
|
+
|
|
621
|
+
return base_create_engine(url, **kwargs)
|
|
622
|
+
|
|
623
|
+
|
|
598
624
|
def connection_args(engine):
|
|
599
625
|
"""Get PostgreSQL connection arguments for an engine"""
|
|
600
626
|
_psql_flags = {"-U": "username", "-h": "host", "-p": "port", "-P": "password"}
|
|
@@ -610,15 +636,24 @@ def connection_args(engine):
|
|
|
610
636
|
return flags, engine.url.database
|
|
611
637
|
|
|
612
638
|
|
|
613
|
-
def db_isready(engine_or_url):
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
639
|
+
def db_isready(engine_or_url, use_shell_command=False):
|
|
640
|
+
if use_shell_command:
|
|
641
|
+
args, _ = connection_args(engine_or_url)
|
|
642
|
+
c = cmd("pg_isready", args, capture_output=True)
|
|
643
|
+
return c.returncode == 0
|
|
644
|
+
# Use a more typical sqlalchemy connection approach
|
|
645
|
+
engine = create_engine(engine_or_url)
|
|
646
|
+
try:
|
|
647
|
+
with engine.connect() as conn:
|
|
648
|
+
conn.execute(text("SELECT 1"))
|
|
649
|
+
return True
|
|
650
|
+
except OperationalError:
|
|
651
|
+
return False
|
|
617
652
|
|
|
618
653
|
|
|
619
|
-
def wait_for_database(engine_or_url, quiet=False):
|
|
654
|
+
def wait_for_database(engine_or_url, *, quiet=False, use_shell_command=False):
|
|
620
655
|
msg = "Waiting for database..."
|
|
621
|
-
while not db_isready(engine_or_url):
|
|
656
|
+
while not db_isready(engine_or_url, use_shell_command=use_shell_command):
|
|
622
657
|
if not quiet:
|
|
623
658
|
echo(msg, err=True)
|
|
624
659
|
log.info(msg)
|
|
@@ -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 = "
|
|
6
|
+
version = "4.0.0"
|
|
7
7
|
|
|
8
8
|
[tool.poetry.dependencies]
|
|
9
9
|
GeoAlchemy2 = "^0.15.2"
|
|
@@ -11,13 +11,14 @@ SQLAlchemy = "^2.0.18"
|
|
|
11
11
|
SQLAlchemy-Utils = "^0.41.1"
|
|
12
12
|
click = "^8.1.3"
|
|
13
13
|
"macrostrat.utils" = "^1.3.0"
|
|
14
|
-
psycopg2-binary = "^2.9.6"
|
|
15
14
|
python = "^3.10"
|
|
16
15
|
sqlparse = "^0.5.1"
|
|
17
16
|
aiofiles = "^23.2.1"
|
|
18
17
|
rich = "^13.7.1"
|
|
18
|
+
psycopg = "^3.2.1"
|
|
19
|
+
psycopg2 = "^2.9.11"
|
|
19
20
|
|
|
20
|
-
[tool.poetry.dev
|
|
21
|
+
[tool.poetry.group.dev.dependencies]
|
|
21
22
|
"macrostrat.utils" = { path = "../utils", develop = true }
|
|
22
23
|
|
|
23
24
|
[build-system]
|
{macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/mapper/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/transfer/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/transfer/move_tables.py
RENAMED
|
File without changes
|
|
File without changes
|
{macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/transfer/stream_utils.py
RENAMED
|
File without changes
|
{macrostrat_database-3.5.3 → macrostrat_database-4.0.0}/macrostrat/database/transfer/utils.py
RENAMED
|
File without changes
|