33)
44
55import logging
6+ from asyncio import (
7+ TimeoutError ,
8+ )
69from collections .abc import (
710 AsyncIterator ,
11+ Iterable ,
812)
913from typing import (
1014 Optional ,
2226)
2327
2428from minos .common import (
29+ CircuitBreakerMixin ,
2530 ConnectionException ,
2631 DatabaseClient ,
2732 IntegrityException ,
3540logger = logging .getLogger (__name__ )
3641
3742
38- class AiopgDatabaseClient (DatabaseClient ):
43+ class AiopgDatabaseClient (DatabaseClient , CircuitBreakerMixin ):
3944 """Aiopg Database Client class."""
4045
4146 _connection : Optional [Connection ]
@@ -48,10 +53,17 @@ def __init__(
4853 port : Optional [int ] = None ,
4954 user : Optional [str ] = None ,
5055 password : Optional [str ] = None ,
56+ circuit_breaker_exceptions : Iterable [type ] = tuple (),
57+ connection_timeout : Optional [float ] = None ,
58+ cursor_timeout : Optional [float ] = None ,
5159 * args ,
5260 ** kwargs ,
5361 ):
54- super ().__init__ (* args , ** kwargs )
62+ super ().__init__ (
63+ * args ,
64+ ** kwargs ,
65+ circuit_breaker_exceptions = (OperationalError , TimeoutError , * circuit_breaker_exceptions ),
66+ )
5567
5668 if host is None :
5769 host = "localhost"
@@ -61,13 +73,20 @@ def __init__(
6173 user = "postgres"
6274 if password is None :
6375 password = ""
76+ if connection_timeout is None :
77+ connection_timeout = 1
78+ if cursor_timeout is None :
79+ cursor_timeout = 60
6480
6581 self ._database = database
6682 self ._host = host
6783 self ._port = port
6884 self ._user = user
6985 self ._password = password
7086
87+ self ._connection_timeout = connection_timeout
88+ self ._cursor_timeout = cursor_timeout
89+
7190 self ._connection = None
7291 self ._cursor = None
7392
@@ -80,19 +99,22 @@ async def _destroy(self) -> None:
8099 await self ._close_connection ()
81100
82101 async def _create_connection (self ):
83- try :
84- self ._connection = await aiopg .connect (
85- host = self .host , port = self .port , dbname = self .database , user = self .user , password = self .password
86- )
87- except OperationalError as exc :
88- msg = f"There was an { exc !r} while trying to get a database connection."
89- logger .warning (msg )
90- raise ConnectionException (msg )
102+ self ._connection = await self .with_circuit_breaker (self ._connect )
91103
92104 logger .debug (f"Created { self .database !r} database connection identified by { id (self ._connection )} !" )
93105
106+ async def _connect (self ) -> Connection :
107+ return await aiopg .connect (
108+ timeout = self ._connection_timeout ,
109+ host = self .host ,
110+ port = self .port ,
111+ dbname = self .database ,
112+ user = self .user ,
113+ password = self .password ,
114+ )
115+
94116 async def _close_connection (self ):
95- if self . _connection is not None and not self ._connection . closed :
117+ if await self .is_valid () :
96118 await self ._connection .close ()
97119 self ._connection = None
98120 logger .debug (f"Destroyed { self .database !r} database connection identified by { id (self ._connection )} !" )
@@ -114,12 +136,18 @@ async def _reset(self, **kwargs) -> None:
114136
115137 # noinspection PyUnusedLocal
116138 async def _fetch_all (self ) -> AsyncIterator [tuple ]:
117- await self ._create_cursor ()
139+ if self ._cursor is None :
140+ raise ProgrammingException ("An operation must be executed before fetching any value." )
141+
118142 try :
119143 async for row in self ._cursor :
120144 yield row
121145 except ProgrammingError as exc :
122146 raise ProgrammingException (str (exc ))
147+ except OperationalError as exc :
148+ msg = f"There was an { exc !r} while trying to connect to the database."
149+ logger .warning (msg )
150+ raise ConnectionException (msg )
123151
124152 # noinspection PyUnusedLocal
125153 async def _execute (self , operation : AiopgDatabaseOperation ) -> None :
@@ -131,10 +159,14 @@ async def _execute(self, operation: AiopgDatabaseOperation) -> None:
131159 await self ._cursor .execute (operation = operation .query , parameters = operation .parameters )
132160 except IntegrityError as exc :
133161 raise IntegrityException (f"The requested operation raised a integrity error: { exc !r} " )
162+ except OperationalError as exc :
163+ msg = f"There was an { exc !r} while trying to connect to the database."
164+ logger .warning (msg )
165+ raise ConnectionException (msg )
134166
135167 async def _create_cursor (self ):
136168 if self ._cursor is None :
137- self ._cursor = await self ._connection .cursor ()
169+ self ._cursor = await self ._connection .cursor (timeout = self . _cursor_timeout )
138170
139171 async def _destroy_cursor (self , ** kwargs ):
140172 if self ._cursor is not None :
0 commit comments