hdx-python-database 1.3.9__py3-none-any.whl → 1.4.0__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/_version.py CHANGED
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.3.9'
22
- __version_tuple__ = version_tuple = (1, 3, 9)
21
+ __version__ = version = '1.4.0'
22
+ __version_tuple__ = version_tuple = (1, 4, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
hdx/database/database.py CHANGED
@@ -65,6 +65,7 @@ class Database:
65
65
  reflect (bool): Whether to reflect existing tables. Defaults to False.
66
66
  **kwargs: See below
67
67
  pg_restore_file (Path | str): Restore database from pg_restore file
68
+ pg_timeout (int): Seconds to wait for PostgreSQL. Defaults to None (wait forever).
68
69
  recreate_schema (bool): Whether to recreate schema
69
70
  schema_name (str): Database schema name. Defaults to "public".
70
71
  prepare_fn (Callable[[], None]]): Function to call before Base.metadata.create_all.
@@ -73,6 +74,7 @@ class Database:
73
74
  ssh_username (str): SSH username
74
75
  ssh_password (str): SSH password
75
76
  ssh_private_key: Location of SSH private key (instead of ssh_password)
77
+ ssh_host_key (str): SSH server host key to verify against. Strongly recommended.
76
78
  For more advanced usage, see SSHTunnelForwarder documentation.
77
79
 
78
80
  """
@@ -98,10 +100,12 @@ class Database:
98
100
  schema_name = None
99
101
  if len(kwargs) == 0:
100
102
  pg_restore_file = None
103
+ pg_timeout = None
101
104
  recreate_schema = False
102
105
  prepare_fn = do_nothing_fn
103
106
  else:
104
107
  pg_restore_file = kwargs.pop("pg_restore_file", None)
108
+ pg_timeout = kwargs.pop("pg_timeout", None)
105
109
  recreate_schema = kwargs.pop("recreate_schema", False)
106
110
  schema_name = kwargs.pop("schema", "public")
107
111
  prepare_fn = kwargs.pop("prepare_fn", do_nothing_fn)
@@ -116,6 +120,10 @@ class Database:
116
120
  raise
117
121
  ssh_host = kwargs["ssh_host"]
118
122
  del kwargs["ssh_host"]
123
+ if "ssh_host_key" not in kwargs:
124
+ logger.warning(
125
+ "ssh_host_key not supplied: SSH server identity will not be verified."
126
+ )
119
127
  ssh_port = kwargs.get("ssh_port")
120
128
  if ssh_port is not None:
121
129
  ssh_port = int(ssh_port)
@@ -147,7 +155,7 @@ class Database:
147
155
  "No engine, database uri or connection parameters provided!"
148
156
  )
149
157
  if not engine and dialect == "postgresql":
150
- wait_for_postgresql(db_uri)
158
+ wait_for_postgresql(db_uri, pg_timeout)
151
159
  if pg_restore_file and db_uri:
152
160
  restore_from_pgfile(db_uri, pg_restore_file)
153
161
  if not table_base:
hdx/database/dburi.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """Connection URI utilities"""
2
2
 
3
3
  from typing import Any
4
- from urllib.parse import urlsplit
4
+ from urllib.parse import quote_plus, unquote, urlsplit
5
5
 
6
6
 
7
7
  def get_params_from_connection_uri(
@@ -28,8 +28,8 @@ def get_params_from_connection_uri(
28
28
  "database": result.path[1:],
29
29
  "host": result.hostname,
30
30
  "port": result.port,
31
- "username": result.username,
32
- "password": result.password,
31
+ "username": unquote(result.username) if result.username else None,
32
+ "password": unquote(result.password) if result.password else None,
33
33
  "dialect": dialect,
34
34
  }
35
35
  if include_driver:
@@ -72,9 +72,9 @@ def get_connection_uri(
72
72
  strings.append(f"+{driver}")
73
73
  strings.append("://")
74
74
  if username:
75
- strings.append(username)
75
+ strings.append(quote_plus(username))
76
76
  if password:
77
- strings.append(f":{password}@")
77
+ strings.append(f":{quote_plus(password)}@")
78
78
  else:
79
79
  strings.append("@")
80
80
  if host:
@@ -1,6 +1,6 @@
1
1
  """Utilities for database datetime columns without timezone"""
2
2
 
3
- from datetime import datetime, timezone
3
+ from datetime import UTC, datetime
4
4
 
5
5
  from sqlalchemy import DateTime, TypeDecorator
6
6
  from sqlalchemy.orm import DeclarativeBase, declared_attr
@@ -19,7 +19,7 @@ class ConversionNoTZ(TypeDecorator):
19
19
  if value is None:
20
20
  return value
21
21
  if value.tzinfo:
22
- value = value.astimezone(timezone.utc)
22
+ value = value.astimezone(UTC)
23
23
 
24
24
  return value.replace(tzinfo=None)
25
25
 
@@ -27,9 +27,9 @@ class ConversionNoTZ(TypeDecorator):
27
27
  if value is None:
28
28
  return value
29
29
  if value.tzinfo is None:
30
- return value.replace(tzinfo=timezone.utc)
30
+ return value.replace(tzinfo=UTC)
31
31
 
32
- return value.astimezone(timezone.utc)
32
+ return value.astimezone(UTC)
33
33
 
34
34
 
35
35
  class Base(DeclarativeBase):
@@ -26,17 +26,19 @@ class PostgresError(Exception):
26
26
  pass
27
27
 
28
28
 
29
- def wait_for_postgresql(db_uri: str) -> None:
29
+ def wait_for_postgresql(db_uri: str, timeout: int | None = None) -> None:
30
30
  """Waits for PostgreSQL database to be up
