Skip to content

Commit 83f0824

Browse files
committed
Add documentation
1 parent 5743ebf commit 83f0824

File tree

6 files changed

+388
-8
lines changed

6 files changed

+388
-8
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
python-version: ["3.11", "3.12"]
14+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
1515
name: Python ${{ matrix.python-version }} Tests
1616

1717
services:

README.md

Lines changed: 335 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# message-db-py
22

3+
Message DB is a fully-featured event store and message store implemented in
4+
PostgreSQL for Pub/Sub, Event Sourcing, Messaging, and Evented Microservices
5+
applications.
6+
37
`message-db-py` is a Python interface to the Message DB event store and message
48
store, designed for easy integration into Python applications.
59

@@ -17,24 +21,350 @@ Use pip to install:
1721
$ pip install message-db-py
1822
```
1923

24+
## Setting up Message DB database
25+
26+
Clone the Message DB repository to set up the database:
27+
28+
```shell
29+
git clone [email protected]:message-db/message-db.git
30+
```
31+
32+
More detailed instructions are in the [Installation]
33+
(https://github.com/message-db/message-db?tab=readme-ov-file#installation)
34+
section of Message DB repo.
35+
36+
Running the database installation script creates the database, schema, table,
37+
indexes, functions, views, types, a user role, and limit the user's privileges
38+
to the message store's public interface.
39+
40+
The installation script is in the database directory of the cloned Message DB
41+
repo. Change directory to the message-db directory where you cloned the repo,
42+
and run the script:
43+
44+
```shell
45+
database/install.sh
46+
```
47+
48+
Make sure that your default Postgres user has administrative privileges.
49+
50+
### Database Name
51+
52+
By default, the database creation tool will create a database named
53+
`message_store`.
54+
55+
If you prefer either a different database name, you can override the name
56+
using the `DATABASE_NAME` environment variable.
57+
58+
```shell
59+
DATABASE_NAME=some_other_database database/install.sh
60+
```
61+
62+
### Uninstalling the Database
63+
64+
If you need to drop the database (for example, on a local dev machine):
65+
66+
``` bash
67+
database/uninstall.sh
68+
```
69+
70+
If you're upgrading a previous version of the database:
71+
72+
``` bash
73+
database/update.sh
74+
```
75+
76+
## Docker Image
77+
78+
You can optionally use a Docker image with Message DB pre-installed and ready
79+
to go. This is especially helpful to run test cases locally.
80+
81+
The docker image is available in [Docker Hub](https://hub.docker.com/r/ethangarofolo/message-db).
82+
The source is in [Gitlab](https://gitlab.com/such-software/message-db-docker)
83+
2084
## Usage
2185

86+
The complete user guide for Message DB is available at
87+
[http://docs.eventide-project.org/user-guide/message-db/]
88+
(http://docs.eventide-project.org/user-guide/message-db/).
89+
90+
Below is documentation for methods exposed through the Python API.
91+
92+
### Quickstart
93+
2294
Here's a quick example of how to publish and read messages using Message-DB-py:
2395

2496
```python
25-
from message_db.client import MessageDB
97+
from message_db import MessageDB
2698

2799
# Initialize the database connection
28-
mdb = MessageDB("your_connection_string")
100+
store = MessageDB(CONN_URL)
29101

30102
# Write a message
31-
mdb.write("your_stream_name", "your_message_type", {"data": "value"})
103+
store.write("user_stream", "register", {"name": "John Doe"})
32104

