Skip to content

Commit 75af320

Browse files
author
Sergio García Prado
authored
Merge pull request #277 from minos-framework/issue-276-ref-resolve-bug
#276 - Fix `Ref.resolve` bug
2 parents 848def0 + 11aaebe commit 75af320

File tree

6 files changed

+107
-96
lines changed

6 files changed

+107
-96
lines changed

packages/core/minos-microservice-aggregate/minos/aggregate/entities/refs/models.py

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@
1616
SafeUUID,
1717
)
1818

19-
from dependency_injector.wiring import (
20-
Provide,
21-
inject,
22-
)
23-
2419
from minos.common import (
2520
DataDecoder,
2621
DataEncoder,
@@ -30,19 +25,10 @@
3025
SchemaDecoder,
3126
SchemaEncoder,
3227
)
33-
from minos.networks import (
34-
BrokerClient,
35-
BrokerClientPool,
36-
BrokerMessageV1,
37-
BrokerMessageV1Payload,
38-
)
3928

4029
from ...contextvars import (
4130
IS_REPOSITORY_SERIALIZATION_CONTEXT_VAR,
4231
)
43-
from ...exceptions import (
44-
RefException,
45-
)
4632

4733
logger = logging.getLogger(__name__)
4834
MT = TypeVar("MT", bound=Model)
@@ -53,14 +39,11 @@ class Ref(DeclarativeModel, UUID, Generic[MT]):
5339

5440
data: Union[MT, UUID]
5541

56-
@inject
57-
def __init__(self, data: Union[MT, UUID], *args, broker_pool: BrokerClientPool = Provide["broker_pool"], **kwargs):
42+
def __init__(self, data: Union[MT, UUID], *args, **kwargs):
5843
if not isinstance(data, UUID) and not hasattr(data, "uuid"):
5944
raise ValueError(f"data must be an {UUID!r} instance or have 'uuid' as one of its fields")
6045
DeclarativeModel.__init__(self, data, *args, **kwargs)
6146

62-
self._broker_pool = broker_pool
63-
6447
def __setitem__(self, key: str, value: Any) -> None:
6548
try:
6649
return super().__setitem__(key, value)
@@ -81,6 +64,9 @@ def __getitem__(self, item: str) -> Any:
8164
if item == "uuid":
8265
return self.uuid
8366

67+
if not self.resolved:
68+
raise KeyError(f"The reference is not resolved: {self!r}")
69+
8470
try:
8571
return self.data[item]
8672
except Exception:
@@ -106,6 +92,9 @@ def __getattr__(self, item: str) -> Any:
10692
if item == "data":
10793
raise exc
10894

95+
if not self.resolved:
96+
raise AttributeError(f"The reference is not resolved: {self!r}")
97+
10998
try:
11099
return getattr(self.data, item)
111100
except Exception:
@@ -223,24 +212,22 @@ async def resolve(self, force: bool = False, **kwargs) -> None:
223212
if not force and self.resolved:
224213
return
225214

226-
name = self.data_cls.__name__
227-
228-
message = BrokerMessageV1(f"Get{name}", BrokerMessageV1Payload({"uuid": self.uuid}))
229-
async with self._broker_pool.acquire() as broker:
230-
await broker.send(message)
231-
self.data = await self._get_response(broker)
215+
from .resolvers import (
216+
RefResolver,
217+
)
232218

233-
@staticmethod
234-
async def _get_response(broker: BrokerClient, **kwargs) -> MT:
235-
message = await broker.receive(**kwargs)
236-
if not message.ok:
237-
raise RefException(f"The received message is not ok: {message!r}")
238-
return message.content
219+
self.data = await RefResolver(**kwargs).resolve(self)
239220

