Skip to content

Commit d063903

Browse files
author
Sergio García Prado
authored
Merge pull request #173 from minos-framework/0.5.2
0.5.2
2 parents cd248c0 + a5f61e8 commit d063903

File tree

43 files changed

+502
-198
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+502
-198
lines changed

README.md

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Minos is a framework which helps you create [reactive](https://www.reactivemanif
1919
### Roadmap
2020

2121
#### 0.6.x
22+
2223
* [#78](https://github.com/minos-framework/minos-python/issues/78) Implement a circuit breaker for `minos-broker-kafka`.
2324
* [#87](https://github.com/minos-framework/minos-python/issues/87) Implement idempotency for `BrokerSubscriber` message processing.
2425
* [#100](https://github.com/minos-framework/minos-python/issues/100) Create the `minos-serializers-avro` plugin.
@@ -67,6 +68,10 @@ Here is a summary containing the most useful commands:
6768

6869
For more information, visit the [`minos-cli`](https://github.com/minos-framework/minos-cli) repository.
6970

71+
## Documentation
72+
73+
The best place to start learning how to use the Minos Framework is at [Minos Learn](http://www.minos.run/learn/). The official API Reference is publicly available at the [GitHub Pages](https://minos-framework.github.io/minos-python).
74+
7075
## QuickStart
7176

7277
This section includes a quickstart guide to create your first `minos` microservice, so that anyone can get the gist of the framework.
@@ -80,9 +85,54 @@ The required environment to run this quickstart is the following:
8085
* A `postgres` instance available at `localhost:5432` with the `foo_db` and `foobar_db` databases accessible with the `user:pass` credentials.
8186
* Two TCP sockets available to use at `localhost:4545` and `localhost:4546`.
8287

88+
89+
<details>
90+
<summary>Click to show a <code>docker-compose.yml</code> that provides the <code>kafka</code> and <code>postgres</code> instances ready to be used!</summary>
91+
92+
```yaml
93+
# docker-compose.yml
94+
version: "3.9"
95+
services:
96+
zookeeper:
97+
restart: always
98+
image: wurstmeister/zookeeper:latest
99+
kafka:
100+
restart: always
101+
image: wurstmeister/kafka:latest
102+
ports:
103+
- "9092:9092"
104+
depends_on:
105+
- zookeeper
106+
environment:
107+
KAFKA_ADVERTISED_HOST_NAME: kafka
108+
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
109+
postgres:
110+
restart: always
111+
image: postgres:latest
112+
ports:
113+
- "5432:5432"
114+
environment:
115+
- POSTGRES_USER=user
116+
- POSTGRES_PASSWORD=pass
117+
```
118+
119+
Then, start the environment:
120+
121+
```shell
122+
docker-compose up
123+
```
124+
125+
To create the databases, just run the following command:
126+
127+
```shell
128+
docker-compose exec postgres psql -U user -tc 'CREATE database foo_db'
129+
docker-compose exec postgres psql -U user -tc 'CREATE database foobar_db'
130+
```
131+
</details>
132+
83133
Note that these parameters can be customized on the configuration files.
84134

85-
### Install the dependencies
135+
### Install the dependencies
86136

87137
If you want to directly use `minos` without the command-line utility, the following command will install the needed packages:
88138

@@ -96,7 +146,6 @@ pip install \
96146
minos-broker-kafka
97147
```
98148

99-
100149
### Configure a Microservice
101150

102151
To keep things simpler, this quickstart will create a microservice assuming all the source code is stored on a single `foo/main.py` file. In addition to the source file, a `foo/config.yml` will contain all the configuration stuff.
@@ -1108,10 +1157,6 @@ The plugin packages provide connectors to external technologies like brokers, di
11081157
* [minos-broker-kafka](https://minos-framework.github.io/minos-python/packages/plugins/minos-broker-kafka): The `kafka` plugin package.
11091158
* [minos-discovery-minos](https://minos-framework.github.io/minos-python/packages/plugins/minos-discovery-minos): The `minos-discovery` plugin package.
11101159
1111-
## Documentation
1112-
1113-
The official API Reference is publicly available at the [GitHub Pages](https://minos-framework.github.io/minos-python).
1114-
11151160
## Source Code
11161161
11171162
The source code of this project is hosted at the [GitHub Repository](https://github.com/minos-framework/minos-python).

packages/core/minos-microservice-aggregate/HISTORY.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,12 @@
6868

6969
## 0.5.1 (2022-02-03)
7070

71-
* Fix bug related with dependency specification.
71+
* Fix bug related with dependency specification.
72+
73+
## 0.5.2 (2022-02-08)
74+
75+
* Add `Condition.LIKE` operator to be used with the `find` method from `SnapshotRepository`.
76+
* Add `get_all` method to `RootEntity` and `SnapshotRepository` to get all the stored instance on the repository.
77+
* Rename `SnapshotService` command topics to avoid collisions with application-level topics.
78+
* Rename `TransactionService` command topics to avoid collisions with application-level topics.
79+
* Minor changes.

packages/core/minos-microservice-aggregate/minos/aggregate/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
__author__ = "Minos Framework Devs"
22
__email__ = "[email protected]"
3-
__version__ = "0.5.1"
3+
__version__ = "0.5.2"
44

55
from .actions import (
66
Action,

packages/core/minos-microservice-aggregate/minos/aggregate/entities/collections.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@
1818
UUID,
1919
)
2020

21-
from minos.aggregate.collections import (
22-
IncrementalSet,
23-
IncrementalSetDiff,
24-
)
2521
from minos.common import (
2622
DataDecoder,
2723
DataEncoder,
@@ -31,6 +27,11 @@
3127
SchemaEncoder,
3228
)
3329

30+
from ..collections import (
31+
IncrementalSet,
32+
IncrementalSetDiff,
33+
)
34+
3435
T = TypeVar("T", bound=Model)
3536

3637

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

Lines changed: 75 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,29 @@
2222
inject,
2323
)
2424

25-
from minos.aggregate.events import (
25+
from minos.common import (
26+
NULL_DATETIME,
27+
NULL_UUID,
28+
DeclarativeModel,
29+
NotProvidedException,
30+
)
31+
32+
from ..events import (
2633
Event,
2734
EventEntry,
2835
EventRepository,
2936
IncrementalFieldDiff,
3037
)
31-
from minos.aggregate.exceptions import (
38+
from ..exceptions import (
3239
EventRepositoryException,
3340
)
34-
from minos.aggregate.queries import (
41+
from ..queries import (
3542
_Condition,
3643
_Ordering,
3744
)
38-
from minos.aggregate.snapshots import (
45+
from ..snapshots import (
3946
SnapshotRepository,
4047
)
41-
from minos.common import (
42-
NULL_DATETIME,
43-
NULL_UUID,
44-
DeclarativeModel,
45-
NotProvidedException,
46-
)
4748

4849
logger = logging.getLogger(__name__)
4950

@@ -83,46 +84,74 @@ def __init__(
8384
version: int = 0,
8485
created_at: datetime = NULL_DATETIME,
8586
updated_at: datetime = NULL_DATETIME,
86-
_repository: EventRepository = Provide["event_repository"],
87-
_snapshot: SnapshotRepository = Provide["snapshot_repository"],
87+
_event_repository: EventRepository = Provide["event_repository"],
88+
_snapshot_repository: SnapshotRepository = Provide["snapshot_repository"],
8889
**kwargs,
8990
):
9091

9192
super().__init__(version, created_at, updated_at, *args, uuid=uuid, **kwargs)
9293

93-
if _repository is None or isinstance(_repository, Provide):
94-
raise NotProvidedException("An event repository instance is required.")
95-
if _snapshot is None or isinstance(_snapshot, Provide):
96-
raise NotProvidedException("A snapshot instance is required.")
94+
if _event_repository is None or isinstance(_event_repository, Provide):
95+
raise NotProvidedException(f"A {EventRepository!r} instance is required.")
96+
if _snapshot_repository is None or isinstance(_snapshot_repository, Provide):
97+
raise NotProvidedException(f"A {SnapshotRepository!r} instance is required.")
9798

98-
self._repository = _repository
99-
self._snapshot = _snapshot
99+
self._event_repository = _event_repository
100+
self._snapshot_repository = _snapshot_repository
100101

101102
@classmethod
102103
@inject
103104
async def get(
104-
cls: Type[T], uuid: UUID, _snapshot: SnapshotRepository = Provide["snapshot_repository"], **kwargs
105+
cls: Type[T], uuid: UUID, *, _snapshot_repository: SnapshotRepository = Provide["snapshot_repository"], **kwargs
105106
) -> T:
106107
"""Get one instance from the database based on its identifier.
107108
108109
:param uuid: The identifier of the instance.
109-
:param _snapshot: Snapshot to be set to the root entity.
110+
:param _snapshot_repository: Snapshot to be set to the root entity.
110111
:return: A ``RootEntity`` instance.
111112
"""
112-
if _snapshot is None or isinstance(_snapshot, Provide):
113-
raise NotProvidedException("A snapshot instance is required.")
113+
if _snapshot_repository is None or isinstance(_snapshot_repository, Provide):
114+
raise NotProvidedException(f"A {SnapshotRepository!r} instance is required.")
114115

115116
# noinspection PyTypeChecker
116-
return await _snapshot.get(cls.classname, uuid, _snapshot=_snapshot, **kwargs)
117+
return await _snapshot_repository.get(cls.classname, uuid, _snapshot_repository=_snapshot_repository, **kwargs)
117118

118119
@classmethod
119120
@inject
120-
async def find(
121+
def get_all(
122+
cls: Type[T],
123+
ordering: Optional[_Ordering] = None,
124+
limit: Optional[int] = None,
125+
*,
126+
_snapshot_repository: SnapshotRepository = Provide["snapshot_repository"],
127+
**kwargs,
128+
) -> AsyncIterator[T]:
129+
"""Get all instance from the database.
130+
131+
:param ordering: Optional argument to return the instance with specific ordering strategy. The default behaviour
132+
is to retrieve them without any order pattern.
133+
:param limit: Optional argument to return only a subset of instances. The default behaviour is to return all the
134+
instances that meet the given condition.
135+
:param _snapshot_repository: Snapshot to be set to the root entity.
136+
:return: A ``RootEntity`` instance.
137+
"""
138+
if _snapshot_repository is None or isinstance(_snapshot_repository, Provide):
139+
raise NotProvidedException(f"A {SnapshotRepository!r} instance is required.")
140+
141+
# noinspection PyTypeChecker
142+
return _snapshot_repository.get_all(
143+
cls.classname, ordering, limit, _snapshot_repository=_snapshot_repository, **kwargs
144+
)
145+
146+
@classmethod
147+
@inject
148+
def find(
121149
cls: Type[T],
122150
condition: _Condition,
123151
ordering: Optional[_Ordering] = None,
124152
limit: Optional[int] = None,
125-
_snapshot: SnapshotRepository = Provide["snapshot_repository"],
153+
*,
154+
_snapshot_repository: SnapshotRepository = Provide["snapshot_repository"],
126155
**kwargs,
127156
) -> AsyncIterator[T]:
128157
"""Find a collection of instances based on a given ``Condition``.
@@ -132,16 +161,15 @@ async def find(
132161
is to retrieve them without any order pattern.
133162
:param limit: Optional argument to return only a subset of instances. The default behaviour is to return all the
134163
instances that meet the given condition.
135-
:param _snapshot: Snapshot to be set to the instances.
164+
:param _snapshot_repository: Snapshot to be set to the instances.
136165
:return: An asynchronous iterator of ``RootEntity`` instances.
137166
"""
138-
if _snapshot is None or isinstance(_snapshot, Provide):
139-
raise NotProvidedException("A snapshot instance is required.")
167+
if _snapshot_repository is None or isinstance(_snapshot_repository, Provide):
168+
raise NotProvidedException(f"A {SnapshotRepository!r} instance is required.")
140169
# noinspection PyTypeChecker
141-
iterable = _snapshot.find(cls.classname, condition, ordering, limit, _snapshot=_snapshot, **kwargs)
142-
# noinspection PyTypeChecker
143-
async for instance in iterable:
144-
yield instance
170+
return _snapshot_repository.find(
171+
cls.classname, condition, ordering, limit, _snapshot_repository=_snapshot_repository, **kwargs
172+
)
145173

146174
@classmethod
147175
async def create(cls: Type[T], *args, **kwargs) -> T:
@@ -171,7 +199,7 @@ async def create(cls: Type[T], *args, **kwargs) -> T:
171199
instance: T = cls(*args, **kwargs)
172200

173201
event = Event.from_root_entity(instance)
174-
entry = await instance._repository.submit(event)
202+
entry = await instance._event_repository.submit(event)
175203

176204
instance._update_from_repository_entry(entry)
177205

@@ -201,12 +229,14 @@ async def update(self: T, **kwargs) -> T:
201229
for key, value in kwargs.items():
202230
setattr(self, key, value)
203231

204-
previous = await self.get(self.uuid, _repository=self._repository, _snapshot=self._snapshot)
232+
previous = await self.get(
233+
self.uuid, _event_repository=self._event_repository, _snapshot_repository=self._snapshot_repository
234+
)
205235
event = self.diff(previous)
206236
if not len(event.fields_diff):
207237
return self
208238

209-
entry = await self._repository.submit(event)
239+
entry = await self._event_repository.submit(event)
210240

211241
self._update_from_repository_entry(entry)
212242

@@ -234,17 +264,23 @@ async def save(self) -> None:
234264
if k not in {"uuid", "version", "created_at", "updated_at"}
235265
}
236266
if is_creation:
237-
new = await self.create(**values, _repository=self._repository, _snapshot=self._snapshot)
267+
new = await self.create(
268+
**values, _event_repository=self._event_repository, _snapshot_repository=self._snapshot_repository
269+
)
238270
self._fields |= new.fields
239271
else:
240-
await self.update(**values, _repository=self._repository, _snapshot=self._snapshot)
272+
await self.update(
273+
**values, _event_repository=self._event_repository, _snapshot_repository=self._snapshot_repository
274+
)
241275

242276
async def refresh(self) -> None:
243277
"""Refresh the state of the given instance.
244278
245279
:return: This method does not return anything.
246280
"""
247-
new = await type(self).get(self.uuid, _repository=self._repository, _snapshot=self._snapshot)
281+
new = await self.get(
282+
self.uuid, _event_repository=self._event_repository, _snapshot_repository=self._snapshot_repository
283+
)
248284
self._fields |= new.fields
249285

250286
async def delete(self) -> None:
@@ -253,7 +289,7 @@ async def delete(self) -> None:
253289
:return: This method does not return anything.
254290
"""
255291
event = Event.from_deleted_root_entity(self)
256-
entry = await self._repository.submit(event)
292+
entry = await self._event_repository.submit(event)
257293

258294
self._update_from_repository_entry(entry)
259295

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@
2020
inject,
2121
)
2222

23-
from minos.aggregate.contextvars import (
24-
IS_REPOSITORY_SERIALIZATION_CONTEXT_VAR,
25-
)
2623
from minos.common import (
2724
DataDecoder,
2825
DataEncoder,
@@ -39,6 +36,10 @@
3936
BrokerMessageV1Payload,
4037
)
4138

39+
from ...contextvars import (
40+
IS_REPOSITORY_SERIALIZATION_CONTEXT_VAR,
41+
)
42+
4243
MT = TypeVar("MT", bound=Model)
4344

4445

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@ async def resolve(self, data: Any, **kwargs) -> Any:
6565
async def _query(self, references: dict[str, set[UUID]]) -> dict[UUID, Model]:
6666
async with self.broker_pool.acquire() as broker:
6767
futures = (
68-
broker.send(BrokerMessageV1(f"Get{name}s", BrokerMessageV1Payload({"uuids": uuids})))
69-
for name, uuids in references.items()
68+
broker.send(
69+
BrokerMessageV1(f"_Get{simplified_name}Snapshots", BrokerMessageV1Payload({"uuids": uuids}))
70+
)
71+
for simplified_name, uuids in references.items()
7072
)
7173
await gather(*futures)
7274

0 commit comments

Comments
 (0)