31
31
 
32
32
  Args:
33
33
  db_uri (str): Connection URI
34
+ timeout (Optional[int]): Seconds to wait. Defaults to None (wait forever).
34
35
 
35
36
  Returns:
36
37
  None
37
38
  """
38
39
  connecting_string = "Checking for PostgreSQL..."
39
40
  db_uri_nd = remove_driver_from_uri(db_uri)
41
+ start = time.monotonic()
40
42
  while True:
41
43
  try:
42
44
  logger.info(connecting_string)
@@ -45,6 +47,8 @@ def wait_for_postgresql(db_uri: str) -> None:
45
47
  logger.info("PostgreSQL is running!")
46
48
  break
47
49
  except psycopg.OperationalError:
50
+ if timeout is not None and time.monotonic() - start > timeout:
51
+ raise PostgresError(f"PostgreSQL not available after {timeout}s")
48
52
  time.sleep(1)
49
53
 
50
54
 
@@ -59,7 +63,7 @@ def restore_from_pgfile(db_uri: str, pg_restore_file: Path | str) -> str:
59
63
  Returns:
60
64
  str: Output from the pg_restore command
61
65
  """
62
- pg_restore_file = Path(pg_restore_file)
66
+ pg_restore_file = Path(pg_restore_file).absolute()
63
67
  db_params = get_params_from_connection_uri(db_uri)
64
68
  if pg_restore_file.suffix == ".xz":
