Skip to content
This repository was archived by the owner on Jan 28, 2022. It is now read-only.

Commit 1b504ca

Browse files
author
Sergio García Prado
authored
Merge pull request #420 from Clariteia/0.0.19
0.0.19
2 parents 642a769 + 31ee851 commit 1b504ca

File tree

16 files changed

+152
-53
lines changed

16 files changed

+152
-53
lines changed

HISTORY.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,9 @@ History
109109

110110
* Add `PeriodicTask`, `PeriodicTaskScheduler` and `PeriodicTaskSchedulerService`.
111111
* Add `@enroute.periodic.event` decorator
112+
113+
0.0.19 (2021-11-03)
114+
------------------
115+
116+
* Add `"user"` context variable to be accessible during `Request` handling (same as `Request.user`).
117+
* Add support for `Request.user` propagation over `CommandBroker`.

minos/networks/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.0.18"
1+
__version__ = "0.0.19"
22

33
from .brokers import (
44
Broker,
@@ -60,6 +60,7 @@
6060
HandlerSetup,
6161
)
6262
from .messages import (
63+
USER_CONTEXT_VAR,
6364
Request,
6465
Response,
6566
ResponseException,

minos/networks/brokers/commands.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,27 @@ def _from_config(cls, *args, config: MinosConfig, **kwargs) -> CommandBroker:
3838
return cls(*args, **config.broker.queue._asdict(), default_reply_topic=default_reply_topic, **kwargs)
3939

4040
# noinspection PyMethodOverriding
41-
async def send(self, data: Any, topic: str, saga: UUID, reply_topic: Optional[str] = None, **kwargs) -> int:
41+
async def send(
42+
self,
43+
data: Any,
44+
topic: str,
45+
saga: UUID,
46+
reply_topic: Optional[str] = None,
47+
user: Optional[UUID] = None,
48+
**kwargs,
49+
) -> int:
4250
"""Send a ``Command``.
4351
4452
:param data: The data to be send.
4553
:param topic: Topic in which the message will be published.
4654
:param saga: Saga identifier.
4755
:param reply_topic: Topic name in which the reply will be published.
56+
:param user: Optional user identifier. If the value is not `None` then the command is authenticated, otherwise
57+
the command is not authenticated.
4858
:return: This method does not return anything.
4959
"""
5060
if reply_topic is None:
5161
reply_topic = self.default_reply_topic
52-
command = Command(topic, data, saga, reply_topic)
62+
command = Command(topic, data, saga, reply_topic, user)
5363
logger.info(f"Sending '{command!s}'...")
5464
return await self.enqueue(command.topic, command.avro_bytes)

minos/networks/handlers/commands/handlers.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
EnrouteBuilder,
3232
)
3333
from ...messages import (
34+
USER_CONTEXT_VAR,
3435
Response,
3536
ResponseException,
3637
)
@@ -95,8 +96,10 @@ def get_callback(
9596
"""
9697

9798
async def _fn(command: Command) -> Tuple[Any, CommandStatus]:
99+
request = HandlerRequest(command)
100+
token = USER_CONTEXT_VAR.set(request.user)
101+
98102
try:
99-
request = HandlerRequest(command)
100103
response = fn(request)
101104
if isawaitable(response):
102105
response = await response
@@ -109,5 +112,7 @@ async def _fn(command: Command) -> Tuple[Any, CommandStatus]:
109112
except Exception as exc:
110113
logger.exception(f"Raised a system exception: {exc!r}")
111114
return repr(exc), CommandStatus.SYSTEM_ERROR
115+
finally:
116+
USER_CONTEXT_VAR.reset(token)
112117

113118
return _fn

minos/networks/messages.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66
ABC,
77
abstractmethod,
88
)
9+
from contextvars import (
10+
ContextVar,
11+
)
912
from inspect import (
1013
isawaitable,
1114
)
1215
from typing import (
1316
Any,
1417
Callable,
18+
Final,
1519
Optional,
1620
)
1721
from uuid import (
@@ -26,6 +30,9 @@
2630
MinosException,
2731
)
2832

33+
USER_CONTEXT_VAR: Final[ContextVar[Optional[UUID]]] = ContextVar("user", default=None)
34+
USER_CONTEXT_VAR.set(None) # needed to "register" the context variable.
35+
2936

3037
class Request(ABC):
3138
"""Request interface."""

minos/networks/rest/handlers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
EnrouteBuilder,
3030
)
3131
from ..messages import (
32+
USER_CONTEXT_VAR,
3233
Response,
3334
ResponseException,
3435
)
@@ -127,6 +128,7 @@ async def _fn(request: web.Request) -> web.Response:
127128
logger.info(f"Dispatching '{request!s}' from '{request.remote!s}'...")
128129

129130
request = RestRequest(request)
131+
token = USER_CONTEXT_VAR.set(request.user)
130132

131133
try:
132134
response = fn(request)
@@ -143,6 +145,8 @@ async def _fn(request: web.Request) -> web.Response:
143145
except Exception as exc:
144146
logger.exception(f"Raised a system exception: {exc!r}")
145147
raise web.HTTPInternalServerError()
148+
finally:
149+
USER_CONTEXT_VAR.reset(token)
146150

147151
return _fn
148152

minos/networks/rest/messages.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,18 @@ def user(self) -> Optional[UUID]:
5757
"""
5858
Returns the UUID of the user making the Request.
5959
"""
60-
return UUID(self.raw_request.headers["User"])
60+
if "User" not in self.headers:
61+
return None
62+
return UUID(self.headers["User"])
63+
64+
@property
65+
def headers(self) -> dict[str, str]:
66+
"""Get the headers of the request.
67+
68+
:return: A dictionary in which keys are ``str`` instances and values are ``str`` instances.
69+
"""
70+
# noinspection PyTypeChecker
71+
return self.raw_request.headers
6172

6273
async def content(self, model_type: Union[ModelType, Type[Model], str] = "Content", **kwargs) -> Any:
6374
"""Get the request content.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "minos_microservice_networks"
3-
version = "0.0.18"
3+
version = "0.0.19"
44
description = "Python Package with the common network classes and utilities used in Minos Microservice."
55
readme = "README.md"
66
repository = "https://github.com/clariteia/minos_microservice_network"

tests/test_networks/test_brokers/test_commands.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,22 @@ async def test_send_with_default_reply_topic(self):
6565
self.assertEqual("fake", args[0])
6666
self.assertEqual(Command("fake", FakeModel("foo"), saga, "OrderReply"), Command.from_avro_bytes(args[1]))
6767

68+
async def test_send_with_user(self):
69+
mock = AsyncMock(return_value=56)
70+
saga = uuid4()
71+
user = uuid4()
72+
73+
async with CommandBroker.from_config(config=self.config) as broker:
74+
broker.enqueue = mock
75+
identifier = await broker.send(FakeModel("foo"), "fake", saga, "ekaf", user)
76+
77+
self.assertEqual(56, identifier)
78+
self.assertEqual(1, mock.call_count)
79+
80+
args = mock.call_args.args
81+
self.assertEqual("fake", args[0])
82+
self.assertEqual(Command("fake", FakeModel("foo"), saga, "ekaf", user), Command.from_avro_bytes(args[1]))
83+
6884

6985
if __name__ == "__main__":
7086
unittest.main()

tests/test_networks/test_handlers/test_commands/test_handlers.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
PostgresAsyncTestCase,
1717
)
1818
from minos.networks import (
19+
USER_CONTEXT_VAR,
1920
CommandHandler,
2021
HandlerEntry,
2122
HandlerRequest,
@@ -56,7 +57,8 @@ def setUp(self) -> None:
5657
super().setUp()
5758
self.broker = FakeBroker()
5859
self.handler = CommandHandler.from_config(config=self.config, broker=self.broker)
59-
self.command = Command("AddOrder", FakeModel("foo"), uuid4(), "UpdateTicket")
60+
self.user = uuid4()
61+
self.command = Command("AddOrder", FakeModel("foo"), self.user, "UpdateTicket")
6062

6163
def test_from_config(self):
6264
broker = FakeBroker()
@@ -117,6 +119,18 @@ async def test_get_callback_raises_exception(self):
117119
expected = (repr(ValueError()), CommandStatus.SYSTEM_ERROR)
118120
self.assertEqual(expected, await fn(self.command))
119121

122+
async def test_get_callback_with_user(self):
123+
async def _fn(request) -> None:
124+
self.assertEqual(self.user, request.user)
125+
self.assertEqual(self.user, USER_CONTEXT_VAR.get())
126+
127+
mock = AsyncMock(side_effect=_fn)
128+
129+
handler = self.handler.get_callback(mock)
130+
await handler(self.command)
131+
132+
self.assertEqual(1, mock.call_count)
133+
120134

121135
if __name__ == "__main__":
122136
unittest.main()

0 commit comments

Comments
 (0)