Skip to content

Commit b824342

Browse files
authored
Merge pull request #3 from AntonOfTheWoods/subbro
2 parents ec2b98d + 4a21b2d commit b824342

25 files changed

+616
-3
lines changed

django-subscriptions-rxdb/Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
install:
2+
poetry install && PYTHONPATH=src poetry run ./manage.py migrate
3+
4+
install-postgres:
5+
poetry install -E postgres && PYTHONPATH=src poetry run ./manage.py migrate
6+
7+
run:
8+
PYTHONPATH=src poetry run uvicorn demo.asgi:application --reload --debug --reload-dir src

django-subscriptions-rxdb/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# django-subscriptions-rxdb
2+
3+
## tl;dr
4+
This example reproduces the server-side component of the RxDB GraphQL example, with a couple of extras, which can be found [here](https://github.com/pubkey/rxdb/tree/master/examples/graphql). Checkout RxDB and run the GraphQL example. When running Ok, change `export const GRAPHQL_PORT = 10102;` and `export const GRAPHQL_SUBSCRIPTION_PORT = 10103;` ([lines 1 and 3 of this file](https://github.com/pubkey/rxdb/blob/master/examples/graphql/shared.js)) to equal `8000`. Then in the directory of this README file, `make install && make run`. More information about RxDB and GraphQL synchronisation can be found [here](https://rxdb.info/replication-graphql.html).
5+
6+
## Requirements
7+
- [Clone of RxDB](https://github.com/pubkey/rxdb)
8+
- [Python 3.8+](https://www.python.org/downloads/) (might work with other versions, not tested)
9+
- [Poetry](https://python-poetry.org/)
10+
- `make`
11+
- [PostgreSQL](https://www.postgresql.org/) (optional)
12+
13+
Tested on Ubuntu 20.04, should work everywhere these are available with no changes.
14+
15+
## Reason for this example
16+
While connectivity is getting better around the world every year, every user will always at least have moments when their connection is spotty, if not completely absent (at human prices) for certain periods (eg., on a plane). With an offline-first, local database and other offline-first tech (service workers, etc.), it is possible to develop web-technology-based applications that will continue to offer much of their functionality offline, with realtime sync when they are online. This really is the best of both worlds, and can give much more fluid and friendly usage when connections are spotty.
17+
18+
So you already have a Django-based app, and want to add some rich, client-side, offline-first functionality without starting from scratch? This is an example of some of the server-side code for one way of starting out.
19+
20+
The RxDB component of this is not included here to keep things as light as possible. Offline-first has complexities that need to be mastered, so this is not intended to be a template or even a particularly good way of doing things, just a rough example to get you started with `rxdb`, `strawberry`, `django` and `broadcaster`.
21+
22+
### The full example stack
23+
24+
The example shows how you can do offline-realtime synchronisation of a local, RxDB database with Django. It uses Strawberry GraphQL subscriptions over websockets for the realtime sync capabilities. The default Django database configured in the `settings` file is Sqlite3 but there is also commented-out config for PostgreSQL.
25+
26+
If you use PostgreSQL then you will have to set up a database like you would for any PostgreSQL-powered Django site but if you do, the example will also then use PostgreSQL for the realtime notification functionality. To run the example with `postgres`, comment out the `sqlite3` config and put your `postgres` config in the settings file (src/demo/settings.py). You will also need to install extra deps with:
27+
28+
```
29+
make install-postgres
30+
```
31+
32+
Now simply run `make run` as usual to run it.
33+
34+
This extended setup shows how you can have multiple instances of your Django (in a Kubernetes cluster, for example), and realtime notifications will work for any client connected to any of the servers. This example uses [broadcaster](https://github.com/encode/broadcaster) for subscription notifications which, in addition to `postgres`, supports `memory` (for a single node), `redis` and `kafka`. If your `django` is not using `postgres` and/or you are using one of the other options for caching or messaging, you might want to use one of those. It should Just Work if you follow the `broadcaster` docs for the connection string.

django-subscriptions-rxdb/manage.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env python
2+
"""Django's command-line utility for administrative tasks."""
3+
import os
4+
import sys
5+
6+
7+
def main():
8+
"""Run administrative tasks."""
9+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'demo.settings')
10+
try:
11+
from django.core.management import execute_from_command_line
12+
except ImportError as exc:
13+
raise ImportError(
14+
"Couldn't import Django. Are you sure it's installed and "
15+
"available on your PYTHONPATH environment variable? Did you "
16+
"forget to activate a virtual environment?"
17+
) from exc
18+
execute_from_command_line(sys.argv)
19+
20+
21+
if __name__ == '__main__':
22+
main()
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[tool.poetry]
2+
name = "django-subscriptions-rxdb"
3+
version = "0.1.0"
4+
description = ""
5+
authors = ["Patrick Arminio <[email protected]>", "Anton Melser <[email protected]>"]
6+
license = "MIT"
7+
8+
[tool.poetry.dependencies]
9+
python = "^3.8"
10+
strawberry-graphql = "^0.46.0"
11+
Django = "^3.1.7"
12+
uvicorn = {extras = ["standard"], version = "^0.13.4"}
13+
django-cors-headers = "^3.7.0"
14+
psycopg2-binary = {version = "^2.8.6", extras = ["postgres"], optional = true}
15+
broadcaster = {extras = ["postgres"], version = "^0.2.0"}
16+
strawberry-graphql-django = "^0.0.6"
17+
18+
[tool.poetry.extras]
19+
postgres = ["psycopg2-binary"]
20+
21+
22+
[tool.poetry.dev-dependencies]
23+
black = {version = "^20.8b1", allow-prereleases = true}
24+
flake8 = "^3.8.4"
25+
isort = "^5.7.0"
26+
27+
[build-system]
28+
requires = ["poetry-core>=1.0.0"]
29+
build-backend = "poetry.core.masonry.api"

django-subscriptions-rxdb/src/api/__init__.py

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from django.apps import AppConfig
4+
5+
6+
class ApiConfig(AppConfig):
7+
name = "api"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import typing
4+
5+
from starlette.requests import Request
6+
from starlette.websockets import WebSocket
7+
from strawberry.asgi import GraphQL
8+
9+
from .context import Context, get_broadcast
10+
11+
12+
class MyGraphQL(GraphQL):
13+
async def get_context(
14+
self, request: typing.Union[Request, WebSocket]
15+
) -> typing.Optional[typing.Any]:
16+
broadcast = await get_broadcast()
17+
18+
return Context(broadcast)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from dataclasses import dataclass
4+
5+
from broadcaster import Broadcast
6+
from django.db import connection
7+
8+
broadcast = None
9+
10+
11+
async def get_broadcast():
12+
global broadcast
13+
14+
if not broadcast:
15+
dbinfo = connection.settings_dict
16+
if dbinfo.get("ENGINE") == "django.db.backends.postgresql":
17+
host = (
18+
f"{dbinfo.get('HOST')}:{dbinfo.get('PORT')}"
19+
if dbinfo.get("PORT")
20+
else dbinfo.get("HOST")
21+
)
22+
dsnstr = f"postgresql://{dbinfo.get('USER')}:{dbinfo.get('PASSWORD')}@{host}/{dbinfo.get('NAME')}"
23+
broadcast = Broadcast(dsnstr)
24+
else:
25+
broadcast = Broadcast("memory://")
26+
27+
await broadcast.connect()
28+
29+
return broadcast
30+
31+
32+
@dataclass
33+
class Context:
34+
broadcast: Broadcast
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Generated by Django 3.1.7 on 2021-02-28 03:58
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
initial = True
9+
10+
dependencies = []
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name="Hero",
15+
fields=[
16+
(
17+
"id",
18+
models.CharField(max_length=100, primary_key=True, serialize=False),
19+
),
20+
("color", models.CharField(max_length=100)),
21+
("name", models.CharField(max_length=100, null=True)),
22+
("updatedAt", models.FloatField(max_length=100, null=True)),
23+
("deleted", models.BooleanField(default=True)),
24+
],
25+
),
26+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 3.1.7 on 2021-03-03 02:54
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("api", "0001_initial"),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name="hero",
15+
name="deleted",
16+
field=models.BooleanField(),
17+
),
18+
]

0 commit comments

Comments
 (0)