改为新的db_handler
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
"""Postgres helper for reading tables into pandas DataFrame.
|
||||
|
||||
Provides PgHandler class with methods:
|
||||
- read_table(table, schema='public', columns=None)
|
||||
- read_table_by_conditions(table, conditions, schema='public', columns=None)
|
||||
|
||||
Conditions format (single dict or list of dicts):
|
||||
{
|
||||
'column': 'aaa',
|
||||
'condition': 'larger than', # 'equal', 'larger than', 'lower than'
|
||||
'target': ['relative', 'bbb'] or ['direct', 123]
|
||||
}
|
||||
|
||||
All returned results are pandas.DataFrame.
|
||||
"""
|
||||
from typing import Optional, List, Any
|
||||
import pandas as pd
|
||||
import psycopg2
|
||||
|
||||
|
||||
class PgHandler:
|
||||
"""Simple Postgres handler for reading tables into pandas DataFrames.
|
||||
|
||||
Parameters (pass to constructor):
|
||||
- host, port, user, password, database (db)
|
||||
|
||||
Behavior:
|
||||
- Connects directly to the given host:port with provided credentials.
|
||||
"""
|
||||
|
||||
def __init__(self, *, host: str = "localhost", port: int = 5432, user: str = "postgres",
|
||||
password: Optional[str] = None, database: Optional[str] = None):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.user = user
|
||||
self.password = password
|
||||
self.database = database
|
||||
|
||||
# internal runtime resources
|
||||
self._conn = None
|
||||
|
||||
def _get_conn(self):
|
||||
"""Lazily create and return a psycopg2 connection."""
|
||||
if self._conn is None:
|
||||
if self.database is None:
|
||||
raise ValueError("database must be provided")
|
||||
|
||||
connect_host = self.host
|
||||
connect_port = self.port
|
||||
|
||||
self._conn = psycopg2.connect(host=connect_host, port=connect_port, user=self.user,
|
||||
password=self.password, dbname=self.database)
|
||||
return self._conn
|
||||
|
||||
def close(self):
|
||||
"""Close the underlying DB connection."""
|
||||
if self._conn is not None:
|
||||
try:
|
||||
self._conn.close()
|
||||
finally:
|
||||
self._conn = None
|
||||
|
||||
def read_table(self, table: str, schema: str = "public", columns: Optional[List[str]] = None) -> pd.DataFrame:
|
||||
"""Read a table (or specific columns) and return a DataFrame.
|
||||
|
||||
If columns is None or empty -> select *.
|
||||
"""
|
||||
conn = self._get_conn()
|
||||
cols = "*" if not columns else ", ".join([f'"{c}"' for c in columns])
|
||||
q = f'SELECT {cols} FROM "{schema}"."{table}"'
|
||||
return pd.read_sql_query(q, con=conn)
|
||||
|
||||
def read_table_by_conditions(self, table: str, conditions: Any, schema: str = "public", columns: Optional[List[str]] = None) -> pd.DataFrame:
|
||||
"""Read rows filtered by condition dict or list of such dicts.
|
||||
|
||||
conditions can be a single dict or a list of dicts. Each dict has keys:
|
||||
- column: column name to compare
|
||||
- condition: 'equal', 'larger than', 'lower than'
|
||||
- target: ['relative', other_column] or ['direct', value]
|
||||
|
||||
Multiple conditions are AND-ed together.
|
||||
"""
|
||||
if isinstance(conditions, dict):
|
||||
condition_list = [conditions]
|
||||
elif isinstance(conditions, list):
|
||||
condition_list = conditions
|
||||
else:
|
||||
raise ValueError("conditions must be a dict or list of dicts")
|
||||
|
||||
# build where clauses
|
||||
where_clauses = []
|
||||
params_list: List[Any] = []
|
||||
|
||||
for cond in condition_list:
|
||||
col = cond.get('column')
|
||||
op = cond.get('condition')
|
||||
target = cond.get('target')
|
||||
if col is None or op is None or target is None:
|
||||
raise ValueError(f"condition dict missing keys: {cond}")
|
||||
|
||||
if not isinstance(target, (list, tuple)) or len(target) < 2:
|
||||
raise ValueError(f"target must be a list like ['relative','col'] or ['direct', value]: {target}")
|
||||
|
||||
kind = target[0]
|
||||
left = f'"{col}"'
|
||||
if kind == 'relative':
|
||||
other_col = target[1]
|
||||
right = f'"{other_col}"'
|
||||
if op == 'equal':
|
||||
clause = f"{left} = {right}"
|
||||
elif op == 'larger than':
|
||||
clause = f"{left} > {right}"
|
||||
elif op == 'lower than':
|
||||
clause = f"{left} < {right}"
|
||||
else:
|
||||
raise ValueError(f"unsupported condition operator: {op}")
|
||||
elif kind == 'direct':
|
||||
val = target[1]
|
||||
if op == 'equal':
|
||||
clause = f"{left} = %s"
|
||||
elif op == 'larger than':
|
||||
clause = f"{left} > %s"
|
||||
elif op == 'lower than':
|
||||
clause = f"{left} < %s"
|
||||
else:
|
||||
raise ValueError(f"unsupported condition operator: {op}")
|
||||
params_list.append(val)
|
||||
else:
|
||||
raise ValueError(f"target first element must be 'relative' or 'direct', got {kind}")
|
||||
|
||||
where_clauses.append(clause)
|
||||
|
||||
where_sql = " AND ".join(where_clauses) if where_clauses else ""
|
||||
cols = "*" if not columns else ", ".join([f'"{c}"' for c in columns])
|
||||
q = f'SELECT {cols} FROM "{schema}"."{table}"'
|
||||
if where_sql:
|
||||
q = q + " WHERE " + where_sql
|
||||
|
||||
conn = self._get_conn()
|
||||
# pandas.read_sql_query accepts DB-API connection and positional params
|
||||
return pd.read_sql_query(q, con=conn, params=tuple(params_list) if params_list else None)
|
||||
|
||||
def query(self, query: str) -> list[dict]:
|
||||
conn = self._get_conn()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(query)
|
||||
payload = cursor.fetchall()
|
||||
columns = [desc[0] for desc in cursor.description]
|
||||
result_list = [dict(zip(columns, row)) for row in payload]
|
||||
return result_list
|
||||
|
||||
def get_all_tables(self, schema: str = "public") -> list[str]:
|
||||
"""Get a list of all table names in the specified schema."""
|
||||
conn = self._get_conn()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(f"select tablename from pg_tables where schemaname='{schema}'", (schema,))
|
||||
tables = [row[0] for row in cursor.fetchall()]
|
||||
return tables
|
||||
|
||||
|
||||
def __enter__(self):
|
||||
# ensure connection is created and return self for use in `with` blocks
|
||||
self._get_conn()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
# always close connection on exit
|
||||
self.close()
|
||||
# do not suppress exceptions
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from src.config.load_config import project_config
|
||||
|
||||
|
||||
|
||||
host = project_config['db_host']
|
||||
port = project_config['db_port']
|
||||
user = project_config['db_user']
|
||||
password = project_config['db_password']
|
||||
database = project_config['db_name']
|
||||
|
||||
pg_handler = PgHandler(
|
||||
host=host, port=port, user=user, password=password or None, database=database,
|
||||
)
|
||||
|
||||
with pg_handler as pg:
|
||||
xxx = pg.get_all_tables()
|
||||
print(xxx)
|
||||
|
||||
|
||||
#
|
||||
# with PgHandler(host=host, port=port, user=user, password=password or None, database=database) as pg:
|
||||
# sql = f'select "CEC manager" from public.cec_manager where "Vendor Name" = \'Sutherland\' and "Segment" = \'Standard\''
|
||||
# df = pg.query(sql)
|
||||
# print('111')
|
||||
Reference in New Issue
Block a user