<p align="center">
<img src="https://depeche-py.github.io/depeche-db/assets/logo-bg.png" width="200" />
</p>
# Depeche DB
A library for building event-based systems on top of PostgreSQL
[![Tests](https://github.com/depeche-py/depeche-db/actions/workflows/tests.yml/badge.svg)](https://github.com/depeche-py/depeche-db/actions/workflows/tests.yml)
[![pypi](https://img.shields.io/pypi/v/depeche-db.svg)](https://pypi.python.org/pypi/depeche-db)
[![versions](https://img.shields.io/pypi/pyversions/depeche-db.svg)](https://github.com/depeche-py/depeche-db)
[![Docs](https://img.shields.io/badge/docs-here-green.svg)](https://depeche-py.github.io/depeche-db/)
[![license](https://img.shields.io/github/license/depeche-py/depeche-db.svg)](https://github.com/depeche-py/depeche-db/blob/main/LICENSE)
---
**Documentation**: [https://depeche-py.github.io/depeche-db/](https://depeche-py.github.io/depeche-db/)
**Source code**: [https://github.com/depeche-py/depeche-db](https://github.com/depeche-py/depeche-db)
---
Depeche DB is modern Python library for building event-based systems
Key features:
* Message store with optimistic concurrency control & strong ordering guarantees
* Subscriptions with "at least once" semantics
* Parallel processing of (partitioned) subscriptions
* No database polling
## Requirements
Python 3.9+
SQLAlchemy 1.4 or 2+
PostgreSQL 12+
## Installation
```bash
pip install depeche-db
# OR
poetry add depeche-db
```
## Example
```python
import pydantic, sqlalchemy, uuid, datetime as dt
from depeche_db import (
MessageStore,
StoredMessage,
MessageHandler,
SubscriptionMessage,
)
from depeche_db.tools import PydanticMessageSerializer
DB_DSN = "postgresql://depeche:depeche@localhost:4888/depeche_demo"
db_engine = sqlalchemy.create_engine(DB_DSN)
class MyMessage(pydantic.BaseModel):
content: int
message_id: uuid.UUID = pydantic.Field(default_factory=uuid.uuid4)
sent_at: dt.datetime = pydantic.Field(default_factory=dt.datetime.utcnow)
def get_message_id(self) -> uuid.UUID:
return self.message_id
def get_message_time(self) -> dt.datetime:
return self.sent_at
message_store = MessageStore[MyMessage](
name="example_store",
engine=db_engine,
serializer=PydanticMessageSerializer(MyMessage),
)
message_store.write(stream="aggregate-me-1", message=MyMessage(content=2))
print(list(message_store.read(stream="aggregate-me-1")))
# [StoredMessage(message_id=UUID('...'), stream='aggregate-me-1', version=1, message=MyMessage(content=2, message_id=UUID('...'), sent_at=datetime.datetime(...)), global_position=1)]
class ContentMessagePartitioner:
def get_partition(self, message: StoredMessage[MyMessage]) -> int:
return message.message.content % 10
class MyHandlers(MessageHandler[MyMessage]):
@MessageHandler.register
def handle_message(self, message: SubscriptionMessage[MyMessage]):
print(message)
aggregated_stream = message_store.aggregated_stream(
name="aggregated",
partitioner=ContentMessagePartitioner(),
stream_wildcards=["aggregate-me-%"],
)
subscription = aggregated_stream.subscription(
name="example_subscription",
handlers=MyHandlers(),
)
aggregated_stream.projector.run()
subscription.runner.run()
# MyHandlers.handle_message prints:
# SubscriptionMessage(partition=2, position=0, stored_message=StoredMessage(...))
```
## Contribute
Contributions in the form of issues, questions, feedback and pull requests are
welcome. Before investing a lot of time, let me know what you are up to so
we can see if your contribution fits the vision of the project.
Raw data
{
"_id": null,
"home_page": "https://github.com/depeche-py/depeche-db",
"name": "depeche-db",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.9,<4.0",
"maintainer_email": "",
"keywords": "sqlalchemy,postgresql,event-driven,event store,message store,event sourcing",
"author": "Martin Vielsmaier",
"author_email": "martin@vielsmaier.net",
"download_url": "https://files.pythonhosted.org/packages/59/71/fc822397a51d52bdb0aa8a67fa47062fd1629bf2b06ed8449c0df7431725/depeche_db-0.8.0.tar.gz",
"platform": null,
"description": "\n\n<p align=\"center\">\n <img src=\"https://depeche-py.github.io/depeche-db/assets/logo-bg.png\" width=\"200\" />\n</p>\n\n# Depeche DB\n\nA library for building event-based systems on top of PostgreSQL\n\n[![Tests](https://github.com/depeche-py/depeche-db/actions/workflows/tests.yml/badge.svg)](https://github.com/depeche-py/depeche-db/actions/workflows/tests.yml)\n[![pypi](https://img.shields.io/pypi/v/depeche-db.svg)](https://pypi.python.org/pypi/depeche-db)\n[![versions](https://img.shields.io/pypi/pyversions/depeche-db.svg)](https://github.com/depeche-py/depeche-db)\n[![Docs](https://img.shields.io/badge/docs-here-green.svg)](https://depeche-py.github.io/depeche-db/)\n[![license](https://img.shields.io/github/license/depeche-py/depeche-db.svg)](https://github.com/depeche-py/depeche-db/blob/main/LICENSE)\n\n---\n\n**Documentation**: [https://depeche-py.github.io/depeche-db/](https://depeche-py.github.io/depeche-db/)\n\n**Source code**: [https://github.com/depeche-py/depeche-db](https://github.com/depeche-py/depeche-db)\n\n---\n\nDepeche DB is modern Python library for building event-based systems\n\nKey features:\n\n* Message store with optimistic concurrency control & strong ordering guarantees\n* Subscriptions with \"at least once\" semantics\n* Parallel processing of (partitioned) subscriptions\n* No database polling\n\n## Requirements\n\nPython 3.9+\n\nSQLAlchemy 1.4 or 2+\n\nPostgreSQL 12+\n\n\n## Installation\n\n```bash\npip install depeche-db\n# OR\npoetry add depeche-db\n```\n\n## Example\n\n```python\nimport pydantic, sqlalchemy, uuid, datetime as dt\n\nfrom depeche_db import (\n MessageStore,\n StoredMessage,\n MessageHandler,\n SubscriptionMessage,\n)\nfrom depeche_db.tools import PydanticMessageSerializer\n\nDB_DSN = \"postgresql://depeche:depeche@localhost:4888/depeche_demo\"\ndb_engine = sqlalchemy.create_engine(DB_DSN)\n\n\nclass MyMessage(pydantic.BaseModel):\n content: int\n message_id: uuid.UUID = pydantic.Field(default_factory=uuid.uuid4)\n sent_at: dt.datetime = pydantic.Field(default_factory=dt.datetime.utcnow)\n\n def get_message_id(self) -> uuid.UUID:\n return self.message_id\n\n def get_message_time(self) -> dt.datetime:\n return self.sent_at\n\n\nmessage_store = MessageStore[MyMessage](\n name=\"example_store\",\n engine=db_engine,\n serializer=PydanticMessageSerializer(MyMessage),\n)\nmessage_store.write(stream=\"aggregate-me-1\", message=MyMessage(content=2))\nprint(list(message_store.read(stream=\"aggregate-me-1\")))\n# [StoredMessage(message_id=UUID('...'), stream='aggregate-me-1', version=1, message=MyMessage(content=2, message_id=UUID('...'), sent_at=datetime.datetime(...)), global_position=1)]\n\n\nclass ContentMessagePartitioner:\n def get_partition(self, message: StoredMessage[MyMessage]) -> int:\n return message.message.content % 10\n\n\nclass MyHandlers(MessageHandler[MyMessage]):\n @MessageHandler.register\n def handle_message(self, message: SubscriptionMessage[MyMessage]):\n print(message)\n\n\naggregated_stream = message_store.aggregated_stream(\n name=\"aggregated\",\n partitioner=ContentMessagePartitioner(),\n stream_wildcards=[\"aggregate-me-%\"],\n)\nsubscription = aggregated_stream.subscription(\n name=\"example_subscription\",\n handlers=MyHandlers(),\n)\n\naggregated_stream.projector.run()\nsubscription.runner.run()\n# MyHandlers.handle_message prints:\n# SubscriptionMessage(partition=2, position=0, stored_message=StoredMessage(...))\n\n```\n\n\n## Contribute\n\nContributions in the form of issues, questions, feedback and pull requests are\nwelcome. Before investing a lot of time, let me know what you are up to so\nwe can see if your contribution fits the vision of the project.\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A library for building event-based systems on top of PostgreSQL",
"version": "0.8.0",
"project_urls": {
"Changelog": "https://depeche-py.github.io/depeche-db/CHANGELOG/",
"Documentation": "https://depeche-py.github.io/depeche-db/",
"Homepage": "https://github.com/depeche-py/depeche-db",
"Repository": "https://github.com/depeche-py/depeche-db"
},
"split_keywords": [
"sqlalchemy",
"postgresql",
"event-driven",
"event store",
"message store",
"event sourcing"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "6f26ff2484ed52e4817eb6eb063eeaa6e705144ebffd445b646ed0a398687ffb",
"md5": "d757d05fbcea6db62c6dc5eb442a3c75",
"sha256": "92f054b0d992abf8066c20f58a1a74b9d7f1b40793cabae7a5f2dc70b14fd0d7"
},
"downloads": -1,
"filename": "depeche_db-0.8.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "d757d05fbcea6db62c6dc5eb442a3c75",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9,<4.0",
"size": 32743,
"upload_time": "2024-01-20T19:31:22",
"upload_time_iso_8601": "2024-01-20T19:31:22.196156Z",
"url": "https://files.pythonhosted.org/packages/6f/26/ff2484ed52e4817eb6eb063eeaa6e705144ebffd445b646ed0a398687ffb/depeche_db-0.8.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "5971fc822397a51d52bdb0aa8a67fa47062fd1629bf2b06ed8449c0df7431725",
"md5": "6c9621db14622ede5cf0311d040565d9",
"sha256": "be1ff87a860546aaa0b9fac120ca81357ecc1048de7d44927404fe868e201c90"
},
"downloads": -1,
"filename": "depeche_db-0.8.0.tar.gz",
"has_sig": false,
"md5_digest": "6c9621db14622ede5cf0311d040565d9",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9,<4.0",
"size": 26635,
"upload_time": "2024-01-20T19:31:23",
"upload_time_iso_8601": "2024-01-20T19:31:23.815201Z",
"url": "https://files.pythonhosted.org/packages/59/71/fc822397a51d52bdb0aa8a67fa47062fd1629bf2b06ed8449c0df7431725/depeche_db-0.8.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-01-20 19:31:23",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "depeche-py",
"github_project": "depeche-db",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "depeche-db"
}