33105
# Read a message
34-
message = mdb.read_last_message("your_stream_name")
106+
message = store.read_last_message("user_stream")
35107
print(message)
36108
```
37109

110+
## Primary APIs
111+
112+
### Write messages
113+
114+
The `write` method is used to append a new message to a specified stream within
115+
the message database. This method ensures that the message is written with the
116+
appropriate type, data, and metadata, and optionally, at a specific expected
117+
version of the stream.
118+
119+
#### Parameters
120+
121+
- `stream_name` (`str`): The name of the stream to which the message will be
122+
written. This identifies the logical series of messages.
123+
- `message_type` (`str`): The type of message being written. Typically, this
124+
reflects the nature of the event or data change the message represents.
125+
- `data` (`Dict`): The data payload of the message. This should be a dictionary
126+
containing the actual information the message carries.
127+
- `metadata` (`Dict` | `None`): Optional. Metadata about the message, provided as a
128+
dictionary. Metadata can include any additional information that is not part of
129+
the - main data payload, such as sender information or timestamps.
130+
Defaults to None.
131+
- `expected_version` (`int` | `None`): Optional. The version of the stream where the
132+
client expects to write the message. This is used for concurrency control and
133+
ensuring the integrity of the stream's order. Defaults to `None`.
134+
135+
#### Returns
136+
137+
- `position` (`int`): The position (or version number) of the message in the
138+
stream after it has been successfully written.
139+
140+
#### Example
141+
142+
```python
143+
message_db = MessageDB(connection_pool=my_pool)
144+
stream_name = "user_updates"
145+
message_type = "UserCreated"
146+
data = {"user_id": 123, "username": "example"}
147+
metadata = {"source": "web_app"}
148+
149+
position = message_db.write(stream_name, message_type, data, metadata)
150+
151+
print("Message written at position:", position)
152+
```
153+
154+
### Read messages from a stream or category
155+
156+
The `read` method retrieves messages from a specified stream or category. This
157+
method supports flexible query options through a direct SQL parameter or by
158+
determining the SQL based on the stream name and its context
159+
(stream vs. category vs. all messages).
160+
161+
#### Parameters
162+
163+
- `stream_name` (`str`): The identifier for the stream or category from which
164+
messages are to be retrieved. Special names like "$all" can be used to fetch
165+
messages across all streams.
166+
- `sql` (`str` | `None`, optional): An optional SQL query string that if
167+
provided, overrides the default SQL generation based on the stream_name.
168+
If None, the SQL is automatically generated based on the stream_name value.
169+
Defaults to None.
170+
- `position` (`int`, optional): The starting position in the stream or category
171+
from which to begin reading messages. Defaults to 0.
172+
- `no_of_messages` (`int`, optional): The maximum number of messages to
173+
retrieve. Defaults to 1000.
174+
175+
#### Returns
176+
177+
- List[Dict[str, Any]]: A list of messages, where each message is
178+
represented as a dictionary containing details such as the message ID,
179+
stream name, type, position, global position, data, metadata, and timestamp.
180+
181+
#### Example
182+
183+
```python
184+
message_db = MessageDB(connection_pool=my_pool)
185+
stream_name = "user-updates"
186+
position = 10
187+
no_of_messages = 50
188+
189+
# Reading from a specific stream
190+
messages = message_db.read(stream_name, position=position, no_of_messages=no_of_messages)
191+
192+
# Custom SQL query
193+
custom_sql = "SELECT * FROM get_stream_messages(%(stream_name)s, %(position)s, %(batch_size)s);"
194+
messages = message_db.read(stream_name, sql=custom_sql, position=position, no_of_messages=no_of_messages)
195+
196+
for message in messages:
197+
print(message)
198+
```
199+
200+
### Read Last Message from stream
201+
202+
The `read_last_message` method retrieves the most recent message from a
203+
specified stream. This method is useful when you need the latest state or
204+
event in a stream without querying the entire message history.
205+
206+
#### Parameters
207+
208+
- `stream_name` (`str`): The name of the stream from which the last message is to be
209+
retrieved.
210+
211+
#### Returns
212+
213+
- `Dict`[`str`, `Any`] | `None`: A dictionary representing the last message
214+
in the specified stream. If the stream is empty or the message does not exist,
215+
`None` is returned.
216+
217+
#### Example
218+
219+
```python
220+
message_db = MessageDB(connection_pool=my_pool)
221+
stream_name = "user_updates"
222+
223+
# Reading the last message from a stream
224+
last_message = message_db.read_last_message(stream_name)
225+
226+
if last_message:
227+
print("Last message data:", last_message)
228+
else:
229+
print("No messages found in the stream.")
230+
```
231+
232+
## Utility APIs
233+
234+
### Read Stream
235+
236+
The `read_stream` method retrieves a sequence of messages from a specified stream
237+
within the message database. This method is specifically designed to fetch
238+
messages from a well-defined stream based on a starting position and a
239+
specified number of messages.
240+
241+
#### Parameters
242+
243+
- `stream_name` (`str`): The name of the stream from which messages are to be
244+
retrieved. This name must include a hyphen (-) to be recognized as a valid
245+
stream identifier.
246+
- `position` (`int`, optional): The zero-based index position from which to start
247+
reading messages. Defaults to 0, which starts reading from the beginning of
248+
the stream.
249+
- `no_of_messages` (`int`, optional): The maximum number of messages to retrieve
250+
from the stream. Defaults to 1000.
251+
252+
#### Returns
253+
254+
- `List`[`Dict`[`str`, `Any`]]: A list of dictionaries, each representing a message
255+
retrieved from the stream. Each dictionary contains the message details
256+
structured in key-value pairs.
257+
258+
#### Exceptions
259+
260+
- `ValueError`: Raised if the provided stream_name does not contain a hyphen
261+
(-), which is required to validate the name as a stream identifier.
262+
263+
#### Example
264+
265+
```python
266+
message_db = MessageDB(connection_pool=my_pool)
267+
stream_name = "user-updates-2023"
268+
position = 0
269+
no_of_messages = 100
270+
271+
messages = message_db.read_stream(stream_name, position, no_of_messages)
272+
273+
for message in messages:
274+
print(message)
275+
```
276+
277+
### Read Category
278+
279+
The `read_category` method retrieves a sequence of messages from a specified
280+
category within the message database. It is designed to fetch messages based
281+
on a category identifier, starting from a specific position, and up to a
282+
defined limit of messages.
283+
284+
#### Parameters
285+
286+
- `category_name` (`str`): The name of the category from which messages are to be
287+
retrieved. This identifier should not include a hyphen (-) to validate it as
288+
a category name.
289+
- `position` (`int`, optional): The zero-based index position from which to start
290+
reading messages within the category. Defaults to 0.
291+
- `no_of_messages` (`int`, optional): The maximum number of messages to retrieve
292+
from the category. Defaults to 1000.
293+
294+
#### Returns
295+
296+
- List[Dict[str, Any]]: A list of dictionaries, each representing a message.
297+
Each dictionary includes details about the message such as the message ID,
298+
stream name, type, position, global position, data, metadata, and time of
299+
creation.
300+
301+
#### Exceptions
302+
303+
- `ValueError`: Raised if the provided category_name contains a hyphen (-),
304+
which is not allowed for category identifiers and implies a misunderstanding
305+
between streams and categories.
306+
307+
#### Example
308+
309+
```python
310+
message_db = MessageDB(connection_pool=my_pool)
311+
category_name = "user_updates"
312+
position = 0
313+
no_of_messages = 100
314+
315+
# Reading messages from a category
316+
messages = message_db.read_category(category_name, position, no_of_messages)
317+
318+
for message in messages:
319+
print(message)
320+
```
321+
322+
### Write Batch
323+
324+
The `write_batch` method is designed to write a series of messages to a
325+
specified stream in a batch operation. It ensures atomicity in writing
326+
operations, where all messages are written in sequence, and each subsequent
327+
message can optionally depend on the position of the last message written.
328+
This method is useful when multiple messages need to be written as a part of a
329+
single transactional context.
330+
331+
#### Parameters
332+
333+
- `stream_name` (`str`): The name of the stream to which the batch of messages
334+
will be written.
335+
- `data` (`List`[`Tuple`[`str`, `Dict`, `Dict` | `None`]]): A list of tuples,
336+
where each tuple represents a message. The tuple format is (message_type, data,
337+
metadata), with metadata being optional.
338+
- `expected_version` (`int` | `None`, optional): The version of the stream
339+
where the batch operation expects to start writing. This can be used for
340+
concurrency control to ensure messages are written in the expected order.
341+
Defaults to None.
342+
343+
#### Returns
344+
345+
- `position` (`int`): The position (or version number) of the last message
346+
written in the stream as a result of the batch operation.
347+
348+
#### Example
349+
350+
```python
351+
message_db = MessageDB(connection_pool=my_pool)
352+
stream_name = "order_events"
353+
data = [
354+
("OrderCreated", {"order_id": 123, "product_id": 456}, None),
355+
("OrderShipped",
356+
{"order_id": 123, "shipment_id": 789},
357+
{"priority": "high"}
358+
),
359+
("OrderDelivered", {"order_id": 123, "delivery_date": "2024-04-23"}, None)
360+
]
361+
362+
# Writing a batch of messages to a stream
363+
last_position = message_db.write_batch(stream_name, data)
364+
365+
print(f"Last message written at position: {last_position}")
366+
```
367+
38368
## License
39369

40-
The Postgres Message Store is released under the [MIT License](https://github.com/subhashb/message-db-py/blob/main/LICENSE).
370+
[MIT](https://github.com/subhashb/message-db-py/blob/main/LICENSE)

0 commit comments

Comments
 (0)