65
69
  decompress = subprocess.Popen(
hdx/database/views.py CHANGED
@@ -24,12 +24,13 @@ class DropView(DDLElement):
24
24
 
25
25
  @compiler.compiles(CreateView)
26
26
  def _create_view(element, compiler, **kw):
27
- return f"CREATE VIEW {element.name} AS {compiler.sql_compiler.process(element.selectable, literal_binds=True)}"
27
+ name = compiler.preparer.quote(element.name)
28
+ return f"CREATE VIEW {name} AS {compiler.sql_compiler.process(element.selectable, literal_binds=True)}"
28
29
 
29
30
 
30
31
  @compiler.compiles(DropView)
31
32
  def _drop_view(element, compiler, **kw):
32
- return f"DROP VIEW {element.name}"
33
+ return f"DROP VIEW {compiler.preparer.quote(element.name)}"
33
34
 
34
35
 
35
36
  def view_exists(ddl, target, connection, **kw):
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: hdx-python-database
3
- Version: 1.3.9
3
+ Version: 1.4.0
4
4
  Summary: HDX Python database utilities
5
5
  Project-URL: Homepage, https://github.com/OCHA-DAP/hdx-python-database
6
6
  Author-email: Michael Rans <rans@un.org>
@@ -17,14 +17,15 @@ Classifier: Operating System :: POSIX :: Linux
17
17
  Classifier: Operating System :: Unix
18
18
  Classifier: Programming Language :: Python
19
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
- Requires-Python: >=3.10
20
+ Requires-Python: >=3.11
21
21
  Requires-Dist: sqlalchemy>=2
22
22
  Provides-Extra: docs
23
23
  Requires-Dist: mkapi; extra == 'docs'
24
24
  Provides-Extra: postgresql
25
25
  Requires-Dist: psycopg[binary]; extra == 'postgresql'
26
26
  Provides-Extra: sshtunnel
27
- Requires-Dist: sshtunnel; extra == 'sshtunnel'
27
+ Requires-Dist: cryptography>=48.0.1; extra == 'sshtunnel'
28
+ Requires-Dist: sshtunnel>=0.5.1; extra == 'sshtunnel'
28
29
  Description-Content-Type: text/markdown
29
30
 
30
31
  [![Build Status](https://github.com/OCHA-DAP/hdx-python-database/actions/workflows/run-python-tests.yaml/badge.svg)](https://github.com/OCHA-DAP/hdx-python-database/actions/workflows/run-python-tests.yaml)
@@ -0,0 +1,13 @@
1
+ hdx/database/__init__.py,sha256=sG2OYV5LHskgiDkB4IeVeQyPHL22Ta1kuxf-xN92-5E,155
2
+ hdx/database/_version.py,sha256=u2oUj6dCZIqQ271Pm6INHvXKeiux4BSapl6g0c_tllg,520
3
+ hdx/database/database.py,sha256=SA3XwmWMg7VIrUaWWRXVLMxXtKV4F9gz-jK5CJEPI18,13320
4
+ hdx/database/dburi.py,sha256=5PbvVe56MglpHxflAFeRY0TXli3XThZrDphPWmBp4ho,3055
5
+ hdx/database/no_timezone.py,sha256=MG4Z2t7SwjPKDnwDD0CVftoIZC0hxpSg-C-1FyEy0R0,1067
6
+ hdx/database/postgresql.py,sha256=JSLWQrAYps7Dxkb0En0kndr-9lNQhPpaY5UvxydTmb8,3321
7
+ hdx/database/utils.py,sha256=nzChgDj2_Mq8GS6xWaNmqOgpAiwK9tGYV_skKbErEVA,401
8
+ hdx/database/views.py,sha256=3ZQT5jS-q3w30zKypnyuy8-2BKwkyYC0It9om9hxmCs,1669
9
+ hdx/database/with_timezone.py,sha256=GUUylmTwQHp22yedmCVKpVJzvjWgdS9lg9KeqciNJkY,235
10
+ hdx_python_database-1.4.0.dist-info/METADATA,sha256=ezN2GRZ808XVFheZ6d7DFaWqsX-am5kQ8jLvbrVAegQ,3985
11
+ hdx_python_database-1.4.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
12
+ hdx_python_database-1.4.0.dist-info/licenses/LICENSE,sha256=wc-4GpMn-ODs-U_bTe1YCiPVgvcjzrpYOx2wPuyAeII,1079
13
+ hdx_python_database-1.4.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.29.0
2
+ Generator: hatchling 1.32.4
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,13 +0,0 @@
1
- hdx/database/__init__.py,sha256=sG2OYV5LHskgiDkB4IeVeQyPHL22Ta1kuxf-xN92-5E,155
2
- hdx/database/_version.py,sha256=g62BHujLgGdP6cB1oucUZ89PYnKUKXkd5IR17viSOVM,520
3
- hdx/database/database.py,sha256=ZjI8roQM0x56RlywG5xLIVZ4pVqVqiREilbTfzYxwNU,12856
4
- hdx/database/dburi.py,sha256=Kkg9eyS0Dia63vTTWPu4K1nqhn9igpZYMw-66wPn2ZU,2934
5
- hdx/database/no_timezone.py,sha256=WuiSijzzUox6mpvPMveklA2oqKQcaudr2zz-U78Lq6A,1099
6
- hdx/database/postgresql.py,sha256=sBvuBQNoT_iJvy3W2lFupDKbx-g25UhlRYv2LlQmpVs,3013
7
- hdx/database/utils.py,sha256=nzChgDj2_Mq8GS6xWaNmqOgpAiwK9tGYV_skKbErEVA,401
8
- hdx/database/views.py,sha256=zkRTsD1cE_afJaxdYd72EoUv3qdnqBL-ekYPkv-DeD4,1603
9
- hdx/database/with_timezone.py,sha256=GUUylmTwQHp22yedmCVKpVJzvjWgdS9lg9KeqciNJkY,235
10
- hdx_python_database-1.3.9.dist-info/METADATA,sha256=YIP17SHZ_JMjbUlKBYmKmN0LAUI1nPEYgMmmI2XNH0g,3920
11
- hdx_python_database-1.3.9.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
12
- hdx_python_database-1.3.9.dist-info/licenses/LICENSE,sha256=wc-4GpMn-ODs-U_bTe1YCiPVgvcjzrpYOx2wPuyAeII,1079
13
- hdx_python_database-1.3.9.dist-info/RECORD,,