database-wrapper-pgsql 0.2.7__py3-none-any.whl → 0.2.12__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.
@@ -24,6 +24,7 @@ from .connector import (
24
24
  PgConnectionTypeAsync,
25
25
  PgCursorTypeAsync,
26
26
  )
27
+ from .pg_introspector import PostgresIntrospector
27
28
 
28
29
  # Set the logger to a quiet default, can be enabled if needed
29
30
  logger = logging.getLogger("database_wrapper_pgsql")
@@ -47,4 +48,5 @@ __all__ = [
47
48
  "PgCursorTypeAsync",
48
49
  # Helpers
49
50
  "PgConfig",
51
+ "PostgresIntrospector",
50
52
  ]
@@ -0,0 +1,72 @@
1
+ import psycopg
2
+
3
+ from database_wrapper import ColumnMetaIntrospector, DBIntrospector
4
+ from .type_mapping import map_db_type
5
+
6
+
7
+ class PostgresIntrospector(DBIntrospector):
8
+ conn: psycopg.Connection
9
+
10
+ def map_db_type(self, db_type: str) -> str:
11
+ return map_db_type(db_type)
12
+
13
+ def get_table_columns(self, schema: str, table: str) -> list[ColumnMetaIntrospector]:
14
+ """
15
+ Returns ColumnMeta including enum labels if column type is enum.
16
+ """
17
+ q = """
18
+ WITH cols AS (
19
+ SELECT
20
+ a.attname AS col_name,
21
+ a.attnotnull AS not_null,
22
+ pg_catalog.format_type(a.atttypid, a.atttypmod) AS fmt_type,
23
+ t.typname AS typname,
24
+ t.typcategory AS typcategory,
25
+ a.atttypid AS typid,
26
+ pg_catalog.pg_get_expr(ad.adbin, ad.adrelid) AS default_expr
27
+ FROM pg_attribute a
28
+ JOIN pg_class c ON c.oid = a.attrelid
29
+ JOIN pg_namespace n ON n.oid = c.relnamespace
30
+ JOIN pg_type t ON t.oid = a.atttypid
31
+ LEFT JOIN pg_attrdef ad
32
+ ON ad.adrelid = a.attrelid
33
+ AND ad.adnum = a.attnum
34
+ WHERE n.nspname = %s
35
+ AND c.relname = %s
36
+ AND a.attnum > 0
37
+ AND NOT a.attisdropped
38
+ ORDER BY a.attnum
39
+ )
40
+ SELECT
41
+ cols.col_name,
42
+ cols.typname,
43
+ cols.fmt_type,
44
+ NOT not_null AS is_nullable,
45
+ default_expr,
46
+ CASE WHEN typcategory = 'E' THEN (
47
+ SELECT array_agg(e.enumlabel ORDER BY e.enumsortorder)
48
+ FROM pg_enum e
49
+ WHERE e.enumtypid = cols.typid
50
+ )
51
+ ELSE NULL
52
+ END AS enum_labels
53
+ FROM cols;
54
+ """
55
+ with self.conn.cursor() as cur:
56
+ cur.execute(q, (schema, table))
57
+ rows = cur.fetchall()
58
+
59
+ out: list[ColumnMetaIntrospector] = []
60
+ for row in rows:
61
+ out.append(
62
+ ColumnMetaIntrospector(
63
+ col_name=row["col_name"],
64
+ db_type=row["typname"],
65
+ is_nullable=bool(row["is_nullable"]),
66
+ has_default=(row["default_expr"] is not None),
67
+ default_expr=row["default_expr"],
68
+ # enum_labels=list(row["enum_labels"]) if row["enum_labels"] else None,
69
+ )
70
+ )
71
+
72
+ return out
@@ -0,0 +1,54 @@
1
+ # type_mapping_pg.py
2
+ from typing import Any, Optional, Tuple
3
+ from decimal import Decimal
4
+ import datetime
5
+ from database_wrapper import SerializeType
6
+
7
+ # Flip this if you want lossless decimals
8
+ USE_DECIMAL = False
9
+
10
+ _PG_TO_PY_BASE: dict[str, Tuple[type, Optional[SerializeType]]] = {
11
+ # integers
12
+ "int2": (int, None),
13
+ "int4": (int, None),
14
+ "int8": (int, None),
15
+ # floats
16
+ "float4": (float, None),
17
+ "float8": (float, None),
18
+ # bool
19
+ "bool": (bool, None),
20
+ "boolean": (bool, None),
21
+ # text
22
+ "text": (str, None),
23
+ "varchar": (str, None),
24
+ "bpchar": (str, None),
25
+ "citext": (str, None),
26
+ # json
27
+ "json": (dict[str, Any], SerializeType.JSON),
28
+ "jsonb": (dict[str, Any], SerializeType.JSON),
29
+ # temporal
30
+ "timestamptz": (datetime.datetime, SerializeType.DATETIME),
31
+ "timestamp": (datetime.datetime, SerializeType.DATETIME),
32
+ "date": (datetime.date, SerializeType.DATE),
33
+ "time": (datetime.time, SerializeType.TIME),
34
+ "timetz": (datetime.time, SerializeType.TIME),
35
+ # UUID
36
+ "uuid": (str, None), # or python's uuid.UUID if you prefer
37
+ }
38
+
39
+
40
+ def map_db_type(
41
+ db_type: str, *, length: int | None = None, precision: int | None = None, scale: int | None = None
42
+ ) -> tuple[type, Optional[SerializeType]]:
43
+ t = db_type.lower()
44
+ if t == "numeric":
45
+ if USE_DECIMAL:
46
+ return (Decimal, SerializeType.DECIMAL)
47
+ return (float, None)
48
+
49
+ # money: tends to need Decimal
50
+ if t in ("money", "smallmoney"):
51
+ return (Decimal, SerializeType.DECIMAL) if USE_DECIMAL else (float, None)
52
+
53
+ # fall back to base
54
+ return _PG_TO_PY_BASE.get(t, (str, None))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: database_wrapper_pgsql
3
- Version: 0.2.7
3
+ Version: 0.2.12
4
4
  Summary: database_wrapper for PostgreSQL database
