django-litestream 0.0.1__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.
Files changed (30) hide show
  1. django_litestream-0.0.1/.idea/django-litestream.iml +10 -0
  2. django_litestream-0.0.1/.idea/inspectionProfiles/Project_Default.xml +68 -0
  3. django_litestream-0.0.1/.idea/inspectionProfiles/profiles_settings.xml +6 -0
  4. django_litestream-0.0.1/.idea/material_theme_project_new.xml +13 -0
  5. django_litestream-0.0.1/.idea/misc.xml +7 -0
  6. django_litestream-0.0.1/.idea/modules.xml +8 -0
  7. django_litestream-0.0.1/.idea/vcs.xml +6 -0
  8. django_litestream-0.0.1/.idea/workspace.xml +96 -0
  9. django_litestream-0.0.1/.pre-commit-config.yaml +44 -0
  10. django_litestream-0.0.1/LICENSE.txt +9 -0
  11. django_litestream-0.0.1/PKG-INFO +56 -0
  12. django_litestream-0.0.1/README.md +24 -0
  13. django_litestream-0.0.1/demo/db.sqlite3 +0 -0
  14. django_litestream-0.0.1/demo/demo/__init__.py +0 -0
  15. django_litestream-0.0.1/demo/demo/asgi.py +15 -0
  16. django_litestream-0.0.1/demo/demo/settings.py +125 -0
  17. django_litestream-0.0.1/demo/demo/urls.py +22 -0
  18. django_litestream-0.0.1/demo/demo/wsgi.py +15 -0
  19. django_litestream-0.0.1/demo/litestream.yml +8 -0
  20. django_litestream-0.0.1/demo/manage.py +22 -0
  21. django_litestream-0.0.1/justfile +16 -0
  22. django_litestream-0.0.1/pyproject.toml +67 -0
  23. django_litestream-0.0.1/src/django_litestream/__about__.py +4 -0
  24. django_litestream-0.0.1/src/django_litestream/__init__.py +3 -0
  25. django_litestream-0.0.1/src/django_litestream/apps.py +6 -0
  26. django_litestream-0.0.1/src/django_litestream/conf.py +35 -0
  27. django_litestream-0.0.1/src/django_litestream/management/__init__.py +40 -0
  28. django_litestream-0.0.1/src/django_litestream/management/commands/__init__.py +0 -0
  29. django_litestream-0.0.1/src/django_litestream/management/commands/litestream.py +233 -0
  30. django_litestream-0.0.1/tests/__init__.py +3 -0
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
6
+ </content>
7
+ <orderEntry type="jdk" jdkName="Python 3.12 (django-litestream)" jdkType="Python SDK" />
8
+ <orderEntry type="sourceFolder" forTests="false" />
9
+ </component>
10
+ </module>
@@ -0,0 +1,68 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <profile version="1.0">
3
+ <option name="myName" value="Project Default" />
4
+ <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
5
+ <inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
6
+ <option name="myValues">
7
+ <value>
8
+ <list size="20">
9
+ <item index="0" class="java.lang.String" itemvalue="nobr" />
10
+ <item index="1" class="java.lang.String" itemvalue="noembed" />
11
+ <item index="2" class="java.lang.String" itemvalue="comment" />
12
+ <item index="3" class="java.lang.String" itemvalue="noscript" />
13
+ <item index="4" class="java.lang.String" itemvalue="embed" />
14
+ <item index="5" class="java.lang.String" itemvalue="script" />
15
+ <item index="6" class="java.lang.String" itemvalue="c-button" />
16
+ <item index="7" class="java.lang.String" itemvalue="c-tabs" />
17
+ <item index="8" class="java.lang.String" itemvalue="c-tab" />
18
+ <item index="9" class="java.lang.String" itemvalue="c-accordion" />
19
+ <item index="10" class="java.lang.String" itemvalue="c-accordion-title" />
20
+ <item index="11" class="java.lang.String" itemvalue="c-slot" />
21
+ <item index="12" class="java.lang.String" itemvalue="c-card" />
22
+ <item index="13" class="java.lang.String" itemvalue="c-theme-toggle" />
23
+ <item index="14" class="java.lang.String" itemvalue="c-heoricon" />
24
+ <item index="15" class="java.lang.String" itemvalue="c-heroicon" />
25
+ <item index="16" class="java.lang.String" itemvalue="c-vars" />
26
+ <item index="17" class="java.lang.String" itemvalue="c-heroicon-outline" />
27
+ <item index="18" class="java.lang.String" itemvalue="c-heroicon.micro" />
28
+ <item index="19" class="java.lang.String" itemvalue="c-heroicon.outline" />
29
+ </list>
30
+ </value>
31
+ </option>
32
+ <option name="myCustomValuesEnabled" value="true" />
33
+ </inspection_tool>
34
+ <inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
35
+ <option name="ourVersions">
36
+ <value>
37
+ <list size="3">
38
+ <item index="0" class="java.lang.String" itemvalue="3.10" />
39
+ <item index="1" class="java.lang.String" itemvalue="3.11" />
40
+ <item index="2" class="java.lang.String" itemvalue="3.12" />
41
+ </list>
42
+ </value>
43
+ </option>
44
+ </inspection_tool>
45
+ <inspection_tool class="PyPackageRequirementsInspection" enabled="false" level="WARNING" enabled_by_default="false">
46
+ <option name="ignoredPackages">
47
+ <value>
48
+ <list size="13">
49
+ <item index="0" class="java.lang.String" itemvalue="fuzzy-couscous" />
50
+ <item index="1" class="java.lang.String" itemvalue="jupyter-core" />
51
+ <item index="2" class="java.lang.String" itemvalue="notebook-shim" />
52
+ <item index="3" class="java.lang.String" itemvalue="jupyterlab-server" />
53
+ <item index="4" class="java.lang.String" itemvalue="typing-extensions" />
54
+ <item index="5" class="java.lang.String" itemvalue="jupyter-client" />
55
+ <item index="6" class="java.lang.String" itemvalue="jupyter-server" />
56
+ <item index="7" class="java.lang.String" itemvalue="jupyterlab-pygments" />
57
+ <item index="8" class="java.lang.String" itemvalue="jupyter-server-terminals" />
58
+ <item index="9" class="java.lang.String" itemvalue="jupyterlab-widgets" />
59
+ <item index="10" class="java.lang.String" itemvalue="dj-notebook" />
60
+ <item index="11" class="java.lang.String" itemvalue="prometheus-client" />
61
+ <item index="12" class="java.lang.String" itemvalue="django-tailwind-cli" />
62
+ </list>
63
+ </value>
64
+ </option>
65
+ </inspection_tool>
66
+ <inspection_tool class="PyTypeHintsInspection" enabled="true" level="INFORMATION" enabled_by_default="true" />
67
+ </profile>
68
+ </component>
@@ -0,0 +1,6 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="MaterialThemeProjectNewConfig">
4
+ <option name="metadata">
5
+ <MTProjectMetadataState>
6
+ <option name="migrated" value="true" />
7
+ <option name="pristineConfig" value="false" />
8
+ <option name="userId" value="-19eae3bc:18ab99784c4:-8000" />
9
+ <option name="version" value="8.13.2" />
10
+ </MTProjectMetadataState>
11
+ </option>
12
+ </component>
13
+ </project>
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Black">
4
+ <option name="sdkName" value="Poetry (pw) (2)" />
5
+ </component>
6
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (django-litestream)" project-jdk-type="Python SDK" />
7
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/django-litestream.iml" filepath="$PROJECT_DIR$/.idea/django-litestream.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,96 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="AutoImportSettings">
4
+ <option name="autoReloadType" value="SELECTIVE" />
5
+ </component>
6
+ <component name="ChangeListManager">
7
+ <list default="true" id="06ad9acc-f715-4fe6-a328-19df77ce9574" name="Changes" comment="">
8
+ <change beforePath="$PROJECT_DIR$/demo/demo/asgi.py" beforeDir="false" afterPath="$PROJECT_DIR$/demo/demo/asgi.py" afterDir="false" />
9
+ <change beforePath="$PROJECT_DIR$/demo/demo/settings.py" beforeDir="false" afterPath="$PROJECT_DIR$/demo/demo/settings.py" afterDir="false" />
10
+ <change beforePath="$PROJECT_DIR$/demo/demo/urls.py" beforeDir="false" afterPath="$PROJECT_DIR$/demo/demo/urls.py" afterDir="false" />
11
+ <change beforePath="$PROJECT_DIR$/demo/demo/wsgi.py" beforeDir="false" afterPath="$PROJECT_DIR$/demo/demo/wsgi.py" afterDir="false" />
12
+ <change beforePath="$PROJECT_DIR$/demo/manage.py" beforeDir="false" afterPath="$PROJECT_DIR$/demo/manage.py" afterDir="false" />
13
+ <change beforePath="$PROJECT_DIR$/pyproject.toml" beforeDir="false" afterPath="$PROJECT_DIR$/pyproject.toml" afterDir="false" />
14
+ </list>
15
+ <option name="SHOW_DIALOG" value="false" />
16
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
17
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
18
+ <option name="LAST_RESOLUTION" value="IGNORE" />
19
+ </component>
20
+ <component name="FileTemplateManagerImpl">
21
+ <option name="RECENT_TEMPLATES">
22
+ <list>
23
+ <option value="Python Script" />
24
+ </list>
25
+ </option>
26
+ </component>
27
+ <component name="Git.Settings">
28
+ <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
29
+ </component>
30
+ <component name="GitHubPullRequestSearchHistory"><![CDATA[{
31
+ "lastFilter": {
32
+ "state": "OPEN",
33
+ "assignee": "Tobi-De"
34
+ }
35
+ }]]></component>
36
+ <component name="GithubPullRequestsUISettings"><![CDATA[{
37
+ "selectedUrlAndAccountId": {
38
+ "url": "git@github.com:Tobi-De/django-litestream.git",
39
+ "accountId": "d7d9e3ed-0d7c-494d-88b6-c724e1fdfa89"
40
+ }
41
+ }]]></component>
42
+ <component name="ProjectColorInfo">{
43
+ &quot;associatedIndex&quot;: 7
44
+ }</component>
45
+ <component name="ProjectId" id="2kSdVmzqHeoA51qcKrsajUb6XaN" />
46
+ <component name="ProjectViewState">
47
+ <option name="hideEmptyMiddlePackages" value="true" />
48
+ <option name="showLibraryContents" value="true" />
49
+ <option name="showMembers" value="true" />
50
+ </component>
51
+ <component name="PropertiesComponent"><![CDATA[{
52
+ "keyToString": {
53
+ "RunOnceActivity.ShowReadmeOnStart": "true",
54
+ "git-widget-placeholder": "main",
55
+ "last_opened_file_path": "/home/tobi/Builds/django-litestream",
56
+ "node.js.detected.package.eslint": "true",
57
+ "node.js.detected.package.tslint": "true",
58
+ "node.js.selected.package.eslint": "(autodetect)",
59
+ "node.js.selected.package.tslint": "(autodetect)",
60
+ "nodejs_package_manager_path": "npm",
61
+ "vue.rearranger.settings.migration": "true"
62
+ }
63
+ }]]></component>
64
+ <component name="RecentsManager">
65
+ <key name="CopyFile.RECENT_KEYS">
66
+ <recent name="$PROJECT_DIR$" />
67
+ </key>
68
+ <key name="MoveFile.RECENT_KEYS">
69
+ <recent name="$PROJECT_DIR$/src/django_litestream" />
70
+ </key>
71
+ </component>
72
+ <component name="SharedIndexes">
73
+ <attachedChunks>
74
+ <set>
75
+ <option value="bundled-js-predefined-1d06a55b98c1-0b3e54e931b4-JavaScript-PY-241.19072.16" />
76
+ <option value="bundled-python-sdk-8336bb23522e-2767605e8bc2-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-241.19072.16" />
77
+ </set>
78
+ </attachedChunks>
79
+ </component>
80
+ <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
81
+ <component name="TaskManager">
82
+ <task active="true" id="Default" summary="Default task">
83
+ <changelist id="06ad9acc-f715-4fe6-a328-19df77ce9574" name="Changes" comment="" />
84
+ <created>1723280938477</created>
85
+ <option name="number" value="Default" />
86
+ <option name="presentableId" value="Default" />
87
+ <updated>1723280938477</updated>
88
+ <workItem from="1723280939699" duration="2190000" />
89
+ <workItem from="1723284486183" duration="20140000" />
90
+ </task>
91
+ <servers />
92
+ </component>
93
+ <component name="TypeScriptGeneratedFilesManager">
94
+ <option name="version" value="3" />
95
+ </component>
96
+ </project>
@@ -0,0 +1,44 @@
1
+ # See https://pre-commit.com for more information
2
+ # See https://pre-commit.com/hooks.html for more hooks
3
+ default_language_version:
4
+ python: python3.11
5
+
6
+ repos:
7
+ - repo: https://github.com/pre-commit/pre-commit-hooks
8
+ rev: v4.6.0
9
+ hooks:
10
+ - id: trailing-whitespace
11
+ - id: end-of-file-fixer
12
+ - id: check-yaml
13
+ - id: check-toml
14
+ - id: check-added-large-files
15
+ - id: check-merge-conflict
16
+ - id: check-case-conflict
17
+ - id: check-symlinks
18
+ - id: check-json
19
+
20
+ - repo: https://github.com/asottile/reorder_python_imports
21
+ rev: v3.13.0
22
+ hooks:
23
+ - id: reorder-python-imports
24
+ args:
25
+ - "--application-directories=src"
26
+
27
+ - repo: https://github.com/adamchainz/django-upgrade
28
+ rev: "1.20.0"
29
+ hooks:
30
+ - id: django-upgrade
31
+ args: [--target-version, "5.0"]
32
+
33
+ - repo: https://github.com/tox-dev/pyproject-fmt
34
+ rev: "2.2.1"
35
+ hooks:
36
+ - id: pyproject-fmt
37
+
38
+ - repo: local
39
+ hooks:
40
+ - id: hatch-fmt
41
+ name: run hatch fmt
42
+ entry: hatch fmt
43
+ language: system
44
+ files: \.py
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-present Tobi DEGNON <tobidegnon@proton.me>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,56 @@
1
+ Metadata-Version: 2.3
2
+ Name: django-litestream
3
+ Version: 0.0.1
4
+ Project-URL: Documentation, https://github.com/Tobi DEGNON/django-litestream#readme
5
+ Project-URL: Issues, https://github.com/Tobi DEGNON/django-litestream/issues
6
+ Project-URL: Source, https://github.com/Tobi DEGNON/django-litestream
7
+ Author-email: Tobi DEGNON <tobidegnon@proton.me>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2024-present Tobi DEGNON <tobidegnon@proton.me>
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
+ License-File: LICENSE.txt
18
+ Classifier: Development Status :: 4 - Beta
19
+ Classifier: Programming Language :: Python
20
+ Classifier: Programming Language :: Python :: 3.8
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: Implementation :: CPython
26
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
27
+ Requires-Python: >=3.8
28
+ Requires-Dist: django>=4.2
29
+ Requires-Dist: litestream-bin
30
+ Requires-Dist: pyyaml
31
+ Description-Content-Type: text/markdown
32
+
33
+ # django-litestream
34
+
35
+ [![PyPI - Version](https://img.shields.io/pypi/v/django-litestream.svg)](https://pypi.org/project/django-litestream)
36
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/django-litestream.svg)](https://pypi.org/project/django-litestream)
37
+
38
+ -----
39
+
40
+ > [!IMPORTANT]
41
+ > This package currently contains minimal features and is a work-in-progress
42
+
43
+ ## Table of Contents
44
+
45
+ - [Installation](#installation)
46
+ - [License](#license)
47
+
48
+ ## Installation
49
+
50
+ ```console
51
+ pip install django-litestream
52
+ ```
53
+
54
+ ## License
55
+
56
+ `django-litestream` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
@@ -0,0 +1,24 @@
1
+ # django-litestream
2
+
3
+ [![PyPI - Version](https://img.shields.io/pypi/v/django-litestream.svg)](https://pypi.org/project/django-litestream)
4
+ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/django-litestream.svg)](https://pypi.org/project/django-litestream)
5
+
6
+ -----
7
+
8
+ > [!IMPORTANT]
9
+ > This package currently contains minimal features and is a work-in-progress
10
+
11
+ ## Table of Contents
12
+
13
+ - [Installation](#installation)
14
+ - [License](#license)
15
+
16
+ ## Installation
17
+
18
+ ```console
19
+ pip install django-litestream
20
+ ```
21
+
22
+ ## License
23
+
24
+ `django-litestream` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license.
Binary file
File without changes
@@ -0,0 +1,15 @@
1
+ """
2
+ ASGI config for demo project.
3
+
4
+ It exposes the ASGI callable as a module-level variable named ``application``.
5
+
6
+ For more information on this file, see
7
+ https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
8
+ """
9
+ import os
10
+
11
+ from django.core.asgi import get_asgi_application
12
+
13
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings")
14
+
15
+ application = get_asgi_application()
@@ -0,0 +1,125 @@
1
+ """
2
+ Django settings for demo project.
3
+
4
+ Generated by 'django-admin startproject' using Django 4.2.7.
5
+
6
+ For more information on this file, see
7
+ https://docs.djangoproject.com/en/4.2/topics/settings/
8
+
9
+ For the full list of settings and their values, see
10
+ https://docs.djangoproject.com/en/4.2/ref/settings/
11
+ """
12
+ from pathlib import Path
13
+
14
+ # Build paths inside the project like this: BASE_DIR / 'subdir'.
15
+ BASE_DIR = Path(__file__).resolve().parent.parent
16
+
17
+
18
+ # Quick-start development settings - unsuitable for production
19
+ # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
20
+
21
+ # SECURITY WARNING: keep the secret key used in production secret!
22
+ SECRET_KEY = "django-insecure-1fdgnt7ds_49e#85r6i)b45hhg!-ows-n)cs_*m_^__%88^02c"
23
+
24
+ # SECURITY WARNING: don't run with debug turned on in production!
25
+ DEBUG = True
26
+
27
+ ALLOWED_HOSTS = []
28
+
29
+
30
+ # Application definition
31
+
32
+ INSTALLED_APPS = [
33
+ "django.contrib.admin",
34
+ "django.contrib.auth",
35
+ "django.contrib.contenttypes",
36
+ "django.contrib.sessions",
37
+ "django.contrib.messages",
38
+ "django.contrib.staticfiles",
39
+ "django_litestream",
40
+ ]
41
+
42
+ LITESTREAM = {"config_file": BASE_DIR / "litestream.yml"}
43
+
44
+ MIDDLEWARE = [
45
+ "django.middleware.security.SecurityMiddleware",
46
+ "django.contrib.sessions.middleware.SessionMiddleware",
47
+ "django.middleware.common.CommonMiddleware",
48
+ "django.middleware.csrf.CsrfViewMiddleware",
49
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
50
+ "django.contrib.messages.middleware.MessageMiddleware",
51
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
52
+ ]
53
+
54
+ ROOT_URLCONF = "demo.urls"
55
+
56
+ TEMPLATES = [
57
+ {
58
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
59
+ "DIRS": [],
60
+ "APP_DIRS": True,
61
+ "OPTIONS": {
62
+ "context_processors": [
63
+ "django.template.context_processors.debug",
64
+ "django.template.context_processors.request",
65
+ "django.contrib.auth.context_processors.auth",
66
+ "django.contrib.messages.context_processors.messages",
67
+ ],
68
+ },
69
+ },
70
+ ]
71
+
72
+ WSGI_APPLICATION = "demo.wsgi.application"
73
+
74
+
75
+ # Database
76
+ # https://docs.djangoproject.com/en/4.2/ref/settings/#databases
77
+
78
+ DATABASES = {
79
+ "default": {
80
+ "ENGINE": "django.db.backends.sqlite3",
81
+ "NAME": BASE_DIR / "db.sqlite3",
82
+ }
83
+ }
84
+
85
+
86
+ # Password validation
87
+ # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
88
+
89
+ AUTH_PASSWORD_VALIDATORS = [
90
+ {
91
+ "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
92
+ },
93
+ {
94
+ "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
95
+ },
96
+ {
97
+ "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
98
+ },
99
+ {
100
+ "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
101
+ },
102
+ ]
103
+
104
+
105
+ # Internationalization
106
+ # https://docs.djangoproject.com/en/4.2/topics/i18n/
107
+
108
+ LANGUAGE_CODE = "en-us"
109
+
110
+ TIME_ZONE = "UTC"
111
+
112
+ USE_I18N = True
113
+
114
+ USE_TZ = True
115
+
116
+
117
+ # Static files (CSS, JavaScript, Images)
118
+ # https://docs.djangoproject.com/en/4.2/howto/static-files/
119
+
120
+ STATIC_URL = "static/"
121
+
122
+ # Default primary key field type
123
+ # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
124
+
125
+ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
@@ -0,0 +1,22 @@
1
+ """
2
+ URL configuration for demo project.
3
+
4
+ The `urlpatterns` list routes URLs to views. For more information please see:
5
+ https://docs.djangoproject.com/en/4.2/topics/http/urls/
6
+ Examples:
7
+ Function views
8
+ 1. Add an import: from my_app import views
9
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
10
+ Class-based views
11
+ 1. Add an import: from other_app.views import Home
12
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
13
+ Including another URLconf
14
+ 1. Import the include() function: from django.urls import include, path
15
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
16
+ """
17
+ from django.contrib import admin
18
+ from django.urls import path
19
+
20
+ urlpatterns = [
21
+ path("admin/", admin.site.urls),
22
+ ]
@@ -0,0 +1,15 @@
1
+ """
2
+ WSGI config for demo project.
3
+
4
+ It exposes the WSGI callable as a module-level variable named ``application``.
5
+
6
+ For more information on this file, see
7
+ https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
8
+ """
9
+ import os
10
+
11
+ from django.core.wsgi import get_wsgi_application
12
+
13
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings")
14
+
15
+ application = get_wsgi_application()
@@ -0,0 +1,8 @@
1
+ dbs:
2
+ - path: /home/tobi/Builds/django-litestream/demo/db.sqlite3
3
+ replicas:
4
+ - type: s3
5
+ bucket: $LITESTREAM_REPLICA_BUCKET
6
+ path: db.sqlite3
7
+ access-key-id: $LITESTREAM_ACCESS_KEY_ID
8
+ secret-access-key: $LITESTREAM_SECRET_ACCESS_KEY
@@ -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()
@@ -0,0 +1,16 @@
1
+ set dotenv-load := true
2
+
3
+ # List all available commands
4
+ _default:
5
+ @just --list --unsorted
6
+
7
+ @fmt:
8
+ hatch fmt --formatter
9
+ just --fmt --unstable
10
+ hatch run pre-commit run reorder-python-imports -a
11
+
12
+ @dj *ARGS:
13
+ cd demo && hatch run python manage.py {{ ARGS }}
14
+
15
+ @run-demo:
16
+ cd demo && hatch run python manage.py runserver
@@ -0,0 +1,67 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "django-litestream"
7
+ dynamic = ["version"]
8
+ description = ''
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = {file = "LICENSE.txt"}
12
+ keywords = []
13
+ authors = [
14
+ { name = "Tobi DEGNON", email = "tobidegnon@proton.me" },
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Programming Language :: Python",
19
+ "Programming Language :: Python :: 3.8",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: Implementation :: CPython",
25
+ "Programming Language :: Python :: Implementation :: PyPy",
26
+ ]
27
+ dependencies = ["django>=4.2", "litestream-bin", "pyyaml"]
28
+
29
+ [project.urls]
30
+ Documentation = "https://github.com/Tobi DEGNON/django-litestream#readme"
31
+ Issues = "https://github.com/Tobi DEGNON/django-litestream/issues"
32
+ Source = "https://github.com/Tobi DEGNON/django-litestream"
33
+
34
+ [tool.hatch.version]
35
+ path = "src/django_litestream/__about__.py"
36
+
37
+ [tool.hatch.envs.default]
38
+ dependencies = [
39
+ "pre-commit",
40
+ "reorder-python-imports",
41
+ ]
42
+
43
+ [tool.hatch.envs.types]
44
+ extra-dependencies = [
45
+ "mypy>=1.0.0",
46
+ ]
47
+ [tool.hatch.envs.types.scripts]
48
+ check = "mypy --install-types --non-interactive {args:src/django_litestream tests}"
49
+
50
+ [tool.coverage.run]
51
+ source_pkgs = ["django_litestream", "tests"]
52
+ branch = true
53
+ parallel = true
54
+ omit = [
55
+ "src/django_litestream/__about__.py",
56
+ ]
57
+
58
+ [tool.coverage.paths]
59
+ django_litestream = ["src/django_litestream", "*/django-litestream/src/django_litestream"]
60
+ tests = ["tests", "*/django-litestream/tests"]
61
+
62
+ [tool.coverage.report]
63
+ exclude_lines = [
64
+ "no cov",
65
+ "if __name__ == .__main__.:",
66
+ "if TYPE_CHECKING:",
67
+ ]
@@ -0,0 +1,4 @@
1
+ # SPDX-FileCopyrightText: 2024-present Tobi DEGNON <tobidegnon@proton.me>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ __version__ = "0.0.1"
@@ -0,0 +1,3 @@
1
+ # SPDX-FileCopyrightText: 2024-present Tobi DEGNON <tobidegnon@proton.me>
2
+ #
3
+ # SPDX-License-Identifier: MIT
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DjangoLitestreamConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "django_litestream"
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+ from django.conf import settings
8
+
9
+ if sys.version_info >= (3, 12):
10
+ from typing import override as typing_override
11
+ else: # pragma: no cover
12
+ from typing_extensions import (
13
+ override as typing_override, # pyright: ignore[reportUnreachable]
14
+ )
15
+
16
+ override = typing_override
17
+
18
+
19
+ DJANGO_LITESTREAM_SETTINGS_NAME = "LITESTREAM"
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class AppSettings:
24
+ config_file: Path | str = "/etc/litestream.yml"
25
+ bin_path: Path | str = "litestream"
26
+ dbs: list[dict[str, str]] = None
27
+ extra_dbs: list[dict[str, str]] = None
28
+
29
+ @override
30
+ def __getattribute__(self, __name: str) -> object:
31
+ user_settings = getattr(settings, DJANGO_LITESTREAM_SETTINGS_NAME, {})
32
+ return user_settings.get(__name, super().__getattribute__(__name)) # pyright: ignore[reportAny]
33
+
34
+
35
+ app_settings = AppSettings()
@@ -0,0 +1,40 @@
1
+ def _get_sqlite_db_path() -> str:
2
+ """Return the path to the SQLite database file."""
3
+ from django.conf import settings
4
+
5
+ db_settings = settings.DATABASES.get("default")
6
+ if db_settings and db_settings.get("ENGINE") == "django.db.backends.sqlite3":
7
+ return db_settings["NAME"]
8
+ exit("No SQLite database found in settings")
9
+
10
+
11
+ def run_setup(_):
12
+ """Run some project setup tasks"""
13
+ import os
14
+ import subprocess
15
+ from pathlib import Path
16
+ from django.core.management import execute_from_command_line
17
+ from django.core.management.base import CommandError
18
+ from contextlib import suppress
19
+
20
+ db_path = _get_sqlite_db_path()
21
+ # The Litestream configuration uses this environment variable, so it needs
22
+ # to be injected into every function that runs the Litestream command.
23
+ os.environ.setdefault("DATABASE_PATH", db_path)
24
+
25
+ replica_url = os.getenv("REPLICA_URL")
26
+
27
+ if not replica_url:
28
+ exit("REPLICA_URL environment variable not set")
29
+
30
+ if Path(db_path).exists():
31
+ print("Database already exists, skipping restore")
32
+ else:
33
+ print("No database found, restoring from replica if exists")
34
+ subprocess.run(["litestream", "restore", "-if-replica-exists", "-o", db_path, replica_url])
35
+
36
+ execute_from_command_line(["manage", "migrate"])
37
+ execute_from_command_line(["manage", "setup_periodic_tasks"])
38
+
39
+ with suppress(CommandError):
40
+ execute_from_command_line(["manage", "createsuperuser", "--noinput", "--traceback"])
@@ -0,0 +1,233 @@
1
+ import subprocess
2
+ import sys
3
+ from argparse import ArgumentParser
4
+ from pathlib import Path
5
+
6
+ from django.conf import settings
7
+ from django.core.management import BaseCommand
8
+ from yaml import dump
9
+
10
+ from ...conf import app_settings
11
+
12
+ CONFIG_ARG = {
13
+ "name": "-config",
14
+ "type": Path,
15
+ "help": f"Path to the litestream configuration file, default: {app_settings.config_file}",
16
+ "default": app_settings.config_file,
17
+ "required": False,
18
+ }
19
+
20
+ DB_PATH_ARG = {
21
+ "name": "db_path",
22
+ "type": Path,
23
+ "help": "Path to the SQLite database file",
24
+ }
25
+
26
+ DB_PATH_OR_REPLICA_URL_ARG = {
27
+ "name": "db_path",
28
+ "help": "Path to the SQLite database file or replica URL",
29
+ }
30
+
31
+ NO_EXPAND_ENV_ARG = {
32
+ "name": "-no-expand-env",
33
+ "action": "store_true",
34
+ "help": "Disables environment variable expansion in configuration file.",
35
+ "required": False,
36
+ }
37
+
38
+
39
+ def _get_replica_arg(subcommand: str) -> dict:
40
+ return {
41
+ "name": "-replica",
42
+ "help": f"Optional, filters by replica. Only applies when listing database {subcommand}.",
43
+ "required": False,
44
+ }
45
+
46
+
47
+ litestream_commands = {
48
+ "databases": {
49
+ "description": "List databases specified in config file",
50
+ "arguments": [CONFIG_ARG, NO_EXPAND_ENV_ARG],
51
+ },
52
+ "generations": {
53
+ "description": "List available generations for a database",
54
+ "arguments": [
55
+ CONFIG_ARG,
56
+ NO_EXPAND_ENV_ARG,
57
+ DB_PATH_OR_REPLICA_URL_ARG,
58
+ _get_replica_arg("generations"),
59
+ ],
60
+ },
61
+ "replicate": {
62
+ "description": "Runs a server to replicate databases",
63
+ "arguments": [
64
+ CONFIG_ARG,
65
+ NO_EXPAND_ENV_ARG,
66
+ {
67
+ "name": "-exec",
68
+ "help": "Executes a subcommand. Litestream will exit when the child process exits. "
69
+ "Useful for simple process management.",
70
+ "required": False,
71
+ },
72
+ # {
73
+ # **DB_PATH_ARG,
74
+ # },
75
+ # {"name": "replica_url", "help": "URL of the replicas", "action": "append"}
76
+ ],
77
+ },
78
+ "restore": {
79
+ "description": "Recovers database backup from a replica",
80
+ "arguments": [
81
+ DB_PATH_OR_REPLICA_URL_ARG,
82
+ CONFIG_ARG,
83
+ NO_EXPAND_ENV_ARG,
84
+ {
85
+ "name": "-o",
86
+ "type": Path,
87
+ "help": "Output path of the restored database. Defaults to original DB path.",
88
+ "required": False,
89
+ },
90
+ {
91
+ "name": "-if-replica-exists",
92
+ "action": "store_true",
93
+ "help": "Returns exit code of 0 if no backups found.",
94
+ "required": False,
95
+ },
96
+ {
97
+ "name": "-if-db-not-exists",
98
+ "action": "store_true",
99
+ "help": "Returns exit code of 0 if the database already exists.",
100
+ "required": False,
101
+ },
102
+ {
103
+ "name": "-parallelism",
104
+ "type": int,
105
+ "help": "Determines the number of WAL files downloaded in parallel. Defaults to 8",
106
+ "default": 8,
107
+ "required": False,
108
+ },
109
+ {
110
+ "name": "-generation",
111
+ "help": "Restore from a specific generation. Defaults to generation with latest data",
112
+ "required": False,
113
+ },
114
+ {
115
+ "name": "-index",
116
+ "type": int,
117
+ "help": "Restore up to a specific WAL index (inclusive). Defaults to use the highest available index.",
118
+ "required": False,
119
+ },
120
+ {
121
+ "name": "-timestamp",
122
+ # "type": datetime,
123
+ "help": "Restore to a specific point-in-time. Defaults to use the latest available backup.",
124
+ "required": False,
125
+ },
126
+ ],
127
+ },
128
+ "snapshots": {
129
+ "description": "List available snapshots for a database",
130
+ "arguments": [
131
+ DB_PATH_OR_REPLICA_URL_ARG,
132
+ CONFIG_ARG,
133
+ NO_EXPAND_ENV_ARG,
134
+ _get_replica_arg("snapshots"),
135
+ ],
136
+ },
137
+ "version": {"description": "Prints the binary version", "arguments": []},
138
+ "wal": {
139
+ "description": "List available WAL files for a database",
140
+ "arguments": [
141
+ CONFIG_ARG,
142
+ NO_EXPAND_ENV_ARG,
143
+ {
144
+ "name": "-generation",
145
+ "help": "Optional, filter by a specific generation.",
146
+ "required": False,
147
+ },
148
+ _get_replica_arg("snapshots"),
149
+ ],
150
+ },
151
+ }
152
+
153
+
154
+ class Command(BaseCommand):
155
+ help = "Litestream is a tool for replicating SQLite databases."
156
+
157
+ def add_arguments(self, parser: ArgumentParser) -> None:
158
+ subcommands = parser.add_subparsers(help="subcommands", dest="subcommand")
159
+ init_cmd = subcommands.add_parser(
160
+ "init",
161
+ help="Initialize a new Litestream configuration",
162
+ description="Initialize a new Litestream configuration",
163
+ )
164
+
165
+ _add_argument(init_cmd, CONFIG_ARG)
166
+
167
+ for ls_cmd, details in litestream_commands.items():
168
+ parser = subcommands.add_parser(
169
+ ls_cmd,
170
+ help=details["description"],
171
+ description=details["description"],
172
+ )
173
+ for args in details["arguments"]:
174
+ _add_argument(parser, args)
175
+
176
+ def handle(self, *args, **options) -> None:
177
+ if options["subcommand"] == "init":
178
+ _init(filepath=options["config"])
179
+ self.stdout.write(self.style.SUCCESS("Litestream configuration file created"))
180
+ elif len(sys.argv) == 2:
181
+ self.print_help("manage", "litestream")
182
+ else:
183
+ ls_args = sys.argv[2:]
184
+ if "db_path" in options:
185
+ # if database alias specified, replace it by the file location
186
+ original_value = options["db_path"]
187
+ db_path = _db_location_from_alias(original_value)
188
+ index_original_value = ls_args.index(original_value)
189
+ ls_args[index_original_value] = db_path
190
+ if "-config" not in ls_args and options["subcommand"] != "version":
191
+ ls_args.extend(["-config", str(options["config"])])
192
+ # print(ls_args)
193
+ subprocess.run([app_settings.bin_path, *ls_args])
194
+
195
+
196
+ def _db_location_from_alias(alias: str) -> str:
197
+ db_settings = settings.DATABASES.get(alias, {})
198
+ if db_settings.get("ENGINE") == "django.db.backends.sqlite3":
199
+ return db_settings["NAME"]
200
+ return alias
201
+
202
+
203
+ def _init(filepath: Path):
204
+ if app_settings.dbs:
205
+ dbs = app_settings.dbs
206
+ else:
207
+ dbs = []
208
+ for db_settings in settings.DATABASES.values():
209
+ if db_settings["ENGINE"] == "django.db.backends.sqlite3":
210
+ location = str(db_settings["NAME"])
211
+ dbs.append(
212
+ {
213
+ "path": location,
214
+ "replicas": [
215
+ {
216
+ "type": "s3",
217
+ "bucket": "$LITESTREAM_REPLICA_BUCKET",
218
+ "path": Path(location).name,
219
+ "access-key-id": "$LITESTREAM_ACCESS_KEY_ID",
220
+ "secret-access-key": "$LITESTREAM_SECRET_ACCESS_KEY",
221
+ }
222
+ ],
223
+ }
224
+ )
225
+ if app_settings.extra_dbs:
226
+ dbs.extend(app_settings.extra_dbs)
227
+ with open(filepath, "w") as f:
228
+ dump({"dbs": dbs}, f, sort_keys=False)
229
+
230
+
231
+ def _add_argument(parser: ArgumentParser, args: dict) -> None:
232
+ copied_args = args.copy()
233
+ parser.add_argument(copied_args.pop("name"), **copied_args)
@@ -0,0 +1,3 @@
1
+ # SPDX-FileCopyrightText: 2024-present Tobi DEGNON <tobidegnon@proton.me>
2
+ #
3
+ # SPDX-License-Identifier: MIT