hdx-python-database 1.2.1__py2.py3-none-any.whl → 1.2.2__py2.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 CHANGED
@@ -3,30 +3,32 @@ from .dburi import get_connection_uri
3
3
 
4
4
  """Database utilities"""
5
5
  import logging
6
- from typing import Any, Optional, Union
6
+ from typing import Any, Optional, Type, Union
7
7
 
8
8
  from sqlalchemy import create_engine
9
- from sqlalchemy.ext.declarative import declarative_base, declared_attr
10
- from sqlalchemy.orm import Session, sessionmaker
9
+ from sqlalchemy.ext.declarative import declared_attr
10
+ from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
11
11
  from sqlalchemy.pool import NullPool
12
12
  from sshtunnel import SSHTunnelForwarder
13
13
 
14
- from hdx.database.postgresql import wait_for_postgresql
14
+ from .no_timezone import Base as NoTZBase
15
+ from .postgresql import wait_for_postgresql
15
16
 
16
17
  logger = logging.getLogger(__name__)
17
18
 
18
19
 
19
- class HDXBase:
20
+ class Base(DeclarativeBase):
20
21
  @declared_attr.directive
21
22
  def __tablename__(cls):
22
23
  return f"{cls.__name__.lower()}s"
23
24
 
24
25
 
25
- Base = declarative_base(cls=HDXBase)
26
-
27
-
28
26
  class Database:
29
- """Database helper class to handle ssh tunnels, waiting for PostgreSQL to be up etc.
27
+ """Database helper class to handle ssh tunnels, waiting for PostgreSQL to
28
+ be up etc. Can be used in a with statement returning a Session object.
29
+ db_has_tz which defaults to False indicates whether database datetime
30
+ columns have timezones. If not, conversion occurs between Python datetimes
31
+ with timezones to timezoneless database columns.
30
32
 
31
33
  Args:
32
34
  database (Optional[str]): Database name
@@ -36,6 +38,7 @@ class Database:
36
38
  password (Optional[str]): Password to log into database
37
39
  dialect (str): Database dialect. Defaults to "postgresql".
38
40
  driver (Optional[str]): Database driver. Defaults to None (psycopg if postgresql or None)
41
+ db_has_tz (bool): True if db datetime columns have timezone. Defaults to False.
39
42
  **kwargs: See below
40
43
  ssh_host (str): SSH host (the server to connect to)
41
44
  ssh_port (int): SSH port. Defaults to 22.
@@ -55,6 +58,7 @@ class Database:
55
58
  password: Optional[str] = None,
56
59
  dialect: str = "postgresql",
57
60
  driver: Optional[str] = None,
61
+ db_has_tz: bool = False,
58
62
  **kwargs: Any,
59
63
  ) -> None:
60
64
  if port is not None:
@@ -89,7 +93,11 @@ class Database:
89
93
  )
90
94
  if dialect == "postgresql":
91
95
  wait_for_postgresql(db_uri)
92
- self.session = self.get_session(db_uri)
96
+ if db_has_tz:
97
+ table_base = Base
98
+ else:
99
+ table_base = NoTZBase
100
+ self.session = self.get_session(db_uri, table_base=table_base)
93
101
 
94
102
  def __enter__(self) -> Session:
95
103
  return self.session
@@ -100,17 +108,20 @@ class Database:
100
108
  self.server.stop()
101
109
 
102
110
  @staticmethod
103
- def get_session(db_uri: str) -> Session:
104
- """Gets SQLAlchemy session given url. Your tables must inherit
105
- from Base in hdx.utilities.database.
111
+ def get_session(
112
+ db_uri: str, table_base: Type[DeclarativeBase] = NoTZBase
113
+ ) -> Session:
114
+ """Gets SQLAlchemy session given url. Tables must inherit from Base in
115
+ hdx.utilities.database unless base is defined.
106
116
 
107
117
  Args:
108
118
  db_uri (str): Connection URI
119
+ table_base (Type[DeclarativeBase]): Base database table class. Defaults to NoTZBase.
109
120
 
110
121
  Returns:
111
122
  sqlalchemy.orm.Session: SQLAlchemy session
