source: db.py@ 527c097

main
Last change on this file since 527c097 was 527c097, checked in by teo <teodorbogoeski@…>, 3 months ago

Add connection pooling and transaction management

  • Property mode set to 100644
File size: 3.5 KB
Line 
1import datetime
2import psycopg2
3from psycopg2 import pool
4from contextlib import contextmanager
5
6DB_CONFIG = {
7 "host": "localhost",
8 "port": 5432,
9 "dbname": "frruas_db",
10 "user": "frruas_user",
11 "password": "frruas_pass",
12 "options": "-c search_path=project,public",
13}
14
15# -- Connection Pool ----------------------------------------------------------
16# Reuses database connections instead of opening a new one per query.
17# SimpleConnectionPool maintains between minconn and maxconn connections.
18
19_pool = None
20
21
22def _get_pool():
23 global _pool
24 if _pool is None:
25 _pool = pool.SimpleConnectionPool(minconn=2, maxconn=10, **DB_CONFIG)
26 return _pool
27
28
29@contextmanager
30def get_connection():
31 """Get a read-only connection from the pool.
32 Autocommit is enabled so SELECTs don't need explicit commit.
33 Connection is returned to the pool on exit."""
34 p = _get_pool()
35 conn = p.getconn()
36 try:
37 conn.autocommit = True
38 yield conn
39 finally:
40 conn.autocommit = False
41 p.putconn(conn)
42
43
44@contextmanager
45def get_transaction():
46 """Get a connection with explicit transaction management.
47 Commits on successful exit, rolls back on exception.
48 Connection is returned to the pool in both cases."""
49 p = _get_pool()
50 conn = p.getconn()
51 try:
52 conn.autocommit = False
53 yield conn
54 conn.commit()
55 except Exception:
56 conn.rollback()
57 raise
58 finally:
59 p.putconn(conn)
60
61
62def close_pool():
63 """Close all connections in the pool."""
64 global _pool
65 if _pool is not None:
66 _pool.closeall()
67 _pool = None
68
69
70# -- UI Helpers ---------------------------------------------------------------
71
72
73def pick_from_list(prompt, items):
74 """Display items as a numbered list, return selected 0-based index or None."""
75 if not items:
76 print(" (no items)")
77 return None
78 for i, item in enumerate(items, start=1):
79 print(f" {i}. {item}")
80 print(f" 0. Cancel / Go back")
81 while True:
82 choice = input(prompt).strip()
83 if choice == "0" or choice == "":
84 return None
85 try:
86 idx = int(choice) - 1
87 if 0 <= idx < len(items):
88 return idx
89 except ValueError:
90 pass
91 print(" Invalid choice. Please try again.")
92
93
94def print_table(headers, rows):
95 """Print a formatted ASCII table."""
96 if not rows:
97 print(" (no data)")
98 return
99 str_rows = [[str(v) for v in row] for row in rows]
100 widths = [len(h) for h in headers]
101 for row in str_rows:
102 for i, val in enumerate(row):
103 widths[i] = max(widths[i], len(val))
104 header_line = " | ".join(h.ljust(widths[i]) for i, h in enumerate(headers))
105 print(f" {header_line}")
106 print(f" {'-+-'.join('-' * w for w in widths)}")
107 for row in str_rows:
108 print(f" {' | '.join(val.ljust(widths[i]) for i, val in enumerate(row))}")
109
110
111def input_date(prompt):
112 """Prompt until valid YYYY-MM-DD entered. Returns datetime.date."""
113 while True:
114 val = input(prompt).strip()
115 try:
116 return datetime.datetime.strptime(val, "%Y-%m-%d").date()
117 except ValueError:
118 print(" Invalid date format. Please use YYYY-MM-DD.")
119
120
121def input_time(prompt):
122 """Prompt until valid HH:MM entered. Returns datetime.time."""
123 while True:
124 val = input(prompt).strip()
125 try:
126 return datetime.datetime.strptime(val, "%H:%M").time()
127 except ValueError:
128 print(" Invalid time format. Please use HH:MM.")
Note: See TracBrowser for help on using the repository browser.