Skip to content

Commit cc9a55d

Browse files
author
Sergio García Prado
committed
ISSUE #367
* Improve `DatabaseClient` class.
1 parent 9744e15 commit cc9a55d

File tree

3 files changed

+122
-41
lines changed
  • packages/core/minos-microservice-common

3 files changed

+122
-41
lines changed

packages/core/minos-microservice-common/minos/common/database/clients/abc.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,28 +33,41 @@ class DatabaseClient(ABC, BuildableMixin):
3333
def _from_config(cls, config: Config, name: Optional[str] = None, **kwargs) -> DatabaseClient:
3434
return super()._from_config(config, **config.get_database_by_name(name), **kwargs)
3535

36-
@abstractmethod
3736
async def is_valid(self, **kwargs) -> bool:
3837
"""Check if the instance is valid.
3938
4039
:return: ``True`` if it is valid or ``False`` otherwise.
4140
"""
41+
return await self._is_valid(**kwargs)
42+
43+
@abstractmethod
44+
async def _is_valid(self, **kwargs) -> bool:
45+
raise NotImplementedError
4246

4347
async def reset(self, **kwargs) -> None:
4448
"""Reset the current instance status.
4549
4650
:param kwargs: Additional named parameters.
4751
:return: This method does not return anything.
4852
"""
53+
return await self._reset(**kwargs)
4954

5055
@abstractmethod
56+
async def _reset(self, **kwargs) -> None:
57+
raise NotImplementedError
58+
5159
async def execute(self, *args, **kwargs) -> None:
5260
"""Execute an operation.
5361
5462
:param args: Additional positional arguments.
5563
:param kwargs: Additional named arguments.
5664
:return: This method does not return anything.
5765
"""
66+
await self._execute(*args, **kwargs)
67+
68+
@abstractmethod
69+
async def _execute(self, *args, **kwargs) -> None:
70+
raise NotImplementedError
5871

5972
async def fetch_one(self, *args, **kwargs) -> Any:
6073
"""Fetch one value.
@@ -65,22 +78,30 @@ async def fetch_one(self, *args, **kwargs) -> Any:
6578
"""
6679
return await self.fetch_all(*args, **kwargs).__anext__()
6780

68-
@abstractmethod
6981
def fetch_all(self, *args, **kwargs) -> AsyncIterator[Any]:
7082
"""Fetch all values with an asynchronous iterator.
7183
72-
:param args: Additional positional arguments.
7384
:param kwargs: Additional named arguments.
7485
:return: This method does not return anything.
7586
"""
87+
return self._fetch_all(*args, **kwargs)
7688

77-
@property
7889
@abstractmethod
90+
def _fetch_all(self, *args, **kwargs) -> AsyncIterator[Any]:
91+
raise NotImplementedError
92+
93+
@property
7994
def notifications(self) -> Queue:
8095
"""Get the notifications queue.
8196
8297
:return: A ``Queue`` instance.
8398
"""
99+
return self._notifications
100+
101+
@property
102+
@abstractmethod
103+
def _notifications(self) -> Queue:
104+
raise NotImplementedError
84105

85106

86107
class DatabaseClientBuilder(Builder[DatabaseClient]):

packages/core/minos-microservice-common/minos/common/database/clients/aiopg.py

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,7 @@ async def _close_connection(self):
108108
self._connection = None
109109
logger.debug(f"Destroyed {self.database!r} database connection identified by {id(self._connection)}!")
110110

111-
async def is_valid(self) -> bool:
112-
"""Check if the instance is valid.
113-
114-
:return: ``True`` if it is valid or ``False`` otherwise.
115-
"""
111+
async def _is_valid(self) -> bool:
116112
if self._connection is None:
117113
return False
118114

@@ -124,49 +120,26 @@ async def is_valid(self) -> bool:
124120

125121
return not self._connection.closed
126122

127-
async def reset(self, **kwargs) -> None:
128-
"""Reset the current instance status.
129-
130-
:param kwargs: Additional named parameters.
131-
:return: This method does not return anything.
132-
"""
123+
async def _reset(self, **kwargs) -> None:
133124
await self._destroy_cursor(**kwargs)
134125

