eventsourcing-kurrentdb 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eventsourcing_kurrentdb-1.2.0/LICENSE +29 -0
- eventsourcing_kurrentdb-1.2.0/PKG-INFO +437 -0
- eventsourcing_kurrentdb-1.2.0/README.md +411 -0
- eventsourcing_kurrentdb-1.2.0/eventsourcing_kurrentdb/__init__.py +3 -0
- eventsourcing_kurrentdb-1.2.0/eventsourcing_kurrentdb/factory.py +68 -0
- eventsourcing_kurrentdb-1.2.0/eventsourcing_kurrentdb/py.typed +0 -0
- eventsourcing_kurrentdb-1.2.0/eventsourcing_kurrentdb/recorders.py +331 -0
- eventsourcing_kurrentdb-1.2.0/pyproject.toml +210 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022, John Bywater
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: eventsourcing-kurrentdb
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Python package for eventsourcing with KurrentDB
|
|
5
|
+
License: BSD-3-Clause
|
|
6
|
+
Author: John Bywater
|
|
7
|
+
Author-email: john.bywater@appropriatesoftware.net
|
|
8
|
+
Requires-Python: >=3.9.2
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Dist: eventsourcing (>=9.4.2,<10.0)
|
|
21
|
+
Requires-Dist: kurrentdbclient (>=1.0.2,<2.0)
|
|
22
|
+
Project-URL: Homepage, https://github.com/pyeventsourcing/eventsourcing-kurrentdb
|
|
23
|
+
Project-URL: Repository, https://github.com/pyeventsourcing/eventsourcing-kurrentdb
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
Please note: following the rebranding of EventStoreDB to KurrentDB, this package is
|
|
27
|
+
the rebranding of [`eventsourcing-eventstoredb`](https://pypi.org/project/eventsourcing-eventstoredb). Please
|
|
28
|
+
migrate your code to use the [`eventsourcing-kurrentdb`](https://pypi.org/project/eventsourcing-kurrentdb)
|
|
29
|
+
package when you are ready.
|
|
30
|
+
|
|
31
|
+
# Event Sourcing in Python with KurrentDB
|
|
32
|
+
|
|
33
|
+
This is an extension package for the Python [eventsourcing](https://github.com/pyeventsourcing/eventsourcing) library
|
|
34
|
+
that provides a persistence module for [KurrentDB](https://www.kurrent.io).
|
|
35
|
+
It uses the [kurrentdbclient](https://github.com/pyeventsourcing/kurrentdbclient)
|
|
36
|
+
package to communicate with KurrentDB via the gRPC interface.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
Use pip to install the [stable distribution](https://pypi.org/project/eventsourcing-kurrentdb/)
|
|
41
|
+
from the Python Package Index.
|
|
42
|
+
|
|
43
|
+
$ pip install eventsourcing-kurrentdb
|
|
44
|
+
|
|
45
|
+
Please note, it is recommended to install Python packages into a Python virtual environment.
|
|
46
|
+
|
|
47
|
+
## Getting started
|
|
48
|
+
|
|
49
|
+
Define aggregates and applications in the usual way. Please note, "streams"
|
|
50
|
+
in KurrentDB are constrained to start from position `0`, and this
|
|
51
|
+
package expects the `originator_version` of the first event in an aggregate sequence
|
|
52
|
+
to be `0`, so you must set `INITIAL_VERSION` on your aggregate classes to `0`.
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from __future__ import annotations
|
|
56
|
+
|
|
57
|
+
from uuid import uuid5, NAMESPACE_URL
|
|
58
|
+
from typing import List, TypedDict, Tuple
|
|
59
|
+
|
|
60
|
+
from eventsourcing.application import Application
|
|
61
|
+
from eventsourcing.domain import Aggregate, event
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TrainingSchool(Application):
|
|
65
|
+
def register(self, name: str) -> int:
|
|
66
|
+
dog = Dog(name)
|
|
67
|
+
recordings = self.save(dog)
|
|
68
|
+
return recordings[-1].notification.id
|
|
69
|
+
|
|
70
|
+
def add_trick(self, name: str, trick: str) -> int:
|
|
71
|
+
dog = self._get_dog(name)
|
|
72
|
+
dog.add_trick(trick)
|
|
73
|
+
recordings = self.save(dog)
|
|
74
|
+
return recordings[-1].notification.id
|
|
75
|
+
|
|
76
|
+
def get_dog_details(self, name: str) -> DogDetails:
|
|
77
|
+
dog = self._get_dog(name)
|
|
78
|
+
return {'name': dog.name, 'tricks': tuple(dog.tricks)}
|
|
79
|
+
|
|
80
|
+
def _get_dog(self, name: str) -> Dog:
|
|
81
|
+
return self.repository.get(Dog.create_id(name))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Dog(Aggregate):
|
|
86
|
+
INITIAL_VERSION = 0 # for KurrentDB
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def create_id(name: str):
|
|
90
|
+
return uuid5(NAMESPACE_URL, f"/dogs/{name}")
|
|
91
|
+
|
|
92
|
+
@event('Registered')
|
|
93
|
+
def __init__(self, name):
|
|
94
|
+
self.name = name
|
|
95
|
+
self.tricks: List[str] = []
|
|
96
|
+
|
|
97
|
+
@event('TrickAdded')
|
|
98
|
+
def add_trick(self, trick):
|
|
99
|
+
self.tricks.append(trick)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class DogDetails(TypedDict):
|
|
103
|
+
name: str
|
|
104
|
+
tricks: Tuple[str, ...]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Configure the `TrainingSchool` application to use KurrentDB by setting
|
|
108
|
+
the environment variable `PERSISTENCE_MODULE` to `'eventsourcing_kurrentdb'`. You
|
|
109
|
+
can do this in actual environment variables, or by passing in an `env` argument when
|
|
110
|
+
constructing the application object, or by setting `env` on the application class.
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
import os
|
|
114
|
+
|
|
115
|
+
os.environ['TRAININGSCHOOL_PERSISTENCE_MODULE'] = 'eventsourcing_kurrentdb'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Also set environment variable `KURRENTDB_URI` to an KurrentDB connection
|
|
119
|
+
string URI. This value will be used as the `uri` argument when the `KurrentDBClient`
|
|
120
|
+
class is constructed by this package.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
os.environ['KURRENTDB_URI'] = 'esdb://localhost:2113?Tls=false'
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
If you are connecting to a "secure" KurrentDB server, unless
|
|
127
|
+
the root certificate of the certificate authority used to generate the
|
|
128
|
+
server's certificate is installed locally, then also set environment
|
|
129
|
+
variable `KURRENTDB_ROOT_CERTIFICATES` to an SSL/TLS certificate
|
|
130
|
+
suitable for making a secure gRPC connection to the KurrentDB server(s).
|
|
131
|
+
This value will be used as the `root_certificates` argument when the
|
|
132
|
+
`KurrentDBClient` class is constructed by this package.
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
os.environ['KURRENTDB_ROOT_CERTIFICATES'] = '<PEM encoded SSL/TLS root certificates>'
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Please refer to the [kurrentdbclient](https://github.com/pyeventsourcing/kurrentdbclient)
|
|
140
|
+
documentation for details about starting a "secure" or "insecure" KurrentDB
|
|
141
|
+
server, and the "kdb" and "kdb+discover" KurrentDB connection string
|
|
142
|
+
URI schemes, and how to obtain a suitable SSL/TLS certificate for use
|
|
143
|
+
in the client when connecting to a "secure" KurrentDB server.
|
|
144
|
+
|
|
145
|
+
Construct the application.
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
training_school = TrainingSchool()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Call application methods from tests and user interfaces.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
training_school.register('Fido')
|
|
155
|
+
training_school.add_trick('Fido', 'roll over')
|
|
156
|
+
training_school.add_trick('Fido', 'play dead')
|
|
157
|
+
dog_details = training_school.get_dog_details('Fido')
|
|
158
|
+
assert dog_details['name'] == 'Fido'
|
|
159
|
+
assert dog_details['tricks'] == ('roll over', 'play dead')
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
To see the events have been saved, we can reconstruct the application
|
|
163
|
+
and get Fido's details again.
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
training_school = TrainingSchool()
|
|
167
|
+
|
|
168
|
+
dog_details = training_school.get_dog_details('Fido')
|
|
169
|
+
|
|
170
|
+
assert dog_details['name'] == 'Fido'
|
|
171
|
+
assert dog_details['tricks'] == ('roll over', 'play dead')
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Eventually-consistent materialised views
|
|
175
|
+
|
|
176
|
+
To project the state of an event-sourced application "write model" into a
|
|
177
|
+
materialised view "read model", first define an interface for the materialised view
|
|
178
|
+
using the `TrackingRecorder` class from the `eventsourcing` library.
|
|
179
|
+
|
|
180
|
+
The example below defines methods to count dogs and tricks for the `TrainingSchool`
|
|
181
|
+
application
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from abc import abstractmethod
|
|
185
|
+
from eventsourcing.persistence import Tracking, TrackingRecorder
|
|
186
|
+
|
|
187
|
+
class MaterialisedViewInterface(TrackingRecorder):
|
|
188
|
+
@abstractmethod
|
|
189
|
+
def incr_dog_counter(self, tracking: Tracking) -> None:
|
|
190
|
+
pass
|
|
191
|
+
|
|
192
|
+
@abstractmethod
|
|
193
|
+
def incr_trick_counter(self, tracking: Tracking) -> None:
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
@abstractmethod
|
|
197
|
+
def get_dog_counter(self) -> int:
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
@abstractmethod
|
|
201
|
+
def get_trick_counter(self) -> int:
|
|
202
|
+
pass
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The `MaterialisedViewInterface` can be implemented as a concrete view class using a durable database such as PostgreSQL.
|
|
206
|
+
|
|
207
|
+
The example below counts dogs and tricks in memory, using "plain old Python objects".
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from eventsourcing.popo import POPOTrackingRecorder
|
|
211
|
+
|
|
212
|
+
class InMemoryMaterialiseView(POPOTrackingRecorder, MaterialisedViewInterface):
|
|
213
|
+
def __init__(self):
|
|
214
|
+
super().__init__()
|
|
215
|
+
self._dog_counter = 0
|
|
216
|
+
self._trick_counter = 0
|
|
217
|
+
|
|
218
|
+
def incr_dog_counter(self, tracking: Tracking) -> None:
|
|
219
|
+
with self._database_lock:
|
|
220
|
+
self._assert_tracking_uniqueness(tracking)
|
|
221
|
+
self._insert_tracking(tracking)
|
|
222
|
+
self._dog_counter += 1
|
|
223
|
+
|
|
224
|
+
def incr_trick_counter(self, tracking: Tracking) -> None:
|
|
225
|
+
with self._database_lock:
|
|
226
|
+
self._assert_tracking_uniqueness(tracking)
|
|
227
|
+
self._insert_tracking(tracking)
|
|
228
|
+
self._trick_counter += 1
|
|
229
|
+
|
|
230
|
+
def get_dog_counter(self) -> int:
|
|
231
|
+
return self._dog_counter
|
|
232
|
+
|
|
233
|
+
def get_trick_counter(self) -> int:
|
|
234
|
+
return self._trick_counter
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Define how events will be processed using the `Projection` class from the `eventsourcing` library.
|
|
238
|
+
|
|
239
|
+
The example below processes `Dog` events. The `Dog.Registered` events are processed
|
|
240
|
+
by calling `incr_dog_counter()` on the materialised view. The `Dog.TrickAdded` events
|
|
241
|
+
are processed by calling `incr_trick_counter()`.
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
from eventsourcing.domain import DomainEventProtocol
|
|
245
|
+
from eventsourcing.dispatch import singledispatchmethod
|
|
246
|
+
from eventsourcing.projection import Projection
|
|
247
|
+
from eventsourcing.utils import get_topic
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class CountProjection(Projection[MaterialisedViewInterface]):
|
|
251
|
+
topics = (
|
|
252
|
+
get_topic(Dog.Registered),
|
|
253
|
+
get_topic(Dog.TrickAdded),
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
@singledispatchmethod
|
|
257
|
+
def process_event(self, event: DomainEventProtocol, tracking: Tracking) -> None:
|
|
258
|
+
pass
|
|
259
|
+
|
|
260
|
+
@process_event.register
|
|
261
|
+
def dog_registered(self, event: Dog.Registered, tracking: Tracking) -> None:
|
|
262
|
+
self.view.incr_dog_counter(tracking)
|
|
263
|
+
|
|
264
|
+
@process_event.register
|
|
265
|
+
def trick_added(self, event: Dog.TrickAdded, tracking: Tracking) -> None:
|
|
266
|
+
self.view.incr_trick_counter(tracking)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Run the projection with the `ProjectionRunner` class from the `eventsourcing` library.
|
|
270
|
+
|
|
271
|
+
The example below shows that when the projection is run, the materialised view is updated
|
|
272
|
+
by processing the event of the upstream event-sourced `TrainingSchool` application. It
|
|
273
|
+
also shows that when tricks are subsequently added to the application's aggregates,
|
|
274
|
+
events continue to be processed, such that the trick counter is incremented in the
|
|
275
|
+
downstream materialised view "read model".
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
import os
|
|
279
|
+
from eventsourcing.projection import ProjectionRunner
|
|
280
|
+
|
|
281
|
+
with ProjectionRunner(
|
|
282
|
+
application_class=TrainingSchool,
|
|
283
|
+
projection_class=CountProjection,
|
|
284
|
+
view_class=InMemoryMaterialiseView,
|
|
285
|
+
) as runner:
|
|
286
|
+
|
|
287
|
+
# Get "read model" instance from runner, because
|
|
288
|
+
# state of materialised view is stored in memory.
|
|
289
|
+
materialised_view = runner.projection.view
|
|
290
|
+
|
|
291
|
+
# Wait for the existing events to be processed.
|
|
292
|
+
materialised_view.wait(
|
|
293
|
+
application_name=training_school.name,
|
|
294
|
+
notification_id=training_school.recorder.max_notification_id(),
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Query the "read model".
|
|
298
|
+
dog_count = materialised_view.get_dog_counter()
|
|
299
|
+
trick_count = materialised_view.get_trick_counter()
|
|
300
|
+
|
|
301
|
+
# Record another event in "write model".
|
|
302
|
+
notification_id = training_school.add_trick('Fido', 'sit and stay')
|
|
303
|
+
|
|
304
|
+
# Wait for the new event to be processed.
|
|
305
|
+
materialised_view.wait(
|
|
306
|
+
application_name=training_school.name,
|
|
307
|
+
notification_id=notification_id,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# Expect one trick more, same number of dogs.
|
|
311
|
+
assert dog_count == materialised_view.get_dog_counter()
|
|
312
|
+
assert trick_count + 1 == materialised_view.get_trick_counter()
|
|
313
|
+
|
|
314
|
+
# Write another event.
|
|
315
|
+
notification_id = training_school.add_trick('Fido', 'jump hoop')
|
|
316
|
+
|
|
317
|
+
# Wait for the new event to be processed.
|
|
318
|
+
materialised_view.wait(
|
|
319
|
+
training_school.name,
|
|
320
|
+
notification_id,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# Expect two tricks more, same number of dogs.
|
|
324
|
+
assert dog_count == materialised_view.get_dog_counter()
|
|
325
|
+
assert trick_count + 2 == materialised_view.get_trick_counter()
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
See the Python `eventsourcing` package documentation for more information about
|
|
329
|
+
projecting the state of an event-sourced application into materialised views
|
|
330
|
+
that use a durable database such as SQLite and PostgreSQL.
|
|
331
|
+
|
|
332
|
+
## More information
|
|
333
|
+
|
|
334
|
+
For more information, please refer to the Python
|
|
335
|
+
[eventsourcing](https://github.com/pyeventsourcing/eventsourcing) library, the
|
|
336
|
+
Python [kurrentdbclient](https://github.com/pyeventsourcing/kurrentdbclient) package,
|
|
337
|
+
and the [KurrentDB](https://www.kurrent.io) website.
|
|
338
|
+
|
|
339
|
+
## Contributors
|
|
340
|
+
|
|
341
|
+
### Install Poetry
|
|
342
|
+
|
|
343
|
+
The first thing is to check you have Poetry installed.
|
|
344
|
+
|
|
345
|
+
$ poetry --version
|
|
346
|
+
|
|
347
|
+
If you don't, then please [install Poetry](https://python-poetry.org/docs/#installing-with-the-official-installer).
|
|
348
|
+
|
|
349
|
+
It will help to make sure Poetry's bin directory is in your `PATH` environment variable.
|
|
350
|
+
|
|
351
|
+
But in any case, make sure you know the path to the `poetry` executable. The Poetry
|
|
352
|
+
installer tells you where it has been installed, and how to configure your shell.
|
|
353
|
+
|
|
354
|
+
Please refer to the [Poetry docs](https://python-poetry.org/docs/) for guidance on
|
|
355
|
+
using Poetry.
|
|
356
|
+
|
|
357
|
+
### Setup for PyCharm users
|
|
358
|
+
|
|
359
|
+
You can easily obtain the project files using PyCharm (menu "Git > Clone...").
|
|
360
|
+
PyCharm will then usually prompt you to open the project.
|
|
361
|
+
|
|
362
|
+
Open the project in a new window. PyCharm will then usually prompt you to create
|
|
363
|
+
a new virtual environment.
|
|
364
|
+
|
|
365
|
+
Create a new Poetry virtual environment for the project. If PyCharm doesn't already
|
|
366
|
+
know where your `poetry` executable is, then set the path to your `poetry` executable
|
|
367
|
+
in the "New Poetry Environment" form input field labelled "Poetry executable". In the
|
|
368
|
+
"New Poetry Environment" form, you will also have the opportunity to select which
|
|
369
|
+
Python executable will be used by the virtual environment.
|
|
370
|
+
|
|
371
|
+
PyCharm will then create a new Poetry virtual environment for your project, using
|
|
372
|
+
a particular version of Python, and also install into this virtual environment the
|
|
373
|
+
project's package dependencies according to the `pyproject.toml` file, or the
|
|
374
|
+
`poetry.lock` file if that exists in the project files.
|
|
375
|
+
|
|
376
|
+
You can add different Poetry environments for different Python versions, and switch
|
|
377
|
+
between them using the "Python Interpreter" settings of PyCharm. If you want to use
|
|
378
|
+
a version of Python that isn't installed, either use your favourite package manager,
|
|
379
|
+
or install Python by downloading an installer for recent versions of Python directly
|
|
380
|
+
from the [Python website](https://www.python.org/downloads/).
|
|
381
|
+
|
|
382
|
+
Once project dependencies have been installed, you should be able to run tests
|
|
383
|
+
from within PyCharm (right-click on the `tests` folder and select the 'Run' option).
|
|
384
|
+
|
|
385
|
+
You should also be able to open a terminal window in PyCharm, and run the project's
|
|
386
|
+
Makefile commands from the command line (see below).
|
|
387
|
+
|
|
388
|
+
### Setup from command line
|
|
389
|
+
|
|
390
|
+
Obtain the project files, using Git or suitable alternative.
|
|
391
|
+
|
|
392
|
+
In a terminal application, change your current working directory
|
|
393
|
+
to the root folder of the project files. There should be a Makefile
|
|
394
|
+
in this folder.
|
|
395
|
+
|
|
396
|
+
Use the Makefile to create a new Poetry virtual environment for the
|
|
397
|
+
project and install the project's package dependencies into it,
|
|
398
|
+
using the following command.
|
|
399
|
+
|
|
400
|
+
$ make install
|
|
401
|
+
|
|
402
|
+
Please note, if you create the virtual environment in this way, and then try to
|
|
403
|
+
open the project in PyCharm and configure the project to use this virtual
|
|
404
|
+
environment as an "Existing Poetry Environment", PyCharm sometimes has some
|
|
405
|
+
issues (don't know why) which might be problematic. If you encounter such
|
|
406
|
+
issues, you can resolve these issues by deleting the virtual environment
|
|
407
|
+
and creating the Poetry virtual environment using PyCharm (see above).
|
|
408
|
+
|
|
409
|
+
### Project Makefile commands
|
|
410
|
+
|
|
411
|
+
You can start KurrentDB using the following command.
|
|
412
|
+
|
|
413
|
+
$ make start-kurrentdb
|
|
414
|
+
|
|
415
|
+
You can run tests using the following command (needs KurrentDB to be running).
|
|
416
|
+
|
|
417
|
+
$ make test
|
|
418
|
+
|
|
419
|
+
You can stop KurrentDB using the following command.
|
|
420
|
+
|
|
421
|
+
$ make stop-kurrentdb
|
|
422
|
+
|
|
423
|
+
You can check the formatting of the code using the following command.
|
|
424
|
+
|
|
425
|
+
$ make lint
|
|
426
|
+
|
|
427
|
+
You can reformat the code using the following command.
|
|
428
|
+
|
|
429
|
+
$ make fmt
|
|
430
|
+
|
|
431
|
+
Tests belong in `./tests`. Code-under-test belongs in `./eventsourcing_kurrentdb`.
|
|
432
|
+
|
|
433
|
+
Edit package dependencies in `pyproject.toml`. Update `poetry.lock` and installed packages
|
|
434
|
+
using the following command.
|
|
435
|
+
|
|
436
|
+
$ make update
|
|
437
|
+
|