Skip to content

Commit 25760d4

Browse files
author
Sergio García Prado
committed
ISSUE #367
* Increase coverage up to "100%". * Improve `Lock` API.
1 parent cc4bc79 commit 25760d4

File tree

13 files changed

+189
-75
lines changed

13 files changed

+189
-75
lines changed

packages/core/minos-microservice-aggregate/tests/utils.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,11 @@ def __init__(self, key=None, *args, **kwargs):
101101
key = "fake"
102102
super().__init__(key, *args, **kwargs)
103103

104-
async def __aexit__(self, exc_type, exc_val, exc_tb):
105-
return
104+
async def acquire(self) -> None:
105+
"""For testing purposes."""
106+
107+
async def release(self):
108+
"""For testing purposes."""
106109

107110

108111
class FakeLockPool(LockPool):
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from .abc import (
22
DatabaseClient,
33
DatabaseClientBuilder,
4-
DatabaseClientException,
5-
IntegrityException,
6-
UnableToConnectException,
74
)
85
from .aiopg import (
96
AiopgDatabaseClient,
107
)
8+
from .exceptions import (
9+
DatabaseClientException,
10+
IntegrityException,
11+
UnableToConnectException,
12+
)

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

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,68 +24,86 @@
2424
from ...config import (
2525
Config,
2626
)
27-
from ...exceptions import (
28-
MinosException,
29-
)
3027

3128

3229
class DatabaseClient(ABC, BuildableMixin):
33-
"""TODO"""
30+
"""Database Client base class."""
3431

3532
@classmethod
3633
def _from_config(cls, config: Config, name: Optional[str] = None, **kwargs) -> DatabaseClient:
3734
return super()._from_config(config, **config.get_database_by_name(name), **kwargs)
3835

3936
@abstractmethod
4037
async def is_valid(self, **kwargs) -> bool:
41-
"""TODO"""
38+
"""Check if the instance is valid.
39+
40+
:return: ``True`` if it is valid or ``False`` otherwise.
41+
"""
4242

4343
async def reset(self, **kwargs) -> None:
44-
"""TODO"""
44+
"""Reset the current instance status.
4545
46-
@property
47-
@abstractmethod
48-
def notifications(self) -> Queue:
49-
"""TODO"""
46+
:param kwargs: Additional named parameters.
47+
:return: This method does not return anything.
48+
"""
5049

5150
@abstractmethod
5251
async def execute(self, *args, **kwargs) -> None:
53-
"""TODO"""
52+
"""Execute an operation.
53+
54+
:param args: Additional positional arguments.
55+
:param kwargs: Additional named arguments.
56+
:return: This method does not return anything.
57+
"""
5458

5559
async def fetch_one(self, *args, **kwargs) -> Any:
56-
"""TODO"""
60+
"""Fetch one value.
61+
62+
:param args: Additional positional arguments.
63+
:param kwargs: Additional named arguments.
64+
:return: This method does not return anything.
65+
"""
5766
return await self.fetch_all(*args, **kwargs).__anext__()
5867

5968
@abstractmethod
6069
def fetch_all(self, *args, **kwargs) -> AsyncIterator[Any]:
61-
"""TODO"""
70+
"""Fetch all values with an asynchronous iterator.
71+
72+
:param args: Additional positional arguments.
73+
:param kwargs: Additional named arguments.
74+
:return: This method does not return anything.
75+
"""
76+
77+
@property
78+
@abstractmethod
79+
def notifications(self) -> Queue:
80+
"""Get the notifications queue.
81+
82+
:return: A ``Queue`` instance.
83+
"""
6284

6385

6486
class DatabaseClientBuilder(Builder[DatabaseClient]):
65-
"""TODO"""
87+
"""Database Client Builder class."""
6688

6789
def with_name(self, name: str) -> DatabaseClientBuilder:
68-
"""TODO"""
90+
"""Set name.
91+
92+
:param name: The name to be added.
93+
:return: This method return the builder instance.
94+
"""
6995
self.kwargs["name"] = name
7096
return self
7197