135126
# noinspection PyUnusedLocal
136-
async def fetch_all(
127+
async def _fetch_all(
137128
self,
138129
*args,
139130
timeout: Optional[float] = None,
140131
lock: Optional[int] = None,
141132
**kwargs,
142133
) -> AsyncIterator[tuple]:
143-
"""Fetch all values with an asynchronous iterator.
144-
145-
:param timeout: An optional timeout.
146-
:param lock: Optional key to perform the query with locking. If not set, the query is performed without any
147-
lock.
148-
:param kwargs: Additional named arguments.
149-
:return: This method does not return anything.
150-
"""
151134
await self._create_cursor()
152135

153136
async for row in self._cursor:
154137
yield row
155138

156139
# noinspection PyUnusedLocal
157-
async def execute(
140+
async def _execute(
158141
self, operation: Any, parameters: Any = None, *, timeout: Optional[float] = None, lock: Any = None, **kwargs
159142
) -> None:
160-
"""Execute an operation.
161-
162-
:param operation: Query to be executed.
163-
:param parameters: Parameters to be projected into the query.
164-
:param timeout: An optional timeout.
165-
:param lock: Optional key to perform the query with locking. If not set, the query is performed without any
166-
lock.
167-
:param kwargs: Additional named arguments.
168-
:return: This method does not return anything.
169-
"""
170143
await self._create_cursor(lock=lock)
171144
try:
172145
await self._cursor.execute(operation=operation, parameters=parameters, timeout=timeout)
@@ -216,11 +189,7 @@ def cursor(self) -> Optional[Cursor]:
216189
return self._cursor
217190

218191
@property
219-
def notifications(self) -> ClosableQueue:
220-
"""Get the notifications queue.
221-
222-
:return: A ``ClosableQueue`` instance.
223-
"""
192+
def _notifications(self) -> ClosableQueue:
224193
return self._connection.notifies
225194

226195
@property

packages/core/minos-microservice-common/tests/test_common/test_database/test_clients/test_abc.py

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22
from abc import (
33
ABC,
44
)
5+
from asyncio import (
6+
Queue,
7+
)
8+
from typing import (
9+
Any,
10+
AsyncIterator,
11+
)
12+
from unittest.mock import (
13+
AsyncMock,
14+
MagicMock,
15+
PropertyMock,
16+
call,
17+
patch,
18+
)
519

620
from minos.common import (
721
AiopgDatabaseClient,
@@ -11,18 +25,95 @@
1125
)
1226
from tests.utils import (
1327
CommonTestCase,
28+
FakeAsyncIterator,
1429
)
1530

1631

32+
class _DatabaseClient(DatabaseClient):
33+
"""For testing purposes."""
34+
35+
async def _is_valid(self, **kwargs) -> bool:
36+
"""For testing purposes."""
37+
38+
async def _reset(self, **kwargs) -> None:
39+
"""For testing purposes."""
40+
41+
async def _execute(self, *args, **kwargs) -> None:
42+
"""For testing purposes."""
43+
44+
def _fetch_all(self, *args, **kwargs) -> AsyncIterator[Any]:
45+
"""For testing purposes."""
46+
47+
# noinspection PyPropertyDefinition
48+
@property
49+
def _notifications(self) -> Queue:
50+
"""For testing purposes."""
51+
52+
1753
class TestDatabaseClient(unittest.IsolatedAsyncioTestCase):
1854
def test_abstract(self):
1955
self.assertTrue(issubclass(DatabaseClient, (ABC, BuildableMixin)))
56+
expected = {"_notifications", "_is_valid", "_execute", "_fetch_all", "_reset"}
2057
# noinspection PyUnresolvedReferences
21-
self.assertEqual({"notifications", "is_valid", "execute", "fetch_all"}, DatabaseClient.__abstractmethods__)
58+
self.assertEqual(expected, DatabaseClient.__abstractmethods__)
2259

2360
def test_get_builder(self):
2461
self.assertIsInstance(DatabaseClient.get_builder(), DatabaseClientBuilder)
2562

63+
async def test_is_valid(self):
64+
mock = AsyncMock(side_effect=[True, False])
65+
client = _DatabaseClient()
66+
client._is_valid = mock
67+
68+
self.assertEqual(True, await client.is_valid())
69+
self.assertEqual(False, await client.is_valid())
70+
71+
self.assertEqual([call(), call()], mock.call_args_list)
72+
73+
async def test_reset(self):
74+
mock = AsyncMock()
75+
client = _DatabaseClient()
76+
client._reset = mock
77+
78+
await client.reset()
79+
80+
self.assertEqual([call()], mock.call_args_list)
81+
82+
async def test_execute(self):
83+
mock = AsyncMock()
84+
client = _DatabaseClient()
85+
client._execute = mock
86+
87+
await client.execute("foo")
88+
89+
self.assertEqual([call("foo")], mock.call_args_list)
90+
91+
async def test_fetch_all(self):
92+
mock = MagicMock(return_value=FakeAsyncIterator(["one", "two"]))
93+
client = _DatabaseClient()
94+
client._fetch_all = mock
95+
96+
self.assertEqual(["one", "two"], [v async for v in client.fetch_all()])
97+
98+
self.assertEqual([call()], mock.call_args_list)
99+
100+
async def test_fetch_one(self):
101+
mock = MagicMock(return_value=FakeAsyncIterator(["one", "two"]))
102+
client = _DatabaseClient()
103+
client._fetch_all = mock
104+
105+
self.assertEqual("one", await client.fetch_one())
106+
107+
self.assertEqual([call()], mock.call_args_list)
108+
109+
async def test_notifications(self):
110+
expected = Queue()
111+
client = _DatabaseClient()
112+
with patch.object(_DatabaseClient, "_notifications", new_callable=PropertyMock, return_value=expected) as mock:
113+
self.assertEqual(expected, client.notifications)
114+
115+
self.assertEqual([call()], mock.call_args_list)
116+
26117

27118
class TestDatabaseClientBuilder(CommonTestCase):
28119
def test_with_name(self):

0 commit comments

Comments
 (0)