112
123
  """
113
124
  engine = create_engine(db_uri, poolclass=NullPool, echo=False)
114
125
  Session = sessionmaker(bind=engine)
115
- Base.metadata.create_all(engine)
126
+ table_base.metadata.create_all(engine)
116
127
  return Session()
hdx/database/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # file generated by setuptools_scm
2
2
  # don't change, don't track in version control
3
- __version__ = version = '1.2.1'
4
- __version_tuple__ = version_tuple = (1, 2, 1)
3
+ __version__ = version = '1.2.2'
4
+ __version_tuple__ = version_tuple = (1, 2, 2)
@@ -0,0 +1,40 @@
1
+ """Utilities for database datetime columns without timezone"""
2
+ from datetime import datetime, timezone
3
+ from typing import Optional
4
+
5
+ from sqlalchemy import DateTime, TypeDecorator
6
+ from sqlalchemy.orm import DeclarativeBase, declared_attr
7
+
8
+
9
+ class ConversionNoTZ(TypeDecorator):
10
+ """Convert from/to datetime with timezone from database columns that don't
11
+ use timezone"""
12
+
13
+ impl = DateTime
14
+ cache_ok = True
15
+
16
+ def process_bind_param(self, value: Optional[datetime], _):
17
+ if value is None:
18
+ return value
19
+ if value.tzinfo:
20
+ value = value.astimezone(timezone.utc)
21
+
22
+ return value.replace(tzinfo=None)
23
+
24
+ def process_result_value(self, value, _):
25
+ if value is None:
26
+ return value
27
+ if value.tzinfo is None:
28
+ return value.replace(tzinfo=timezone.utc)
29
+
30
+ return value.astimezone(timezone.utc)
31
+
32
+
33
+ class Base(DeclarativeBase):
34
+ type_annotation_map = {
35
+ datetime: ConversionNoTZ,
36
+ }
37
+
38
+ @declared_attr.directive
39
+ def __tablename__(cls):
40
+ return f"{cls.__name__.lower()}s"
@@ -2,7 +2,7 @@
2
2
  import logging
3
3
  import time
4
4
 
5
- from hdx.database.dburi import remove_driver_from_uri
5
+ from .dburi import remove_driver_from_uri
6
6
 
7
7
  try:
8
8
  import psycopg
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hdx-python-database
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: HDX Python database utilities
5
5
  Home-page: https://github.com/OCHA-DAP/hdx-python-database
6
6
  Author: Michael Rans
@@ -0,0 +1,11 @@
1
+ hdx/database/__init__.py,sha256=HPKyvzWk-Gqd63sE3kcoyEO9n5h_9l3IeuzhzFnMWfQ,4458
2
+ hdx/database/_version.py,sha256=O2qpyKb1ilx0S5H2I5jnx1k8SSt3R0rHyv19gH5I8Ws,160
3
+ hdx/database/dburi.py,sha256=hNksrjaCC7AtD_M5ef0DCjkS0rJCofQqdXVD2BHBIGc,2938
4
+ hdx/database/no_timezone.py,sha256=7Gnidmu317AWAWx4aPY4oIWBio9dqqSsiUTndEi5hMY,1082
5
+ hdx/database/postgresql.py,sha256=9VlcdHhBURZPDaazj1kEq8gA4hUFJY-5hIN9uzibOlM,969
6
+ hdx_python_database-1.2.2.dist-info/LICENSE,sha256=wc-4GpMn-ODs-U_bTe1YCiPVgvcjzrpYOx2wPuyAeII,1079
7
+ hdx_python_database-1.2.2.dist-info/METADATA,sha256=ykW44dWeZ6DVkzUerqYbitQNoJE5WiBOkhBZXo3Rtzg,2464
8
+ hdx_python_database-1.2.2.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
9
+ hdx_python_database-1.2.2.dist-info/top_level.txt,sha256=cfo3TyDjnu-B1jb_itPPFQCH00c5VT5GuZ30eNrGmlI,4
10
+ hdx_python_database-1.2.2.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
11
+ hdx_python_database-1.2.2.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- hdx/database/__init__.py,sha256=OcQxMp3lTyDyw_nwI_du5rz_MfJNizVe7mY815ZI-24,3753
2
- hdx/database/_version.py,sha256=osFGs6qmlEth0Xu2MHIHoChfXcj8NUW4hLWDEu-1_K4,160
3
- hdx/database/dburi.py,sha256=hNksrjaCC7AtD_M5ef0DCjkS0rJCofQqdXVD2BHBIGc,2938
4
- hdx/database/postgresql.py,sha256=mV31vmWCa8Dnqz_82zVkB8x021_HBNbz1rkFzFoKQh0,981
5
- hdx_python_database-1.2.1.dist-info/LICENSE,sha256=wc-4GpMn-ODs-U_bTe1YCiPVgvcjzrpYOx2wPuyAeII,1079
6
- hdx_python_database-1.2.1.dist-info/METADATA,sha256=oRowXwXZgOMa5MDNdFtqRVUoQ_1Ob-tQBuH_tWJ7w3E,2464
7
- hdx_python_database-1.2.1.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
8
- hdx_python_database-1.2.1.dist-info/top_level.txt,sha256=cfo3TyDjnu-B1jb_itPPFQCH00c5VT5GuZ30eNrGmlI,4
9
- hdx_python_database-1.2.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
10
- hdx_python_database-1.2.1.dist-info/RECORD,,