7298
def with_config(self, config: Config) -> DatabaseClientBuilder:
73-
"""TODO"""
99+
"""Set config.
100+
101+
:param config: The config to be set.
102+
:return: This method return the builder instance.
103+
"""
74104
database_config = config.get_database_by_name(self.kwargs.get("name"))
75105
self.kwargs |= database_config
76106
return self
77107

78108

79109
DatabaseClient.set_builder(DatabaseClientBuilder)
80-
81-
82-
class DatabaseClientException(MinosException):
83-
"""TODO"""
84-
85-
86-
class UnableToConnectException(DatabaseClientException):
87-
"""TODO"""
88-
89-
90-
class IntegrityException(DatabaseClientException):
91-
"""TODO"""

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828

2929
from .abc import (
3030
DatabaseClient,
31+
)
32+
from .exceptions import (
3133
IntegrityException,
3234
UnableToConnectException,
3335
)
@@ -138,7 +140,7 @@ async def fetch_all(
138140
lock: Optional[int] = None,
139141
**kwargs,
140142
) -> AsyncIterator[tuple]:
141-
"""Submit a SQL query and return an asynchronous iterator.
143+
"""Fetch all values with an asynchronous iterator.
142144
143145
:param timeout: An optional timeout.
144146
:param lock: Optional key to perform the query with locking. If not set, the query is performed without any
@@ -155,7 +157,7 @@ async def fetch_all(
155157
async def execute(
156158
self, operation: Any, parameters: Any = None, *, timeout: Optional[float] = None, lock: Any = None, **kwargs
157159
) -> None:
158-
"""Submit a SQL query.
160+
"""Execute an operation.
159161
160162
:param operation: Query to be executed.
161163
:param parameters: Parameters to be projected into the query.
@@ -185,11 +187,11 @@ async def _create_cursor(self, *args, lock: Optional[Hashable] = None, **kwargs)
185187
)
186188

187189
self._lock = DatabaseLock(self, lock, *args, **kwargs)
188-
await self._lock.__aenter__()
190+
await self._lock.acquire()
189191

190192
async def _destroy_cursor(self, **kwargs):
191193
if self._lock is not None:
192-
await self._lock.__aexit__(None, None, None)
194+
await self._lock.release()
193195
self._lock = None
194196

195197
if self._cursor is not None:
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from ...exceptions import (
2+
MinosException,
3+
)
4+
5+
6+
class DatabaseClientException(MinosException):
7+
"""Base exception for database client."""
8+
9+
10+
class UnableToConnectException(DatabaseClientException):
11+
"""Exception to be raised when database client is not able to connect to the database."""
12+
13+
14+
class IntegrityException(DatabaseClientException):
15+
"""Exception to be raised when an integrity check is not satisfied."""

packages/core/minos-microservice-common/minos/common/database/locks.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,18 @@ def __init__(self, client: DatabaseClient, key: Hashable, *args, **kwargs):
1919

2020
self.client = client
2121

22-
async def __aenter__(self):
22+
async def acquire(self) -> None:
23+
"""Acquire the lock.
24+
25+
:return: This method does not return anything.
26+
"""
2327
await self.client.execute("select pg_advisory_lock(%(hashed_key)s)", {"hashed_key": self.hashed_key})
24-
return self
2528

26-
async def __aexit__(self, exc_type, exc_val, exc_tb):
29+
async def release(self) -> None:
30+
"""Release the lock.
31+
32+
:return: This method does not return anything.
33+
"""
2734
await self.client.execute("select pg_advisory_unlock(%(hashed_key)s)", {"hashed_key": self.hashed_key})
2835

2936

packages/core/minos-microservice-common/minos/common/database/pools.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ async def _create_instance(self) -> Optional[DatabaseClient]:
6262
try:
6363
await instance.setup()
6464
except UnableToConnectException:
65-
await sleep(1)
65+
await sleep(0.1)
6666
return None
6767

6868
logger.info(f"Created {instance!r}!")

packages/core/minos-microservice-common/minos/common/locks.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@
44

