# Event Sourcing with Django
This package is a Django app that supports using the Python
[eventsourcing](https://github.com/pyeventsourcing/eventsourcing) library with the [Django ORM](https://www.djangoproject.com/).
To use Django with your Python eventsourcing applications:
* install the Python package `eventsourcing_django`
* add `'eventsourcing_django'` to your Django project's `INSTALLED_APPS` setting
* migrate your database for this Django app
* set the environment variable `PERSISTENCE_MODULE` to `'eventsourcing_django'`
See below for more information.
## Installation
Use pip to install the [stable distribution](https://pypi.org/project/eventsourcing_django/)
from the Python Package Index. Please note, it is recommended to
install Python packages into a Python virtual environment.
$ pip install eventsourcing_django
Alternatively, add `eventsourcing_django` to your project's `pyproject.yaml`
or `requirements.txt` file and update your virtual environment accordingly.
## Event sourcing application
Define event-sourced aggregates and applications using the `Application` and
`Aggregate` classes from the `eventsourcing` package.
```python
from eventsourcing.application import Application
from eventsourcing.domain import Aggregate, event
from uuid import uuid5, NAMESPACE_URL
class TrainingSchool(Application):
def register(self, name):
dog = Dog(name)
self.save(dog)
def add_trick(self, name, trick):
dog = self.repository.get(Dog.create_id(name))
dog.add_trick(trick)
self.save(dog)
def get_tricks(self, name):
dog = self.repository.get(Dog.create_id(name))
return dog.tricks
class Dog(Aggregate):
@event('Registered')
def __init__(self, name):
self.name = name
self.tricks = []
@staticmethod
def create_id(name):
return uuid5(NAMESPACE_URL, f'/dogs/{name}')
@event('TrickAdded')
def add_trick(self, trick):
self.tricks.append(trick)
```
The event sourcing application can be developed and tested independently of Django.
Next, let's configure a Django project, and our event sourcing application, so
that events of the event sourcing application are stored in a Django database.
## Django project settings
Add `'eventsourcing_django'` to your Django project's `INSTALLED_APPS` setting.
INSTALLED_APPS = [
...
'eventsourcing_django',
]
This will make the Django models for storing events available in your Django project,
and allow Django to create tables in your database for storing events.
## Django database migration
Run Django's `manage.py migrate` command to create database tables for storing events.
$ python manage.py migrate
Use the `--database` option to create tables in a non-default database. The database
alias must be a key in the `DATABASES` setting of your Django project.
$ python manage.py migrate --database=postgres
Alternatively, after the Django framework has been set up for your project, you
can call Django's `call_command()` function to create the database tables.
```python
from django.core.management import call_command
call_command('migrate')
```
Use the `database` keyword argument to create tables in a non-default database.
```python
call_command('migrate', database='postgres')
```
To set up the Django framework for your Django project, `django.setup()` must have
been called after setting environment variable `DJANGO_SETTINGS_MODULE` to indicate the
settings module of your Django project. This is often done by a Django project's
`manage.py`, `wsgi.py`, and `asgi.py` files, and by tools that support Django users
such as test suite runners provided by IDEs that support Django. Django test suites
usually automatically create and migrate databases when tests are run.
## Event sourcing in Django
The event sourcing application can be configured to store events in the Django project's
database by setting the environment variable `PERSISTENCE_MODULE` to
`'eventsourcing_django'`. This step also depends on the Django framework having been
set up to for your Django project, but it doesn't depend on the database tables having
been created.
```python
training_school = TrainingSchool(
env={'PERSISTENCE_MODULE': 'eventsourcing_django'},
)
```
Use the application environment variable `DJANGO_DB_ALIAS` to configure the application
to store events in a non-default Django project database. The value of `DJANGO_DB_ALIAS`
must correspond to one of the keys in the `DATABASES` setting of the Django project.
```python
training_school = TrainingSchool(
env={
'PERSISTENCE_MODULE': 'eventsourcing_django',
'DJANGO_DB_ALIAS': 'postgres',
}
)
```
You may wish to define your event sourcing application in a separate Django app,
and construct your event sourcing application in a Django `AppConfig` subclass
in its `apps.py` module.
```python
# In your apps.py file.
from django.apps import AppConfig
class TrainingSchoolConfig(AppConfig):
name = '<django-project-name>.training_school'
def ready(self):
self.training_school = TrainingSchool(
env={'PERSISTENCE_MODULE': 'eventsourcing_django'}
)
```
You may also wish to centralize the definition of your event sourcing application's
environment variables in your Django project's settings module, and use this when
constructing the event sourcing application.
```python
# Create secret cipher key.
import os
from eventsourcing.cipher import AESCipher
os.environ['CIPHER_KEY'] = AESCipher.create_key(32)
# In your settings.py file.
import os
EVENT_SOURCING_APPLICATION = {
'PERSISTENCE_MODULE': 'eventsourcing_django',
'DJANGO_DB_ALIAS': 'postgres',
'IS_SNAPSHOTTING_ENABLED': 'y',
'COMPRESSOR_TOPIC': 'eventsourcing.compressor:ZlibCompressor',
'CIPHER_TOPIC': 'eventsourcing.cipher:AESCipher',
'CIPHER_KEY': os.environ['CIPHER_KEY'],
}
# In your apps.py file.
from django.apps import AppConfig
from django.conf import settings
class TrainingSchoolConfig(AppConfig):
name = '<django-project-name>.training_school'
def ready(self):
self.training_school = TrainingSchool(env=settings.EVENT_SOURCING_APPLICATION)
```
The single instance of the event sourcing application can then be obtained in other
places, such as views, forms, management commands, and tests.
```python
from django.apps import apps
training_school = apps.get_app_config('training_school').training_school
```
The event sourcing application's methods can be called in views, forms,
management commands, and tests.
```python
training_school.register('Fido')
training_school.add_trick('Fido', 'roll over')
training_school.add_trick('Fido', 'play dead')
tricks = training_school.get_tricks('Fido')
assert tricks == ['roll over', 'play dead']
```
Events will be stored in the Django project's database, so long as the
database tables have been created before the event sourcing application
methods are called. If the database tables have not been created, an
`OperationalError` will be raised to indicate that the tables are not found.
## Summary
In summary, before constructing an event sourcing application with `eventsourcing_django`
as its persistence module, the Django framework must have been set up for a Django
project that has `'eventsourcing_django'` included in its `INSTALLED_APPS` setting.
And, before calling the methods of the event sourcing application, the Django project's
database must have been migrated.
For more information, please refer to the Python
[eventsourcing](https://github.com/johnbywater/eventsourcing) library
and the [Django](https://www.djangoproject.com/) project.
## Management commands
The `eventsourcing_django` package is a Django app which ships with the following
Django management commands. They are available in Django projects that have
`'eventsourcing_django'` included in their `INSTALLED_APPS` setting.
At the moment, there is only one management command: `sync_followers`.
The `sync_followers` management command helps users of the `eventsourcing.system`
module. Please refer to the `eventsourcing` package docs for more information
about the `eventsourcing.system` module.
### Synchronise followers
Manually synchronise followers (i.e. `ProcessApplication` instances) with all of their
leaders, as defined in the `eventsourcing.system.System`'s pipes.
#### Usage
```shell
$ python manage.py sync_followers [-n] [-v {0,1,2,3}] [follower [follower ...]]
```
Where `follower` denotes the name of a follower to synchronize. Not specifying any means
synchronising *all followers* found in the system.
Relevant options:
- `-n`, `--dry-run`: Load and process all unseen events for the selected followers,
but roll back all changes at the end.
- `-v {0,1,2,3}`, `--verbosity {0,1,2,3}`: Verbosity level; 0=minimal output, 1=normal
output, 2=verbose output, 3=very verbose output.
For a full list of options, pass the `--help` flag to the command.
#### Examples
- To synchronise all followers found in the runner:
```shell
$ python manage.py sync_followers
```
- To synchronise a single follower:
```shell
$ python manage.py sync_followers TrainingSchool
```
The command supports the regular `-v/--verbosity` optional argument, as well as a
`-n/--dry-run` flag.
Note that running the command in dry-run mode *will* pull and process every new
event, though the changes will eventually be rolled back.
#### Error handling
Each selected follower should have its own chance at synchronisation. Therefore, the
command will catch some exceptions on a per-follower basis and continue with the
remaining followers.
The base Django exceptions that are caught are `EmptyResultSet`, `FieldDoesNotExist`,
`FieldError`, `MultipleObjectsReturned`, and `ObjectDoesNotExist`. The base exception
`EventSourcingError` from the `eventsourcing` library is also caught per follower.
### Configuration
This command needs to access a `eventsourcing.system.Runner` instance to query and act
on its followers. The runner's system is additionally the one defining the pipes between
leaders and followers.
The default behaviour, without additional configuration, is to inspect all installed
Django apps and look for an instance of `eventsourcing.system.Runner`. The attribute
name does not matter as long as it is public (i.e. not start with an underscore).
```python
# djangoproject/apps/my_es_app/apps.py
import eventsourcing.system
from django.apps import AppConfig
class MyEventSourcedAppConfig(AppConfig):
name = 'my_event_sourced_app'
runner: eventsourcing.system.Runner
def ready(self) -> None:
self.runner = eventsourcing.system.SingleThreadedRunner(
eventsourcing.system.System(...)
)
```
This is usually enough unless you i) have multiple runners defined in one or more apps,
or ii) do not hold the runner(s) in Django apps. In which case, you should configure the
Django setting `EVENTSOURCING_RUNNER` in one of two ways:
1. Set `EVENTSOURCING_RUNNER` to an app name's attribute. This attribute must be a
`eventsourcing.system.Runner` instance.
```python
# djangoproject/settings.py
...
EVENTSOURCING_RUNNER = 'my_event_sourced_app.runner'
```
2. Set `EVENTSOURCING_RUNNER` to a fully qualified function name. This function will be
called without arguments and should return a `eventsourcing.system.Runner` instance.
```python
# djangoproject/settings.py
...
EVENTSOURCING_RUNNER = 'djangoproject.runner_utils.get_runner'
```
```python
# djangoproject/runner_utils.py
import eventsourcing.system
def get_runner() -> eventsourcing.system.Runner:
return ...
```
All runner classes shipped with the `eventsourcing` library are compatible.
Raw data
{
"_id": null,
"home_page": "https://eventsourcing.readthedocs.io/",
"name": "eventsourcing-django",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.8",
"maintainer_email": null,
"keywords": null,
"author": "John Bywater",
"author_email": "john.bywater@appropriatesoftware.net",
"download_url": "https://files.pythonhosted.org/packages/ea/2b/621dc26853ff4d4cfb07e611a9a7df086051e1cdf49ee7a6f454b797e1ed/eventsourcing_django-0.5.1.tar.gz",
"platform": null,
"description": "# Event Sourcing with Django\n\nThis package is a Django app that supports using the Python\n[eventsourcing](https://github.com/pyeventsourcing/eventsourcing) library with the [Django ORM](https://www.djangoproject.com/).\n\nTo use Django with your Python eventsourcing applications:\n* install the Python package `eventsourcing_django`\n* add `'eventsourcing_django'` to your Django project's `INSTALLED_APPS` setting\n* migrate your database for this Django app\n* set the environment variable `PERSISTENCE_MODULE` to `'eventsourcing_django'`\n\nSee below for more information.\n\n\n## Installation\n\nUse pip to install the [stable distribution](https://pypi.org/project/eventsourcing_django/)\nfrom the Python Package Index. Please note, it is recommended to\ninstall Python packages into a Python virtual environment.\n\n $ pip install eventsourcing_django\n\nAlternatively, add `eventsourcing_django` to your project's `pyproject.yaml`\nor `requirements.txt` file and update your virtual environment accordingly.\n\n## Event sourcing application\n\nDefine event-sourced aggregates and applications using the `Application` and\n`Aggregate` classes from the `eventsourcing` package.\n\n```python\nfrom eventsourcing.application import Application\nfrom eventsourcing.domain import Aggregate, event\nfrom uuid import uuid5, NAMESPACE_URL\n\n\nclass TrainingSchool(Application):\n def register(self, name):\n dog = Dog(name)\n self.save(dog)\n\n def add_trick(self, name, trick):\n dog = self.repository.get(Dog.create_id(name))\n dog.add_trick(trick)\n self.save(dog)\n\n def get_tricks(self, name):\n dog = self.repository.get(Dog.create_id(name))\n return dog.tricks\n\n\nclass Dog(Aggregate):\n @event('Registered')\n def __init__(self, name):\n self.name = name\n self.tricks = []\n\n @staticmethod\n def create_id(name):\n return uuid5(NAMESPACE_URL, f'/dogs/{name}')\n\n @event('TrickAdded')\n def add_trick(self, trick):\n self.tricks.append(trick)\n```\n\nThe event sourcing application can be developed and tested independently of Django.\n\nNext, let's configure a Django project, and our event sourcing application, so\nthat events of the event sourcing application are stored in a Django database.\n\n## Django project settings\n\nAdd `'eventsourcing_django'` to your Django project's `INSTALLED_APPS` setting.\n\n INSTALLED_APPS = [\n ...\n 'eventsourcing_django',\n ]\n\nThis will make the Django models for storing events available in your Django project,\nand allow Django to create tables in your database for storing events.\n\n## Django database migration\n\nRun Django's `manage.py migrate` command to create database tables for storing events.\n\n $ python manage.py migrate\n\nUse the `--database` option to create tables in a non-default database. The database\nalias must be a key in the `DATABASES` setting of your Django project.\n\n $ python manage.py migrate --database=postgres\n\nAlternatively, after the Django framework has been set up for your project, you\ncan call Django's `call_command()` function to create the database tables.\n\n```python\nfrom django.core.management import call_command\n\ncall_command('migrate')\n```\n\nUse the `database` keyword argument to create tables in a non-default database.\n\n```python\ncall_command('migrate', database='postgres')\n```\n\nTo set up the Django framework for your Django project, `django.setup()` must have\nbeen called after setting environment variable `DJANGO_SETTINGS_MODULE` to indicate the\nsettings module of your Django project. This is often done by a Django project's\n`manage.py`, `wsgi.py`, and `asgi.py` files, and by tools that support Django users\nsuch as test suite runners provided by IDEs that support Django. Django test suites\nusually automatically create and migrate databases when tests are run.\n\n## Event sourcing in Django\n\nThe event sourcing application can be configured to store events in the Django project's\ndatabase by setting the environment variable `PERSISTENCE_MODULE` to\n`'eventsourcing_django'`. This step also depends on the Django framework having been\nset up to for your Django project, but it doesn't depend on the database tables having\nbeen created.\n\n```python\ntraining_school = TrainingSchool(\n env={'PERSISTENCE_MODULE': 'eventsourcing_django'},\n)\n```\n\nUse the application environment variable `DJANGO_DB_ALIAS` to configure the application\nto store events in a non-default Django project database. The value of `DJANGO_DB_ALIAS`\nmust correspond to one of the keys in the `DATABASES` setting of the Django project.\n\n```python\ntraining_school = TrainingSchool(\n env={\n 'PERSISTENCE_MODULE': 'eventsourcing_django',\n 'DJANGO_DB_ALIAS': 'postgres',\n }\n)\n```\n\nYou may wish to define your event sourcing application in a separate Django app,\nand construct your event sourcing application in a Django `AppConfig` subclass\nin its `apps.py` module.\n\n```python\n# In your apps.py file.\nfrom django.apps import AppConfig\n\nclass TrainingSchoolConfig(AppConfig):\n name = '<django-project-name>.training_school'\n\n def ready(self):\n self.training_school = TrainingSchool(\n env={'PERSISTENCE_MODULE': 'eventsourcing_django'}\n )\n\n```\n\nYou may also wish to centralize the definition of your event sourcing application's\nenvironment variables in your Django project's settings module, and use this when\nconstructing the event sourcing application.\n\n```python\n# Create secret cipher key.\nimport os\nfrom eventsourcing.cipher import AESCipher\nos.environ['CIPHER_KEY'] = AESCipher.create_key(32)\n\n# In your settings.py file.\nimport os\n\nEVENT_SOURCING_APPLICATION = {\n 'PERSISTENCE_MODULE': 'eventsourcing_django',\n 'DJANGO_DB_ALIAS': 'postgres',\n 'IS_SNAPSHOTTING_ENABLED': 'y',\n 'COMPRESSOR_TOPIC': 'eventsourcing.compressor:ZlibCompressor',\n 'CIPHER_TOPIC': 'eventsourcing.cipher:AESCipher',\n 'CIPHER_KEY': os.environ['CIPHER_KEY'],\n}\n\n# In your apps.py file.\nfrom django.apps import AppConfig\nfrom django.conf import settings\n\nclass TrainingSchoolConfig(AppConfig):\n name = '<django-project-name>.training_school'\n\n def ready(self):\n self.training_school = TrainingSchool(env=settings.EVENT_SOURCING_APPLICATION)\n```\n\nThe single instance of the event sourcing application can then be obtained in other\nplaces, such as views, forms, management commands, and tests.\n\n```python\nfrom django.apps import apps\n\ntraining_school = apps.get_app_config('training_school').training_school\n```\n\nThe event sourcing application's methods can be called in views, forms,\nmanagement commands, and tests.\n\n```python\ntraining_school.register('Fido')\n\ntraining_school.add_trick('Fido', 'roll over')\ntraining_school.add_trick('Fido', 'play dead')\n\ntricks = training_school.get_tricks('Fido')\nassert tricks == ['roll over', 'play dead']\n```\n\nEvents will be stored in the Django project's database, so long as the\ndatabase tables have been created before the event sourcing application\nmethods are called. If the database tables have not been created, an\n`OperationalError` will be raised to indicate that the tables are not found.\n\n## Summary\n\nIn summary, before constructing an event sourcing application with `eventsourcing_django`\nas its persistence module, the Django framework must have been set up for a Django\nproject that has `'eventsourcing_django'` included in its `INSTALLED_APPS` setting.\nAnd, before calling the methods of the event sourcing application, the Django project's\ndatabase must have been migrated.\n\nFor more information, please refer to the Python\n[eventsourcing](https://github.com/johnbywater/eventsourcing) library\nand the [Django](https://www.djangoproject.com/) project.\n\n\n## Management commands\n\nThe `eventsourcing_django` package is a Django app which ships with the following\nDjango management commands. They are available in Django projects that have\n`'eventsourcing_django'` included in their `INSTALLED_APPS` setting.\n\nAt the moment, there is only one management command: `sync_followers`.\n\nThe `sync_followers` management command helps users of the `eventsourcing.system`\nmodule. Please refer to the `eventsourcing` package docs for more information\nabout the `eventsourcing.system` module.\n\n### Synchronise followers\n\nManually synchronise followers (i.e. `ProcessApplication` instances) with all of their\nleaders, as defined in the `eventsourcing.system.System`'s pipes.\n\n#### Usage\n\n```shell\n$ python manage.py sync_followers [-n] [-v {0,1,2,3}] [follower [follower ...]]\n```\n\nWhere `follower` denotes the name of a follower to synchronize. Not specifying any means\nsynchronising *all followers* found in the system.\n\nRelevant options:\n\n - `-n`, `--dry-run`: Load and process all unseen events for the selected followers,\n but roll back all changes at the end.\n - `-v {0,1,2,3}`, `--verbosity {0,1,2,3}`: Verbosity level; 0=minimal output, 1=normal\n output, 2=verbose output, 3=very verbose output.\n\nFor a full list of options, pass the `--help` flag to the command.\n\n#### Examples\n\n - To synchronise all followers found in the runner:\n\n ```shell\n $ python manage.py sync_followers\n ```\n\n - To synchronise a single follower:\n\n ```shell\n $ python manage.py sync_followers TrainingSchool\n ```\n\nThe command supports the regular `-v/--verbosity` optional argument, as well as a\n`-n/--dry-run` flag.\n\nNote that running the command in dry-run mode *will* pull and process every new\nevent, though the changes will eventually be rolled back.\n\n#### Error handling\n\nEach selected follower should have its own chance at synchronisation. Therefore, the\ncommand will catch some exceptions on a per-follower basis and continue with the\nremaining followers.\n\nThe base Django exceptions that are caught are `EmptyResultSet`, `FieldDoesNotExist`,\n`FieldError`, `MultipleObjectsReturned`, and `ObjectDoesNotExist`. The base exception\n`EventSourcingError` from the `eventsourcing` library is also caught per follower.\n\n### Configuration\n\nThis command needs to access a `eventsourcing.system.Runner` instance to query and act\non its followers. The runner's system is additionally the one defining the pipes between\nleaders and followers.\n\nThe default behaviour, without additional configuration, is to inspect all installed\nDjango apps and look for an instance of `eventsourcing.system.Runner`. The attribute\nname does not matter as long as it is public (i.e. not start with an underscore).\n\n```python\n# djangoproject/apps/my_es_app/apps.py\nimport eventsourcing.system\nfrom django.apps import AppConfig\n\n\nclass MyEventSourcedAppConfig(AppConfig):\n name = 'my_event_sourced_app'\n runner: eventsourcing.system.Runner\n\n def ready(self) -> None:\n self.runner = eventsourcing.system.SingleThreadedRunner(\n eventsourcing.system.System(...)\n )\n```\n\nThis is usually enough unless you i) have multiple runners defined in one or more apps,\nor ii) do not hold the runner(s) in Django apps. In which case, you should configure the\nDjango setting `EVENTSOURCING_RUNNER` in one of two ways:\n\n1. Set `EVENTSOURCING_RUNNER` to an app name's attribute. This attribute must be a\n `eventsourcing.system.Runner` instance.\n\n ```python\n # djangoproject/settings.py\n ...\n EVENTSOURCING_RUNNER = 'my_event_sourced_app.runner'\n ```\n\n2. Set `EVENTSOURCING_RUNNER` to a fully qualified function name. This function will be\n called without arguments and should return a `eventsourcing.system.Runner` instance.\n\n ```python\n # djangoproject/settings.py\n ...\n EVENTSOURCING_RUNNER = 'djangoproject.runner_utils.get_runner'\n ```\n ```python\n # djangoproject/runner_utils.py\n import eventsourcing.system\n\n\n def get_runner() -> eventsourcing.system.Runner:\n return ...\n ```\n\nAll runner classes shipped with the `eventsourcing` library are compatible.\n",
"bugtrack_url": null,
"license": "BSD 3-Clause",
"summary": "Python package for eventsourcing with Django.",
"version": "0.5.1",
"project_urls": {
"Homepage": "https://eventsourcing.readthedocs.io/",
"Repository": "https://github.com/pyeventsourcing/eventsourcing-django"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "4004e3a888b23c154dc8c8986615930fd5ecb89ddb54b2c1e7c0b7f65b0d5892",
"md5": "7ab8443e5e481ba1c3b6fa22763cbcb7",
"sha256": "bafc229a31b4a542b93105250fefce5f04641651b02cf8c0435485a6a6851f75"
},
"downloads": -1,
"filename": "eventsourcing_django-0.5.1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "7ab8443e5e481ba1c3b6fa22763cbcb7",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.8",
"size": 15436,
"upload_time": "2024-11-09T05:45:29",
"upload_time_iso_8601": "2024-11-09T05:45:29.025421Z",
"url": "https://files.pythonhosted.org/packages/40/04/e3a888b23c154dc8c8986615930fd5ecb89ddb54b2c1e7c0b7f65b0d5892/eventsourcing_django-0.5.1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "ea2b621dc26853ff4d4cfb07e611a9a7df086051e1cdf49ee7a6f454b797e1ed",
"md5": "1e2dc833c1036d876b96da4b80ba03b6",
"sha256": "5e42c695b1cb60b4961de41ae2ca70d2e364a5a343de09f727b764f5df5f7141"
},
"downloads": -1,
"filename": "eventsourcing_django-0.5.1.tar.gz",
"has_sig": false,
"md5_digest": "1e2dc833c1036d876b96da4b80ba03b6",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.8",
"size": 16616,
"upload_time": "2024-11-09T05:45:31",
"upload_time_iso_8601": "2024-11-09T05:45:31.052613Z",
"url": "https://files.pythonhosted.org/packages/ea/2b/621dc26853ff4d4cfb07e611a9a7df086051e1cdf49ee7a6f454b797e1ed/eventsourcing_django-0.5.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-11-09 05:45:31",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "pyeventsourcing",
"github_project": "eventsourcing-django",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "eventsourcing-django"
}