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.
- django_litestream-0.0.1/.idea/django-litestream.iml +10 -0
- django_litestream-0.0.1/.idea/inspectionProfiles/Project_Default.xml +68 -0
- django_litestream-0.0.1/.idea/inspectionProfiles/profiles_settings.xml +6 -0
- django_litestream-0.0.1/.idea/material_theme_project_new.xml +13 -0
- django_litestream-0.0.1/.idea/misc.xml +7 -0
- django_litestream-0.0.1/.idea/modules.xml +8 -0
- django_litestream-0.0.1/.idea/vcs.xml +6 -0
- django_litestream-0.0.1/.idea/workspace.xml +96 -0
- django_litestream-0.0.1/.pre-commit-config.yaml +44 -0
- django_litestream-0.0.1/LICENSE.txt +9 -0
- django_litestream-0.0.1/PKG-INFO +56 -0
- django_litestream-0.0.1/README.md +24 -0
- django_litestream-0.0.1/demo/db.sqlite3 +0 -0
- django_litestream-0.0.1/demo/demo/__init__.py +0 -0
- django_litestream-0.0.1/demo/demo/asgi.py +15 -0
- django_litestream-0.0.1/demo/demo/settings.py +125 -0
- django_litestream-0.0.1/demo/demo/urls.py +22 -0
- django_litestream-0.0.1/demo/demo/wsgi.py +15 -0
- django_litestream-0.0.1/demo/litestream.yml +8 -0
- django_litestream-0.0.1/demo/manage.py +22 -0
- django_litestream-0.0.1/justfile +16 -0
- django_litestream-0.0.1/pyproject.toml +67 -0
- django_litestream-0.0.1/src/django_litestream/__about__.py +4 -0
- django_litestream-0.0.1/src/django_litestream/__init__.py +3 -0
- django_litestream-0.0.1/src/django_litestream/apps.py +6 -0
- django_litestream-0.0.1/src/django_litestream/conf.py +35 -0
- django_litestream-0.0.1/src/django_litestream/management/__init__.py +40 -0
- django_litestream-0.0.1/src/django_litestream/management/commands/__init__.py +0 -0
- django_litestream-0.0.1/src/django_litestream/management/commands/litestream.py +233 -0
- 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,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,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
|
+
"associatedIndex": 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
|
+
[](https://pypi.org/project/django-litestream)
|
|
36
|
+
[](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
|
+
[](https://pypi.org/project/django-litestream)
|
|
4
|
+
[](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,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,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"])
|
|
File without changes
|
|
@@ -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)
|