Skip to content

Commit

Permalink
Merge branch 'master' into fix-478
Browse files Browse the repository at this point in the history
  • Loading branch information
Archmonger authored Aug 23, 2024
2 parents 5d27e34 + d8f09bd commit 270eb5f
Show file tree
Hide file tree
Showing 32 changed files with 430 additions and 264 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ jobs:
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand All @@ -33,6 +33,6 @@ jobs:
- name: Test with tox
run: tox -r
- name: Upload coverage
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v4
with:
name: Python ${{ matrix.python-version }} Codecov
64 changes: 39 additions & 25 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: Publish PyPI Release
name: Publish PyPI Release (BROKEN)

on:
release:
Expand All @@ -10,27 +10,41 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: "3.x"

- name: Install dependencies
run: |
python -m pip install -U pip
python -m pip install -r requirements/build.txt
python -m pip install -r requirements.txt
- name: Build package
run: |
python -m build --sdist --wheel --outdir dist .
twine check dist/*
- name: Upload packages to Jazzband
uses: pypa/gh-action-pypi-publish@master
with:
user: jazzband
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
repository_url: https://jazzband.co/projects/django-dbbackup/upload
- uses: actions/checkout@v4
# - name: Set up Python
# uses: actions/setup-python@v5
# with:
# python-version: "3.x"

# - name: Install dependencies
# run: |
# python -m pip install -U pip
# python -m pip install -U -r requirements/build.txt
# python -m pip install -U -r requirements.txt

# - name: Build package
# run: |
# python -m build --sdist --wheel --outdir dist .
# twine check dist/*

# - name: Upload packages to Jazzband
# uses: pypa/gh-action-pypi-publish@release/v1
# with:
# user: jazzband
# password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
# repository-url: https://jazzband.co/projects/django-dbbackup/upload

# This workflopw is disabled due to the following error:
#
# ERROR HTTPError: 500 Internal Server Error from
# https://jazzband.co/projects/django-dbbackup/upload
# INTERNAL SERVER ERROR

# Until we are transfered out of Jazzband, the workaround is releasing manually via personal PyPI accounts.
# The following steps are required to release a new version of the package:
# python -m pip install -U pip
# python -m pip install -U -r requirements/build.txt
# python -m pip install -U -r requirements.txt
# python -m build --sdist --wheel --outdir dist .
# twine check dist/*
# twine upload dist/*
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: "v4.5.0"
rev: "v4.6.0"
hooks:
- id: check-merge-conflict
- id: end-of-file-fixer
Expand Down Expand Up @@ -38,20 +38,20 @@ repos:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/psf/black
rev: "24.3.0"
rev: "24.8.0"
hooks:
- id: black
- repo: https://github.com/asottile/pyupgrade
rev: "v3.15.2"
rev: "v3.17.0"
hooks:
- id: pyupgrade
args: ["--py37-plus", "--keep-mock"]
- repo: https://github.com/hhatto/autopep8
rev: "v2.1.0"
rev: "v2.3.1"
hooks:
- id: autopep8
- repo: https://github.com/PyCQA/flake8
rev: "7.0.0"
rev: "7.1.1"
hooks:
- id: flake8
exclude: "^docs/"
Expand Down
34 changes: 34 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.12"
# You can also specify other tool versions:
# nodejs: "20"
# rust: "1.70"
# golang: "1.20"

# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
# builder: "dirhtml"
# Fail on all warnings to avoid broken references
# fail_on_warning: true
# Optionally build your docs in additional formats such as PDF and ePub
# formats:
# - pdf
# - epub

# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: requirements/docs.txt
2 changes: 1 addition & 1 deletion dbbackup/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.1.0
4.2.1
43 changes: 43 additions & 0 deletions dbbackup/checks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
from datetime import datetime

from django.core.checks import Tags, Warning, register

Expand Down Expand Up @@ -35,6 +36,46 @@
"settings.DBBACKUP_ADMINS",
id="dbbackup.W006",
)
W007 = Warning(
"Invalid FILENAME_TEMPLATE parameter",
hint="settings.DBBACKUP_FILENAME_TEMPLATE must not contain slashes ('/'). "
"Did you mean to change the value for 'location'?",
id="dbbackup.W007",
)
W008 = Warning(
"Invalid MEDIA_FILENAME_TEMPLATE parameter",
hint="settings.DBBACKUP_MEDIA_FILENAME_TEMPLATE must not contain slashes ('/')"
"Did you mean to change the value for 'location'?",
id="dbbackup.W007",
)


def check_filename_templates():
return _check_filename_template(
settings.FILENAME_TEMPLATE,
W007,
"db",
) + _check_filename_template(
settings.MEDIA_FILENAME_TEMPLATE,
W008,
"media",
)


def _check_filename_template(filename_template, check_code, content_type) -> list:
if callable(filename_template):
params = {
"servername": "localhost",
"datetime": datetime.now().strftime(settings.DATE_FORMAT),
"databasename": "default",
"extension": "dump",
"content_type": content_type,
}
filename_template = filename_template(params)

if "/" in filename_template:
return [check_code]
return []


@register(Tags.compatibility)
Expand Down Expand Up @@ -64,4 +105,6 @@ def check_settings(app_configs, **kwargs):
if getattr(settings, "FAILURE_RECIPIENTS", None) is not None:
errors.append(W006)

errors += check_filename_templates()

return errors
6 changes: 1 addition & 5 deletions dbbackup/db/postgresql.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,12 @@
from urllib.parse import quote

from .base import BaseCommandDBConnector
from .exceptions import DumpError

logger = logging.getLogger("dbbackup.command")


def create_postgres_uri(self):
host = self.settings.get("HOST")
if not host:
raise DumpError("A host name is required")

host = self.settings.get("HOST") or "localhost"
dbname = self.settings.get("NAME") or ""
user = quote(self.settings.get("USER") or "")
password = self.settings.get("PASSWORD") or ""
Expand Down
20 changes: 16 additions & 4 deletions dbbackup/db/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,23 @@ def restore_dump(self, dump):
if not self.connection.is_usable():
self.connection.connect()
cursor = self.connection.cursor()
sql_command = b""
sql_is_complete = True
for line in dump.readlines():
try:
cursor.execute(line.decode("UTF-8"))
except (OperationalError, IntegrityError) as err:
warnings.warn(f"Error in db restore: {err}")
sql_command = sql_command + line
line_str = line.decode("UTF-8")
if line_str.startswith("INSERT") and not line_str.endswith(");\n"):
sql_is_complete = False
continue
if not sql_is_complete and line_str.endswith(");\n"):
sql_is_complete = True

if sql_is_complete:
try:
cursor.execute(sql_command.decode("UTF-8"))
except (OperationalError, IntegrityError) as err:
warnings.warn(f"Error in db restore: {err}")
sql_command = b""


class SqliteCPConnector(BaseDBConnector):
Expand Down
12 changes: 10 additions & 2 deletions dbbackup/management/commands/dbrestore.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
class Command(BaseDbBackupCommand):
help = "Restore a database backup from storage, encrypted and/or compressed."
content_type = "db"
no_drop = False

option_list = BaseDbBackupCommand.option_list + (
make_option("-d", "--database", help="Database to restore"),
Expand Down Expand Up @@ -52,6 +53,13 @@ class Command(BaseDbBackupCommand):
default=[],
help="Specify schema(s) to restore. Can be used multiple times.",
),
make_option(
"-r",
"--no-drop",
action="store_true",
default=False,
help="Don't clean (drop) the database. This only works with mongodb and postgresql.",
),
)

def handle(self, *args, **options):
Expand All @@ -74,6 +82,7 @@ def handle(self, *args, **options):
self.input_database_name
)
self.storage = get_storage()
self.no_drop = options.get("no_drop")
self.schemas = options.get("schema")
self._restore_backup()
except StorageError as err:
Expand Down Expand Up @@ -129,8 +138,7 @@ def _restore_backup(self):

input_file.seek(0)
self.connector = get_connector(self.database_name)

if self.schemas:
self.connector.schemas = self.schemas

self.connector.restore_dump(input_file)
self.connector.drop = not self.no_drop
3 changes: 1 addition & 2 deletions dbbackup/management/commands/mediabackup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
import os
import tarfile

from django.core.files.storage import get_storage_class
from django.core.management.base import CommandError

from ... import utils
from ...storage import StorageError, get_storage
from ...storage import StorageError, get_storage, get_storage_class
from ._base import BaseDbBackupCommand, make_option


Expand Down
4 changes: 1 addition & 3 deletions dbbackup/management/commands/mediarestore.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@

import tarfile

from django.core.files.storage import get_storage_class

from ... import utils
from ...storage import get_storage
from ...storage import get_storage, get_storage_class
from ._base import BaseDbBackupCommand, make_option


Expand Down
16 changes: 13 additions & 3 deletions dbbackup/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,20 @@
GPG_ALWAYS_TRUST = getattr(settings, "DBBACKUP_GPG_ALWAYS_TRUST", False)
GPG_RECIPIENT = GPG_ALWAYS_TRUST = getattr(settings, "DBBACKUP_GPG_RECIPIENT", None)

STORAGE = getattr(
settings, "DBBACKUP_STORAGE", "django.core.files.storage.FileSystemStorage"
)
STORAGE = getattr(settings, "DBBACKUP_STORAGE", None)
STORAGE_OPTIONS = getattr(settings, "DBBACKUP_STORAGE_OPTIONS", {})
# https://docs.djangoproject.com/en/5.1/ref/settings/#std-setting-STORAGES
STORAGES_DBBACKUP_ALIAS = "dbbackup"
DJANGO_STORAGES = getattr(settings, "STORAGES", {})
django_dbbackup_storage = DJANGO_STORAGES.get(STORAGES_DBBACKUP_ALIAS, {})

if not STORAGE:
STORAGE = (
django_dbbackup_storage.get("BACKEND")
or "django.core.files.storage.FileSystemStorage"
)
if not STORAGE_OPTIONS:
STORAGE_OPTIONS = django_dbbackup_storage.get("OPTIONS") or STORAGE_OPTIONS

CONNECTORS = getattr(settings, "DBBACKUP_CONNECTORS", {})

Expand Down
28 changes: 27 additions & 1 deletion dbbackup/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import logging

from django.core.exceptions import ImproperlyConfigured
from django.core.files.storage import get_storage_class

from . import settings, utils

Expand Down Expand Up @@ -285,3 +284,30 @@ def clean_old_backups(
if keep_filter(filename):
continue
self.delete_file(filename)


def get_storage_class(path=None):
"""
Return the configured storage class.
:param path: Path in Python dot style to module containing the storage
class. If empty, the default storage class will be used.
:type path: str or None
:returns: Storage class
:rtype: :class:`django.core.files.storage.Storage`
"""
from django.utils.module_loading import import_string

if path:
# this is a workaround to keep compatibility with Django >= 5.1 (django.core.files.storage.get_storage_class is removed)
return import_string(path)

try:
from django.core.files.storage import DefaultStorage

return DefaultStorage
except Exception:
from django.core.files.storage import get_storage_class

return get_storage_class()
Loading

0 comments on commit 270eb5f

Please sign in to comment.