55
from abc import (
66
ABC,
7+
abstractmethod,
78
)
89
from collections.abc import (
910
Hashable,
1011
)
11-
from contextlib import (
12-
AbstractAsyncContextManager,
13-
)
1412

1513
from cached_property import (
1614
cached_property,
@@ -24,7 +22,7 @@
2422
)
2523

2624

27-
class Lock(AbstractAsyncContextManager):
25+
class Lock(ABC):
2826
"""Lock base class."""
2927

3028
key: Hashable
@@ -35,6 +33,27 @@ def __init__(self, key: Hashable, *args, **kwargs):
3533

3634
self.key = key
3735

36+
async def __aenter__(self) -> Lock:
37+
await self.acquire()
38+
return self
39+
40+
async def __aexit__(self, exc_type, exc_val, exc_tb):
41+
await self.release()
42+
43+
@abstractmethod
44+
async def acquire(self) -> None:
45+
"""Acquire the lock.
46+
47+
:return: This method does not return anything.
48+
"""
49+
50+
@abstractmethod
51+
async def release(self):
52+
"""Release the lock.
53+
54+
:return: This method does not return anything.
55+
"""
56+
3857
@cached_property
3958
def hashed_key(self) -> int:
4059
"""Get the hashed key.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import unittest
2+
from abc import (
3+
ABC,
4+
)
5+
6+
from minos.common import (
7+
AiopgDatabaseClient,
8+
BuildableMixin,
9+
DatabaseClient,
10+
DatabaseClientBuilder,
11+
)
12+
from tests.utils import (
13+
CommonTestCase,
14+
)
15+
16+
17+
class TestDatabaseClient(unittest.IsolatedAsyncioTestCase):
18+
def test_abstract(self):
19+
self.assertTrue(issubclass(DatabaseClient, (ABC, BuildableMixin)))
20+
# noinspection PyUnresolvedReferences
21+
self.assertEqual({"notifications", "is_valid", "execute", "fetch_all"}, DatabaseClient.__abstractmethods__)
22+
23+
def test_get_builder(self):
24+
self.assertIsInstance(DatabaseClient.get_builder(), DatabaseClientBuilder)
25+
26+
27+
class TestDatabaseClientBuilder(CommonTestCase):
28+
def test_with_name(self):
29+
builder = DatabaseClientBuilder(AiopgDatabaseClient).with_name("query")
30+
self.assertEqual({"name": "query"}, builder.kwargs)
31+
32+
def test_with_config(self):
33+
builder = DatabaseClientBuilder(AiopgDatabaseClient).with_name("query").with_config(self.config)
34+
self.assertEqual({"name": "query"} | self.config.get_database_by_name("query"), builder.kwargs)
35+
36+
def test_build(self):
37+
builder = DatabaseClientBuilder(AiopgDatabaseClient).with_name("query").with_config(self.config)
38+
client = builder.build()
39+
40+
self.assertIsInstance(client, AiopgDatabaseClient)
41+
self.assertEqual(self.config.get_database_by_name("query")["database"], client.database)
42+
43+
44+
if __name__ == "__main__":
45+
unittest.main()

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def test_from_config(self):
5454

5555
async def test_is_valid_true(self):
5656
async with AiopgDatabaseClient.from_config(self.config) as client:
57-
self.assertTrue(client.is_valid())
57+
self.assertTrue(await client.is_valid())
5858

5959
async def test_is_valid_false_not_setup(self):
6060
client = AiopgDatabaseClient.from_config(self.config)
@@ -132,8 +132,8 @@ async def test_execute(self):
132132
)
133133

134134
async def test_execute_with_lock(self):
135-
with patch.object(DatabaseLock, "__aenter__") as enter_lock_mock:
136-
with patch.object(DatabaseLock, "__aexit__") as exit_lock_mock:
135+
with patch.object(DatabaseLock, "acquire") as enter_lock_mock:
136+
with patch.object(DatabaseLock, "release") as exit_lock_mock:
137137
async with AiopgDatabaseClient.from_config(self.config) as client:
138138
await client.execute(self.sql, lock="foo")
139139
self.assertEqual(1, enter_lock_mock.call_count)

0 commit comments

Comments
 (0)