240221
@property
241222
def resolved(self) -> bool:
242223
"""Check if the instance is already resolved.
243224
244225
:return: ``True`` if resolved or ``False`` otherwise.
245226
"""
246-
return not isinstance(self.data, UUID)
227+
try:
228+
return not isinstance(self.data, UUID)
229+
except AttributeError:
230+
return False
231+
232+
def __repr__(self) -> str:
233+
return f"{type(self).__name__}({self.data!r})"

packages/core/minos-microservice-aggregate/minos/aggregate/entities/refs/resolvers.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
)
1111
from typing import (
1212
Any,
13+
Union,
1314
)
1415
from uuid import (
1516
UUID,
@@ -69,13 +70,12 @@ async def resolve(self, data: Any, **kwargs) -> Any:
6970
return RefInjector(data, recovered).build()
7071

7172
async def _query(self, references: dict[str, set[UUID]]) -> dict[UUID, Model]:
73+
messages = (
74+
BrokerMessageV1(self.build_topic_name(name), BrokerMessageV1Payload({"uuids": uuids}))
75+
for name, uuids in references.items()
76+
)
7277
async with self.broker_pool.acquire() as broker:
73-
futures = (
74-
broker.send(
75-
BrokerMessageV1(f"_Get{simplified_name}Snapshots", BrokerMessageV1Payload({"uuids": uuids}))
76-
)
77-
for simplified_name, uuids in references.items()
78-
)
78+
futures = (broker.send(message) for message in messages)
7979
await gather(*futures)
8080

8181
return {model.uuid: model for model in await self._get_response(broker, len(references))}
@@ -89,3 +89,14 @@ async def _get_response(broker: BrokerClient, count: int, **kwargs) -> Iterable[
8989
messages.append(message)
9090

9191
return chain(*(message.content for message in messages))
92+
93+
@staticmethod
94+
def build_topic_name(entity: Union[type, str]) -> str:
95+
"""Build the topic name based on the name of the entity.
96+
97+
:param entity: The name of the entity to be resolved.
98+
:return: The topic name.
99+
"""
100+
if isinstance(entity, type):
101+
entity = entity.__name__
102+
return f"_Get{entity}Snapshots"

packages/core/minos-microservice-aggregate/minos/aggregate/snapshots/services.py

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -63,31 +63,16 @@ def __init__(
6363

6464
@classmethod
6565
def __get_enroute__(cls, config: MinosConfig) -> dict[str, set[EnrouteDecorator]]:
66-
simplified_name = config.service.aggregate.rsplit(".", 1)[-1]
66+
from ..entities import (
67+
RefResolver,
68+
)
69+
70+
name = config.service.aggregate.rsplit(".", 1)[-1]
6771
return {
68-
cls.__get_one__.__name__: {enroute.broker.command(f"_Get{simplified_name}Snapshot")},
69-
cls.__get_many__.__name__: {enroute.broker.command(f"_Get{simplified_name}Snapshots")},
72+
cls.__get_many__.__name__: {enroute.broker.command(RefResolver.build_topic_name(name))},
7073
cls.__synchronize__.__name__: {enroute.periodic.event("* * * * *")},
7174
}
7275

73-
async def __get_one__(self, request: Request) -> Response:
74-
"""Get one ``RootEntity`` instance.
75-
76-
:param request: The ``Request`` instance that contains the instance identifier.
77-
:return: A ``Response`` instance containing the requested instances.
78-
"""
79-
try:
80-
content = await request.content(model_type=ModelType.build("Query", {"uuid": UUID}))
81-
except Exception as exc:
82-
raise ResponseException(f"There was a problem while parsing the given request: {exc!r}")
83-
84-
try:
85-
instance = await self.type_.get(content["uuid"])
86-
except Exception as exc:
87-
raise ResponseException(f"There was a problem while getting the instance: {exc!r}")
88-
89-
return Response(instance)
90-
9176
async def __get_many__(self, request: Request) -> Response:
9277
"""Get many ``RootEntity`` instances.
9378

packages/core/minos-microservice-aggregate/tests/test_aggregate/test_entities/test_refs/test_models.py

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
Generic,
55
Union,
66
)
7+
from unittest.mock import (
8+
patch,
9+
)
710
from uuid import (
811
UUID,
912
uuid4,
@@ -13,18 +16,14 @@
1316
IS_REPOSITORY_SERIALIZATION_CONTEXT_VAR,
1417
Ref,
1518
RefException,
19+
RefResolver,
1620
)
1721
from minos.common import (
1822
DeclarativeModel,
1923
Field,
2024
Model,
2125
ModelType,
2226
)
23-
from minos.networks import (
24-
BrokerMessageV1,
25-
BrokerMessageV1Payload,
26-
BrokerMessageV1Status,
27-
)
2827
from tests.utils import (
2928
MinosTestCase,
3029
)
@@ -69,6 +68,13 @@ def test_uuid_getattr(self):
6968

7069
self.assertEqual(uuid, value.uuid)
7170

71+
def test_uuid_getattr_raises(self):
72+
uuid = uuid4()
73+
value = Ref(uuid)
74+
75+
with self.assertRaises(AttributeError):
76+
value.color
77+
7278
def test_uuid_setattr(self):
7379
uuid_1 = uuid4()
7480
uuid_2 = uuid4()
@@ -84,11 +90,12 @@ def test_uuid_getitem(self):
8490

8591
self.assertEqual(uuid, value["uuid"])
8692

87-
def test_model_getitem_raises(self):
88-
value = Ref(Bar(uuid4(), 1))
93+
def test_uuid_getitem_raises(self):
94+
uuid = uuid4()
95+
value = Ref(uuid)
8996

9097
with self.assertRaises(KeyError):
91-
value["year"]
98+
value["color"]
9299

93100
def test_uuid_setitem(self):
94101
uuid_1 = uuid4()
@@ -149,6 +156,12 @@ def test_model_getitem(self):
149156

150157
self.assertEqual(1, value["age"])
151158

159+
def test_model_getitem_raises(self):
160+
value = Ref(Bar(uuid4(), 1))
161+
162+
with self.assertRaises(KeyError):
163+
value["year"]
164+
152165
def test_model_setitem(self):
153166
value = Ref(Bar(uuid4(), 1))
154167

@@ -285,34 +298,25 @@ def test_uuid_from_avro(self):
285298

286299
async def test_resolve(self):
287300
another = uuid4()
288-
289-
self.broker_subscriber_builder.with_messages([BrokerMessageV1("", BrokerMessageV1Payload(Bar(another, 1)))])
301+
resolved_another = Bar(another, 1)
290302

291303
ref = Foo(another).another # FIXME: This should not be needed to set the type hint properly
292304

293305
self.assertEqual(ref.data, another)
294306

295-
await ref.resolve()
296-
297-
observed = self.broker_publisher.messages
298-
self.assertEqual(1, len(observed))
307+
with patch.object(RefResolver, "resolve", return_value=resolved_another):
308+
await ref.resolve()
299309

300-
self.assertIsInstance(observed[0], BrokerMessageV1)
301-
self.assertEqual("GetBar", observed[0].topic)
302-
self.assertEqual({"uuid": another}, observed[0].content)
303310
self.assertEqual(ref.data, Bar(another, 1))
304311

305312
async def test_resolve_raises(self):
306313
another = uuid4()
307314

308-
self.broker_subscriber_builder.with_messages(
309-
[BrokerMessageV1("", BrokerMessageV1Payload(status=BrokerMessageV1Status.ERROR))]
310-
)
311-
312315
ref = Foo(another).another # FIXME: This should not be needed to set the type hint properly
313316

314317
with self.assertRaises(RefException):
315-
await ref.resolve()
318+
with patch.object(RefResolver, "resolve", side_effect=RefException("test")):
319+
await ref.resolve()
316320

317321
async def test_resolve_already(self):
318322
uuid = uuid4()
@@ -340,6 +344,32 @@ def test_avro_uuid(self):
340344

341345
self.assertEqual(ref, Ref.from_avro_bytes(ref.avro_bytes))
342346

347+
def test_repr_uuid(self):
348+
another_uuid = uuid4()
349+
ref = Foo(another_uuid).another # FIXME: This should not be needed to set the type hint properly
350+
351+
self.assertEqual(f"Ref({another_uuid!r})", repr(ref))
352+
353+
def test_repr_model(self):
354+
another_uuid = uuid4()
355+
another = Bar(another_uuid, 1)
356+
ref = Foo(another).another # FIXME: This should not be needed to set the type hint properly
357+
358+
self.assertEqual(f"Ref({another!r})", repr(ref))
359+
360+
def test_str_uuid(self):
361+
another_uuid = uuid4()
362+
ref = Foo(another_uuid).another # FIXME: This should not be needed to set the type hint properly
363+
364+
self.assertEqual(str(another_uuid), str(ref))
365+
366+
def test_str_model(self):
367+
another_uuid = uuid4()
368+
another = Bar(another_uuid, 1)
369+
ref = Foo(another).another # FIXME: This should not be needed to set the type hint properly
370+
371+
self.assertEqual(str(another_uuid), str(ref))
372+
343373

344374
if __name__ == "__main__":
345375
unittest.main()

packages/core/minos-microservice-aggregate/tests/test_aggregate/test_entities/test_refs/test_resolvers.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ async def test_resolve_raises(self):
6262
with self.assertRaises(RefException):
6363
await self.resolver.resolve(self.value)
6464

65+
def test_build_topic_name_str(self):
66+
expected = "_GetBarSnapshots"
67+
observed = RefResolver.build_topic_name("Bar")
68+
69+
self.assertEqual(observed, expected)
70+
71+
def test_build_topic_name_type(self):
72+
expected = "_GetBarSnapshots"
73+
observed = RefResolver.build_topic_name(Bar)
74+
75+
self.assertEqual(observed, expected)
76+
6577

6678
if __name__ == "__main__":
6779
unittest.main()

packages/core/minos-microservice-aggregate/tests/test_aggregate/test_snapshots/test_services.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
)
1010

1111
from minos.aggregate import (
12+
RefResolver,
1213
RootEntity,
1314
SnapshotService,
1415
)
@@ -43,27 +44,12 @@ def setUp(self) -> None:
4344

4445
def test_get_enroute(self):
4546
expected = {
46-
"__get_one__": {BrokerCommandEnrouteDecorator("_GetOrderSnapshot")},
47-
"__get_many__": {BrokerCommandEnrouteDecorator("_GetOrderSnapshots")},
47+
"__get_many__": {BrokerCommandEnrouteDecorator(RefResolver.build_topic_name("Order"))},
4848
"__synchronize__": {PeriodicEventEnrouteDecorator("* * * * *")},
4949
}
5050
observed = SnapshotService.__get_enroute__(self.config)
5151
self.assertEqual(expected, observed)
5252

53-
async def test_get(self):
54-
uuid = uuid4()
55-
expected = Agg(uuid)
56-
with patch.object(RootEntity, "get", return_value=expected):
57-
response = await self.service.__get_one__(InMemoryRequest({"uuid": uuid}))
58-
self.assertEqual(expected, await response.content())
59-
60-
async def test_get_raises(self):
61-
with self.assertRaises(ResponseException):
62-
await self.service.__get_one__(InMemoryRequest())
63-
with patch.object(RootEntity, "get", side_effect=ValueError):
64-
with self.assertRaises(ResponseException):
65-
await self.service.__get_one__(InMemoryRequest({"uuid": uuid4()}))
66-
6753
async def test_get_many(self):
6854
uuids = [uuid4(), uuid4()]
6955

0 commit comments

Comments
 (0)