Skip to content

Commit 493f6aa

Browse files
Copy existing code from seed project
1 parent a9792c5 commit 493f6aa

File tree

4 files changed

+487
-0
lines changed

4 files changed

+487
-0
lines changed

db_wrapper/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""
2+
Database module, exposes classes for simplifying working with Postgres.
3+
4+
class.ConnectionParameters:
5+
Dataclass to assist with correctly setting up a Postgres connection.
6+
7+
class.Client:
8+
The bread & butter of this module. Wraps aiopg to simplify connecting to,
9+
executing queries on, & disconnecting from a Postgres database.
10+
11+
class.Model:
12+
Not quite an ORM, but close. Used to assist in defining database queries &
13+
associating a Model type. Using this class will help manage separation of
14+
concerns.
15+
16+
class.ModelData:
17+
A simple TypedDict to define the shape of the data for a specific
18+
Model instance.
19+
"""
20+
from .connection import ConnectionParameters
21+
from .client import Client
22+
from .model import Model, ModelData

db_wrapper/client.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
"""Wrapper on aiopg to simplify connecting to & interacting with db."""
2+
3+
from __future__ import annotations
4+
from typing import (
5+
Any,
6+
TypeVar,
7+
Union,
8+
Optional,
9+
Hashable,
10+
List,
11+
Dict)
12+
13+
import aiopg # type: ignore
14+
from psycopg2.extras import register_uuid
15+
# importing for the sole purpose of re-exporting
16+
# pylint: disable=unused-import
17+
from psycopg2 import sql
18+
19+
from .connection import ConnectionParameters, connect
20+
21+
# add uuid support to psycopg2 & Postgres
22+
register_uuid()
23+
24+
25+
# Generic doesn't need a more descriptive name
26+
# pylint: disable=invalid-name
27+
T = TypeVar('T')
28+
29+
# pylint: disable=unsubscriptable-object
30+
Query = Union[str, sql.Composed]
31+
32+
33+
class Client:
34+
"""Class to manage database connection & expose necessary methods to user.
35+
36+
Stores connection parameters on init, then exposes methods to
37+
asynchronously connect & disconnect the database, as well as execute SQL
38+
queries.
39+
"""
40+
41+
_connection_params: ConnectionParameters
42+
_connection: aiopg.Connection
43+
44+
def __init__(self, connection_params: ConnectionParameters) -> None:
45+
self._connection_params = connection_params
46+
47+
async def connect(self) -> None:
48+
"""Connect to the database."""
49+
self._connection = await connect(self._connection_params)
50+
51+
async def disconnect(self) -> None:
52+
"""Disconnect from the database."""
53+
await self._connection.close()
54+
55+
# PENDS python 3.9 support in pylint
56+
# pylint: disable=unsubscriptable-object
57+
@staticmethod
58+
async def _execute_query(
59+
cursor: aiopg.Cursor,
60+
query: Query,
61+
params: Optional[Dict[Hashable, Any]] = None,
62+
) -> None:
63+
if params:
64+
await cursor.execute(query, params)
65+
else:
66+
await cursor.execute(query)
67+
68+
# PENDS python 3.9 support in pylint
69+
# pylint: disable=unsubscriptable-object
70+
async def execute(
71+
self,
72+
query: Query,
73+
params: Optional[Dict[Hashable, Any]] = None,
74+
) -> None:
75+
"""Execute the given SQL query.
76+
77+
Arguments:
78+
query (Query) -- the SQL query to execute
79+
params (dict) -- a dictionary of parameters to interpolate when
80+
executing the query
81+
82+
Returns:
83+
None
84+
"""
85+
async with self._connection.cursor() as cursor:
86+
await self._execute_query(cursor, query, params)
87+
88+
# PENDS python 3.9 support in pylint
89+
# pylint: disable=unsubscriptable-object
90+
async def execute_and_return(
91+
self,
92+
query: Query,
93+
params: Optional[Dict[Hashable, Any]] = None,
94+
) -> List[T]:
95+
"""Execute the given SQL query & return the result.
96+
97+
Arguments:
98+
query (Query) -- the SQL query to execute
99+
params (dict) -- a dictionary of parameters to interpolate when
100+
executing the query
101+
102+
Returns:
103+
List containing all the rows that matched the query.
104+
"""
105+
async with self._connection.cursor() as cursor:
106+
await self._execute_query(cursor, query, params)
107+
108+
result = []
109+
110+
async for row in cursor:
111+
result.append(row)
112+
113+
return result

db_wrapper/connection.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""Interface & function for defining & establishing connection to Postgres."""
2+
3+
import asyncio
4+
from dataclasses import dataclass
5+
import logging
6+
from typing import Optional
7+
8+
from psycopg2 import OperationalError # type: ignore
9+
10+
# no stubs available, starting out by just determining correct return
11+
# types & annotating in my wrappers here
12+
import aiopg # type: ignore
13+
14+
#
15+
# Postgres Connection helper
16+
#
17+
18+
LOGGER = logging.getLogger(__name__)
19+
20+
21+
@dataclass
22+
class ConnectionParameters:
23+
"""Defines connection parameters for database."""
24+
25+
host: str
26+
user: str
27+
password: str
28+
database: str
29+
30+
31+
async def _try_connect(
32+
connection_params: ConnectionParameters,
33+
retries: int = 1
34+
) -> aiopg.Connection:
35+
database = connection_params.database
36+
user = connection_params.user
37+
password = connection_params.password
38+
host = connection_params.host
39+
40+
dsn = f'dbname={database} user={user} password={password} host={host}'
41+
42+
# PENDS python 3.9 support in pylint
43+
# pylint: disable=unsubscriptable-object
44+
connection: Optional[aiopg.Connection] = None
45+
46+
LOGGER.info(
47+
f'Attempting to connect to database {database} as {user}@{host}...')
48+
49+
while connection is None:
50+
try:
51+
connection = await aiopg.connect(dsn)
52+
except OperationalError as err:
53+
print(type(err))
54+
if retries > 12:
55+
raise ConnectionError(
56+
'Max number of connection attempts has been reached (12)'
57+
) from err
58+
59+
LOGGER.info(
60+
f'Connection failed ({retries} time(s))'
61+
'retrying again in 5 seconds...')
62+
63+
await asyncio.sleep(5)
64+
return await _try_connect(connection_params, retries + 1)
65+
66+
return connection
67+
68+
69+
# PENDS python 3.9 support in pylint
70+
# pylint: disable=unsubscriptable-object
71+
async def connect(
72+
connection_params: ConnectionParameters
73+
) -> aiopg.Connection:
74+
"""Establish database connection."""
75+
return await _try_connect(connection_params)

0 commit comments

Comments
 (0)