5
5
  Author-email: Gints Murans <gm@gm.lv>
6
6
  License: GNU General Public License v3.0 (GPL-3.0)
@@ -32,7 +32,7 @@ Classifier: Topic :: Software Development
32
32
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
33
33
  Requires-Python: >=3.8
34
34
  Description-Content-Type: text/markdown
35
- Requires-Dist: database_wrapper==0.2.7
35
+ Requires-Dist: database_wrapper==0.2.12
36
36
  Requires-Dist: psycopg[binary]>=3.2.0
37
37
  Requires-Dist: psycopg[pool]>=3.2.0
38
38
 
@@ -0,0 +1,12 @@
1
+ database_wrapper_pgsql/__init__.py,sha256=MKa2gKPeRh-nQxp_Jd1BxbmZtcow_M3LBtBe7hQhrWQ,1136
2
+ database_wrapper_pgsql/connector.py,sha256=Hicf9DHxOjv6H77ySdTi26YmKjlSTzOIWQrjETq3LRg,25027
3
+ database_wrapper_pgsql/db_wrapper_pgsql.py,sha256=Tm18dzSOINz4Z8xHWynLCPf-HmD_SFP0PYVE8ZPd_A4,1921
4
+ database_wrapper_pgsql/db_wrapper_pgsql_async.py,sha256=d4AZp_dHdmob7oJlEst2DnhMqpeXeG6WOzI8k5OldKg,2024
5
+ database_wrapper_pgsql/db_wrapper_pgsql_mixin.py,sha256=b4m7YSk5OjukuCMky_VwFT8TYCqjU2goNnSC2HOM0HA,5452
6
+ database_wrapper_pgsql/pg_introspector.py,sha256=MIhDFsvQ4a1bLQaCADOmvyV-Kr1cddlKFb9novf7bhw,2556
7
+ database_wrapper_pgsql/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ database_wrapper_pgsql/type_mapping.py,sha256=TD1gEPNEidFfbofTYKRuuNNqHqPG0eiijoE9ICvV6SQ,1647
9
+ database_wrapper_pgsql-0.2.12.dist-info/METADATA,sha256=1FXDiZXNlWGjDQUjV7w4SrJWp2P-Xl5GTHmmMzuT__E,3231
10
+ database_wrapper_pgsql-0.2.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ database_wrapper_pgsql-0.2.12.dist-info/top_level.txt,sha256=EQhZLk12wRdsSp-Lo3Jc4cXmNfG8y5EouNv_7OBCSGo,23
12
+ database_wrapper_pgsql-0.2.12.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- database_wrapper_pgsql/__init__.py,sha256=9QvHYUq5EgEpqIaZArrm30v_USM5zIaf0UN-z6cHWhs,1058
2
- database_wrapper_pgsql/connector.py,sha256=Hicf9DHxOjv6H77ySdTi26YmKjlSTzOIWQrjETq3LRg,25027
3
- database_wrapper_pgsql/db_wrapper_pgsql.py,sha256=Tm18dzSOINz4Z8xHWynLCPf-HmD_SFP0PYVE8ZPd_A4,1921
4
- database_wrapper_pgsql/db_wrapper_pgsql_async.py,sha256=d4AZp_dHdmob7oJlEst2DnhMqpeXeG6WOzI8k5OldKg,2024
5
- database_wrapper_pgsql/db_wrapper_pgsql_mixin.py,sha256=b4m7YSk5OjukuCMky_VwFT8TYCqjU2goNnSC2HOM0HA,5452
6
- database_wrapper_pgsql/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- database_wrapper_pgsql-0.2.7.dist-info/METADATA,sha256=kgUU9s_hDPvWuvg-YC-86i8XTTCGkd3nQGA86JMgGdc,3229
8
- database_wrapper_pgsql-0.2.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
- database_wrapper_pgsql-0.2.7.dist-info/top_level.txt,sha256=EQhZLk12wRdsSp-Lo3Jc4cXmNfG8y5EouNv_7OBCSGo,23
10
- database_wrapper_pgsql-0.2.7.dist-info/RECORD,,