hdx-python-database 1.3.1__py3-none-any.whl → 1.3.2__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.
- hdx/database/__init__.py +31 -14
- hdx/database/_version.py +2 -2
- hdx/database/postgresql.py +55 -1
- {hdx_python_database-1.3.1.dist-info → hdx_python_database-1.3.2.dist-info}/METADATA +1 -1
- hdx_python_database-1.3.2.dist-info/RECORD +12 -0
- {hdx_python_database-1.3.1.dist-info → hdx_python_database-1.3.2.dist-info}/WHEEL +1 -1
- hdx_python_database-1.3.1.dist-info/RECORD +0 -12
- {hdx_python_database-1.3.1.dist-info → hdx_python_database-1.3.2.dist-info}/licenses/LICENSE +0 -0
hdx/database/__init__.py
CHANGED
|
@@ -6,15 +6,14 @@ from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
|
|
6
6
|
from sqlalchemy import Engine, TableClause, create_engine
|
|
7
7
|
from sqlalchemy.exc import SQLAlchemyError
|
|
8
8
|
from sqlalchemy.ext.automap import automap_base
|
|
9
|
-
from sqlalchemy.orm import DeclarativeBase, Session
|
|
9
|
+
from sqlalchemy.orm import DeclarativeBase, Session
|
|
10
10
|
from sqlalchemy.pool import NullPool
|
|
11
11
|
from sqlalchemy.sql.ddl import CreateSchema, DropSchema
|
|
12
|
-
from sqlalchemy.util import Properties
|
|
13
12
|
|
|
14
13
|
from ._version import version as __version__ # noqa: F401
|
|
15
14
|
from .dburi import get_connection_uri
|
|
16
15
|
from .no_timezone import Base as NoTZBase
|
|
17
|
-
from .postgresql import wait_for_postgresql
|
|
16
|
+
from .postgresql import restore_from_pgfile, wait_for_postgresql
|
|
18
17
|
from .views import view
|
|
19
18
|
from .with_timezone import Base as TZBase
|
|
20
19
|
|
|
@@ -41,6 +40,9 @@ class Database:
|
|
|
41
40
|
with timezones to timezoneless database columns (but not when using
|
|
42
41
|
reflection). If table_base is supplied, db_has_tz is ignored.
|
|
43
42
|
|
|
43
|
+
The database can be restored from a given pg_restore file supplied in the
|
|
44
|
+
parameter pg_restore_file.
|
|
45
|
+
|
|
44
46
|
There is an option to wipe and create an empty schema in the database by
|
|
45
47
|
setting recreate_schema to True and setting a schema_name ("public" is the
|
|
46
48
|
default).
|
|
@@ -63,6 +65,7 @@ class Database:
|
|
|
63
65
|
table_base (Optional[Type[DeclarativeBase]]): Override table base. Defaults to None.
|
|
64
66
|
reflect (bool): Whether to reflect existing tables. Defaults to False.
|
|
65
67
|
**kwargs: See below
|
|
68
|
+
pg_restore_file (str): Restore database from pg_restore file
|
|
66
69
|
recreate_schema (bool): Whether to recreate schema
|
|
67
70
|
schema_name (str): Database schema name. Defaults to "public".
|
|
68
71
|
prepare_fn (Callable[[], None]]): Function to call before Base.metadata.create_all.
|
|
@@ -95,9 +98,11 @@ class Database:
|
|
|
95
98
|
port = int(port)
|
|
96
99
|
schema_name = None
|
|
97
100
|
if len(kwargs) == 0:
|
|
101
|
+
pg_restore_file = None
|
|
98
102
|
recreate_schema = False
|
|
99
103
|
prepare_fn = do_nothing_fn
|
|
100
104
|
else:
|
|
105
|
+
pg_restore_file = kwargs.pop("pg_restore_file", None)
|
|
101
106
|
recreate_schema = kwargs.pop("recreate_schema", False)
|
|
102
107
|
schema_name = kwargs.pop("schema", "public")
|
|
103
108
|
prepare_fn = kwargs.pop("prepare_fn", do_nothing_fn)
|
|
@@ -144,6 +149,8 @@ class Database:
|
|
|
144
149
|
)
|
|
145
150
|
if not engine and dialect == "postgresql":
|
|
146
151
|
wait_for_postgresql(db_uri)
|
|
152
|
+
if pg_restore_file and db_uri:
|
|
153
|
+
restore_from_pgfile(db_uri, pg_restore_file)
|
|
147
154
|
if not table_base:
|
|
148
155
|
if db_has_tz:
|
|
149
156
|
table_base = TZBase
|
|
@@ -155,19 +162,31 @@ class Database:
|
|
|
155
162
|
if recreate_schema:
|
|
156
163
|
self.recreate_schema(engine, schema_name)
|
|
157
164
|
self.prepare_results = prepare_fn()
|
|
158
|
-
self.session, self.
|
|
165
|
+
self.session, self.base = self.create_session(
|
|
159
166
|
engine,
|
|
160
167
|
table_base=table_base,
|
|
161
168
|
reflect=reflect,
|
|
162
169
|
)
|
|
170
|
+
if reflect:
|
|
171
|
+
self.reflected_classes = self.base.classes
|
|
172
|
+
else:
|
|
173
|
+
self.reflected_classes = None
|
|
174
|
+
|
|
175
|
+
def cleanup(self) -> None:
|
|
176
|
+
self.base.metadata.clear()
|
|
177
|
+
self.session.close()
|
|
178
|
+
self.engine.dispose()
|
|
179
|
+
if self.server is not None:
|
|
180
|
+
self.server.stop()
|
|
163
181
|
|
|
164
182
|
def __enter__(self) -> "Database":
|
|
165
183
|
return self
|
|
166
184
|
|
|
167
185
|
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
|
168
|
-
self.
|
|
169
|
-
|
|
170
|
-
|
|
186
|
+
self.cleanup()
|
|
187
|
+
|
|
188
|
+
def drop_all(self):
|
|
189
|
+
self.base.metadata.drop_all(self.engine)
|
|
171
190
|
|
|
172
191
|
def get_engine(self) -> Engine:
|
|
173
192
|
"""Returns SQLAlchemy engine.
|
|
@@ -207,7 +226,7 @@ class Database:
|
|
|
207
226
|
db_uri: Optional[str] = None,
|
|
208
227
|
table_base: Type[DeclarativeBase] = NoTZBase,
|
|
209
228
|
reflect: bool = False,
|
|
210
|
-
) -> Tuple[Session,
|
|
229
|
+
) -> Tuple[Session, Any]:
|
|
211
230
|
"""Creates SQLAlchemy session given SQLAlchemy engine or database uri
|
|
212
231
|
(one of which must be supplied). Tables must inherit from Base in
|
|
213
232
|
hdx.utilities.database unless base is defined. If reflect is True,
|
|
@@ -222,22 +241,19 @@ class Database:
|
|
|
222
241
|
reflect (bool): Whether to reflect existing tables. Defaults to False.
|
|
223
242
|
|
|
224
243
|
Returns:
|
|
225
|
-
Tuple[Session,
|
|
244
|
+
Tuple[Session, Any]: (SQLAlchemy session, base)
|
|
226
245
|
"""
|
|
227
246
|
if not engine:
|
|
228
247
|
if db_uri is None:
|
|
229
248
|
raise DatabaseError("No engine or database uri provided!")
|
|
230
249
|
engine = create_engine(db_uri, poolclass=NullPool, echo=False)
|
|
231
|
-
Session = sessionmaker(bind=engine)
|
|
232
250
|
if reflect:
|
|
233
251
|
Base = automap_base(declarative_base=table_base)
|
|
234
252
|
Base.prepare(autoload_with=engine)
|
|
235
|
-
|
|
253
|
+
table_base = Base
|
|
236
254
|
else:
|
|
237
255
|
table_base.metadata.create_all(engine)
|
|
238
|
-
|
|
239
|
-
session = Session()
|
|
240
|
-
return session, reflected_classes
|
|
256
|
+
return Session(engine), table_base
|
|
241
257
|
|
|
242
258
|
@staticmethod
|
|
243
259
|
def recreate_schema(engine: Engine, schema_name: str = "public") -> bool:
|
|
@@ -285,6 +301,7 @@ class Database:
|
|
|
285
301
|
|
|
286
302
|
Args:
|
|
287
303
|
view_params_list (List[Dict]): List of dictionaries with view parameters
|
|
304
|
+
|
|
288
305
|
Returns:
|
|
289
306
|
List[TableClause]: SQLAlchemy Views
|
|
290
307
|
"""
|
hdx/database/_version.py
CHANGED
hdx/database/postgresql.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"""PostgreSQL specific utilities"""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import subprocess
|
|
4
5
|
import time
|
|
6
|
+
from os import environ
|
|
5
7
|
|
|
6
|
-
from .dburi import remove_driver_from_uri
|
|
8
|
+
from .dburi import get_params_from_connection_uri, remove_driver_from_uri
|
|
7
9
|
|
|
8
10
|
try:
|
|
9
11
|
import psycopg
|
|
@@ -19,6 +21,10 @@ except ImportError:
|
|
|
19
21
|
logger = logging.getLogger(__name__)
|
|
20
22
|
|
|
21
23
|
|
|
24
|
+
class PostgresError(Exception):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
22
28
|
def wait_for_postgresql(db_uri: str) -> None:
|
|
23
29
|
"""Waits for PostgreSQL database to be up
|
|
24
30
|
|
|
@@ -39,3 +45,51 @@ def wait_for_postgresql(db_uri: str) -> None:
|
|
|
39
45
|
break
|
|
40
46
|
except psycopg.OperationalError:
|
|
41
47
|
time.sleep(1)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def restore_from_pgfile(db_uri: str, pg_restore_file: str) -> str:
|
|
51
|
+
"""Restore database from a pg_restore file created by pg_backup using the
|
|
52
|
+
pg_restore command.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
db_uri (str): Connection URI
|
|
56
|
+
pg_restore_file (str): Path to the pg_restore database file
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
str: Output from the pg_restore command
|
|
60
|
+
"""
|
|
61
|
+
db_params = get_params_from_connection_uri(db_uri)
|
|
62
|
+
subprocess_params = ["pg_restore", "-c"]
|
|
63
|
+
for key, value in db_params.items():
|
|
64
|
+
match key:
|
|
65
|
+
case "database":
|
|
66
|
+
subprocess_params.append("-d")
|
|
67
|
+
case "host":
|
|
68
|
+
subprocess_params.append("-h")
|
|
69
|
+
case "port":
|
|
70
|
+
subprocess_params.append("-p")
|
|
71
|
+
case "username":
|
|
72
|
+
subprocess_params.append("-U")
|
|
73
|
+
case _:
|
|
74
|
+
continue
|
|
75
|
+
subprocess_params.append(f"{value}")
|
|
76
|
+
|
|
77
|
+
subprocess_params.append(f"{pg_restore_file}")
|
|
78
|
+
env = environ.copy()
|
|
79
|
+
password = db_params.get("password")
|
|
80
|
+
if password:
|
|
81
|
+
env["PGPASSWORD"] = password
|
|
82
|
+
process = subprocess.run(
|
|
83
|
+
subprocess_params, env=env, capture_output=True, encoding="utf-8"
|
|
84
|
+
)
|
|
85
|
+
try:
|
|
86
|
+
process.check_returncode()
|
|
87
|
+
except subprocess.CalledProcessError as ex:
|
|
88
|
+
command = " ".join(subprocess_params)
|
|
89
|
+
raise PostgresError(
|
|
90
|
+
f"{command} failed. Return code: {process.returncode}"
|
|
91
|
+
) from ex
|
|
92
|
+
|
|
93
|
+
for line in process.stdout.splitlines():
|
|
94
|
+
logger.info(line)
|
|
95
|
+
return process.stdout
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
hdx/database/__init__.py,sha256=cOAnY4cOLcjxM556jAIfw9XqTIKk7mSwZCugIf3XpPA,11431
|
|
2
|
+
hdx/database/_version.py,sha256=ik9WUFYTvRcmzFEUSqkI2H_62uw0XFdUT3mTLq8-_RY,411
|
|
3
|
+
hdx/database/dburi.py,sha256=rw8RuLpU85vxYl5-Zk1SdZRAz-tqre_ot5aEnkjhxAQ,2977
|
|
4
|
+
hdx/database/no_timezone.py,sha256=Lw087G3qOf8H70o8Hq_VvOMZ3ANei-lIA_sUlpFU_Uo,1130
|
|
5
|
+
hdx/database/postgresql.py,sha256=BrEuRnuR2dt3HwJ-o_IzGFs8eT3iXKzDqMSNKPyQ18Y,2621
|
|
6
|
+
hdx/database/utils.py,sha256=6lLymWriMFCq8BZgrDt4V5z14GR3PbvG_WXZeBc99Ws,415
|
|
7
|
+
hdx/database/views.py,sha256=R_1Ni0U2CL3IlydxcB1vyZp7QoDZim6g-807jQIaWQU,1950
|
|
8
|
+
hdx/database/with_timezone.py,sha256=GUUylmTwQHp22yedmCVKpVJzvjWgdS9lg9KeqciNJkY,235
|
|
9
|
+
hdx_python_database-1.3.2.dist-info/METADATA,sha256=ZCPvpjSSs0PxElgVksIa2v7wBo2soTX52wfl4d9eau4,2691
|
|
10
|
+
hdx_python_database-1.3.2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
11
|
+
hdx_python_database-1.3.2.dist-info/licenses/LICENSE,sha256=wc-4GpMn-ODs-U_bTe1YCiPVgvcjzrpYOx2wPuyAeII,1079
|
|
12
|
+
hdx_python_database-1.3.2.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
hdx/database/__init__.py,sha256=ApMtv6Ul4dwZCeS1Wc_Igm9ZnYKkWo_HBi9DBKFHx3Q,10957
|
|
2
|
-
hdx/database/_version.py,sha256=cOVPCvD2h2G_2KB6G3ddreYkIQfAnS6WqgAkF_qgGOQ,411
|
|
3
|
-
hdx/database/dburi.py,sha256=rw8RuLpU85vxYl5-Zk1SdZRAz-tqre_ot5aEnkjhxAQ,2977
|
|
4
|
-
hdx/database/no_timezone.py,sha256=Lw087G3qOf8H70o8Hq_VvOMZ3ANei-lIA_sUlpFU_Uo,1130
|
|
5
|
-
hdx/database/postgresql.py,sha256=idtRdJ3zbnpInsjf-_JvWSxtDvU8NQPYcvgHq25ju-k,970
|
|
6
|
-
hdx/database/utils.py,sha256=6lLymWriMFCq8BZgrDt4V5z14GR3PbvG_WXZeBc99Ws,415
|
|
7
|
-
hdx/database/views.py,sha256=R_1Ni0U2CL3IlydxcB1vyZp7QoDZim6g-807jQIaWQU,1950
|
|
8
|
-
hdx/database/with_timezone.py,sha256=GUUylmTwQHp22yedmCVKpVJzvjWgdS9lg9KeqciNJkY,235
|
|
9
|
-
hdx_python_database-1.3.1.dist-info/METADATA,sha256=qxQBQ56bNMZP_DoLVHidyBvpjA0Dm2Qbm0dWtsRfIwk,2691
|
|
10
|
-
hdx_python_database-1.3.1.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
|
11
|
-
hdx_python_database-1.3.1.dist-info/licenses/LICENSE,sha256=wc-4GpMn-ODs-U_bTe1YCiPVgvcjzrpYOx2wPuyAeII,1079
|
|
12
|
-
hdx_python_database-1.3.1.dist-info/RECORD,,
|
{hdx_python_database-1.3.1.dist-info → hdx_python_database-1.3.2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|