Skip to content

Commit

Permalink
fix tests
Browse files Browse the repository at this point in the history
add suggested vscode extensions

Revert "misc fixes"

This reverts commit 4b59021.

[pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

remove trailing comma

fix flake8 warnings

fix autofield warning

Revert "datestring fix"

This reverts commit 9f6232d.

test datestring thing once more

add black to precommit

[pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci
  • Loading branch information
Archmonger committed Apr 29, 2022
1 parent 3f48f9d commit 388cab2
Show file tree
Hide file tree
Showing 42 changed files with 1,463 additions and 1,062 deletions.
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ repos:
hooks:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/psf/black
rev: "22.3.0"
hooks:
- id: black
- repo: https://github.com/asottile/pyupgrade
rev: "v2.31.1"
hooks:
Expand Down
12 changes: 12 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"recommendations": [
"eamodio.gitlens",
"github.vscode-pull-request-github",
"knisterpeter.vscode-github",
"esbenp.prettier-vscode",
"ms-python.vscode-pylance",
"ms-python.python",
"gruntfuggly.todo-tree",
"sourcery.sourcery"
]
}
2 changes: 1 addition & 1 deletion dbbackup/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
import django

if django.VERSION < (3, 2):
default_app_config = 'dbbackup.apps.DbbackupConfig'
default_app_config = "dbbackup.apps.DbbackupConfig"
8 changes: 5 additions & 3 deletions dbbackup/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ class DbbackupConfig(AppConfig):
"""
Config for DBBackup application.
"""
name = 'dbbackup'
label = 'dbbackup'
verbose_name = gettext_lazy('Backup and restore')

name = "dbbackup"
label = "dbbackup"
verbose_name = gettext_lazy("Backup and restore")
default_auto_field = "django.db.models.AutoField"

def ready(self):
log.load()
64 changes: 41 additions & 23 deletions dbbackup/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,37 @@

from dbbackup import settings

W001 = Warning('Invalid HOSTNAME parameter',
hint='Set a non empty string to this settings.DBBACKUP_HOSTNAME',
id='dbbackup.W001')
W002 = Warning('Invalid STORAGE parameter',
hint='Set a valid path to a storage in settings.DBBACKUP_STORAGE',
id='dbbackup.W002')
W003 = Warning('Invalid FILENAME_TEMPLATE parameter',
hint='Include {datetime} to settings.DBBACKUP_FILENAME_TEMPLATE',
id='dbbackup.W003')
W004 = Warning('Invalid MEDIA_FILENAME_TEMPLATE parameter',
hint='Include {datetime} to settings.DBBACKUP_MEDIA_FILENAME_TEMPLATE',
id='dbbackup.W004')
W005 = Warning('Invalid DATE_FORMAT parameter',
hint='settings.DBBACKUP_DATE_FORMAT can contain only [A-Za-z0-9%_-]',
id='dbbackup.W005')
W006 = Warning('FAILURE_RECIPIENTS has been deprecated',
hint='settings.DBBACKUP_FAILURE_RECIPIENTS is replaced by '
'settings.DBBACKUP_ADMINS',
id='dbbackup.W006')
W001 = Warning(
"Invalid HOSTNAME parameter",
hint="Set a non empty string to this settings.DBBACKUP_HOSTNAME",
id="dbbackup.W001",
)
W002 = Warning(
"Invalid STORAGE parameter",
hint="Set a valid path to a storage in settings.DBBACKUP_STORAGE",
id="dbbackup.W002",
)
W003 = Warning(
"Invalid FILENAME_TEMPLATE parameter",
hint="Include {datetime} to settings.DBBACKUP_FILENAME_TEMPLATE",
id="dbbackup.W003",
)
W004 = Warning(
"Invalid MEDIA_FILENAME_TEMPLATE parameter",
hint="Include {datetime} to settings.DBBACKUP_MEDIA_FILENAME_TEMPLATE",
id="dbbackup.W004",
)
W005 = Warning(
"Invalid DATE_FORMAT parameter",
hint="settings.DBBACKUP_DATE_FORMAT can contain only [A-Za-z0-9%_-]",
id="dbbackup.W005",
)
W006 = Warning(
"FAILURE_RECIPIENTS has been deprecated",
hint="settings.DBBACKUP_FAILURE_RECIPIENTS is replaced by "
"settings.DBBACKUP_ADMINS",
id="dbbackup.W006",
)


@register(Tags.compatibility)
Expand All @@ -34,16 +46,22 @@ def check_settings(app_configs, **kwargs):
if not settings.STORAGE or not isinstance(settings.STORAGE, str):
errors.append(W002)

if not callable(settings.FILENAME_TEMPLATE) and '{datetime}' not in settings.FILENAME_TEMPLATE:
if (
not callable(settings.FILENAME_TEMPLATE)
and "{datetime}" not in settings.FILENAME_TEMPLATE
):
errors.append(W003)

if not callable(settings.MEDIA_FILENAME_TEMPLATE) and '{datetime}' not in settings.MEDIA_FILENAME_TEMPLATE:
if (
not callable(settings.MEDIA_FILENAME_TEMPLATE)
and "{datetime}" not in settings.MEDIA_FILENAME_TEMPLATE
):
errors.append(W004)

if re.search(r'[^A-Za-z0-9%_-]', settings.DATE_FORMAT):
if re.search(r"[^A-Za-z0-9%_-]", settings.DATE_FORMAT):
errors.append(W005)

if getattr(settings, 'FAILURE_RECIPIENTS', None) is not None:
if getattr(settings, "FAILURE_RECIPIENTS", None) is not None:
errors.append(W006)

return errors
79 changes: 46 additions & 33 deletions dbbackup/db/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,22 @@

from . import exceptions

logger = logging.getLogger('dbbackup.command')
logger = logging.getLogger("dbbackup.command")
logger.setLevel(logging.DEBUG)


CONNECTOR_MAPPING = {
'django.db.backends.sqlite3': 'dbbackup.db.sqlite.SqliteConnector',
'django.db.backends.mysql': 'dbbackup.db.mysql.MysqlDumpConnector',
'django.db.backends.postgresql': 'dbbackup.db.postgresql.PgDumpBinaryConnector',
'django.db.backends.postgresql_psycopg2': 'dbbackup.db.postgresql.PgDumpBinaryConnector',
'django.db.backends.oracle': None,
'django_mongodb_engine': 'dbbackup.db.mongodb.MongoDumpConnector',
'djongo': 'dbbackup.db.mongodb.MongoDumpConnector',
'django.contrib.gis.db.backends.postgis': 'dbbackup.db.postgresql.PgDumpGisConnector',
'django.contrib.gis.db.backends.mysql': 'dbbackup.db.mysql.MysqlDumpConnector',
'django.contrib.gis.db.backends.oracle': None,
'django.contrib.gis.db.backends.spatialite': 'dbbackup.db.sqlite.SqliteConnector',
"django.db.backends.sqlite3": "dbbackup.db.sqlite.SqliteConnector",
"django.db.backends.mysql": "dbbackup.db.mysql.MysqlDumpConnector",
"django.db.backends.postgresql": "dbbackup.db.postgresql.PgDumpBinaryConnector",
"django.db.backends.postgresql_psycopg2": "dbbackup.db.postgresql.PgDumpBinaryConnector",
"django.db.backends.oracle": None,
"django_mongodb_engine": "dbbackup.db.mongodb.MongoDumpConnector",
"djongo": "dbbackup.db.mongodb.MongoDumpConnector",
"django.contrib.gis.db.backends.postgis": "dbbackup.db.postgresql.PgDumpGisConnector",
"django.contrib.gis.db.backends.mysql": "dbbackup.db.mysql.MysqlDumpConnector",
"django.contrib.gis.db.backends.oracle": None,
"django.contrib.gis.db.backends.spatialite": "dbbackup.db.sqlite.SqliteConnector",
}

if settings.CUSTOM_CONNECTOR_MAPPING:
Expand All @@ -45,12 +45,12 @@ def get_connector(database_name=None):
# Get DB
database_name = database_name or DEFAULT_DB_ALIAS
connection = connections[database_name]
engine = connection.settings_dict['ENGINE']
engine = connection.settings_dict["ENGINE"]
connector_settings = settings.CONNECTORS.get(database_name, {})
connector_path = connector_settings.get('CONNECTOR', CONNECTOR_MAPPING[engine])
connector_module_path = '.'.join(connector_path.split('.')[:-1])
connector_path = connector_settings.get("CONNECTOR", CONNECTOR_MAPPING[engine])
connector_module_path = ".".join(connector_path.split(".")[:-1])
module = import_module(connector_module_path)
connector_name = connector_path.split('.')[-1]
connector_name = connector_path.split(".")[-1]
connector = getattr(module, connector_name)
return connector(database_name, **connector_settings)

Expand All @@ -60,11 +60,13 @@ class BaseDBConnector:
Base class for create database connector. This kind of object creates
interaction with database and allow backup and restore operations.
"""
extension = 'dump'

extension = "dump"
exclude = []

def __init__(self, database_name=None, **kwargs):
from django.db import DEFAULT_DB_ALIAS, connections

self.database_name = database_name or DEFAULT_DB_ALIAS
self.connection = connections[self.database_name]
for attr, value in kwargs.items():
Expand All @@ -73,15 +75,14 @@ def __init__(self, database_name=None, **kwargs):
@property
def settings(self):
"""Mix of database and connector settings."""
if not hasattr(self, '_settings'):
if not hasattr(self, "_settings"):
sett = self.connection.settings_dict.copy()
sett.update(settings.CONNECTORS.get(self.database_name, {}))
self._settings = sett
return self._settings

def generate_filename(self, server_name=None):
return utils.filename_generate(self.extension, self.database_name,
server_name)
return utils.filename_generate(self.extension, self.database_name, server_name)

def create_dump(self):
return self._create_dump()
Expand Down Expand Up @@ -112,10 +113,11 @@ class BaseCommandDBConnector(BaseDBConnector):
"""
Base class for create database connector based on command line tools.
"""
dump_prefix = ''
dump_suffix = ''
restore_prefix = ''
restore_suffix = ''

dump_prefix = ""
dump_suffix = ""
restore_prefix = ""
restore_suffix = ""

use_parent_env = True
env = {}
Expand All @@ -137,29 +139,40 @@ def run_command(self, command, stdin=None, env=None):
"""
logger.debug(command)
cmd = shlex.split(command)
stdout = SpooledTemporaryFile(max_size=settings.TMP_FILE_MAX_SIZE,
dir=settings.TMP_DIR)
stderr = SpooledTemporaryFile(max_size=settings.TMP_FILE_MAX_SIZE,
dir=settings.TMP_DIR)
stdout = SpooledTemporaryFile(
max_size=settings.TMP_FILE_MAX_SIZE, dir=settings.TMP_DIR
)
stderr = SpooledTemporaryFile(
max_size=settings.TMP_FILE_MAX_SIZE, dir=settings.TMP_DIR
)
full_env = os.environ.copy() if self.use_parent_env else {}
full_env.update(self.env)
full_env.update(env or {})
try:
if isinstance(stdin, File):
process = Popen(
cmd, stdin=stdin.open("rb"), stdout=stdout, stderr=stderr,
env=full_env
cmd,
stdin=stdin.open("rb"),
stdout=stdout,
stderr=stderr,
env=full_env,
)
else:
process = Popen(cmd, stdin=stdin, stdout=stdout, stderr=stderr, env=full_env)
process = Popen(
cmd, stdin=stdin, stdout=stdout, stderr=stderr, env=full_env
)
process.wait()
if process.poll():
stderr.seek(0)
raise exceptions.CommandConnectorError(
"Error running: {}\n{}".format(command, stderr.read().decode('utf-8')))
"Error running: {}\n{}".format(
command, stderr.read().decode("utf-8")
)
)
stdout.seek(0)
stderr.seek(0)
return stdout, stderr
except OSError as err:
raise exceptions.CommandConnectorError(
f"Error running: {command}\n{str(err)}")
f"Error running: {command}\n{str(err)}"
)
2 changes: 1 addition & 1 deletion dbbackup/db/mongodb.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def _create_dump(self):
cmd += f" --excludeCollection {collection}"
cmd += " --archive"
cmd = f"{self.dump_prefix} {cmd} {self.dump_suffix}"
stdout, _stderr = self.run_command(cmd, env=self.dump_env)
stdout, stderr = self.run_command(cmd, env=self.dump_env)
return stdout

def _restore_dump(self, dump):
Expand Down
2 changes: 1 addition & 1 deletion dbbackup/db/mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def _create_dump(self):
for table in self.exclude:
cmd += f" --ignore-table={self.settings['NAME']}.{table}"
cmd = f"{self.dump_prefix} {cmd} {self.dump_suffix}"
stdout, _stderr = self.run_command(cmd, env=self.dump_env)
stdout, stderr = self.run_command(cmd, env=self.dump_env)
return stdout

def _restore_dump(self, dump):
Expand Down
6 changes: 3 additions & 3 deletions dbbackup/db/postgresql.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def create_postgres_uri(self):
if not user:
password = ""
else:
host = f"@{host}"
host = "@" + host

port = ":{}".format(self.settings.get("PORT")) if self.settings.get("PORT") else ""
dbname = f"--dbname=postgresql://{user}{password}{host}{port}/{dbname}"
Expand Down Expand Up @@ -48,7 +48,7 @@ def _create_dump(self):
cmd += " --clean"

cmd = f"{self.dump_prefix} {cmd} {self.dump_suffix}"
stdout, _stderr = self.run_command(cmd, env=self.dump_env)
stdout, stderr = self.run_command(cmd, env=self.dump_env)
return stdout

def _restore_dump(self, dump):
Expand Down Expand Up @@ -109,7 +109,7 @@ def _create_dump(self):
for table in self.exclude:
cmd += f" --exclude-table-data={table}"
cmd = f"{self.dump_prefix} {cmd} {self.dump_suffix}"
stdout, _stderr = self.run_command(cmd, env=self.dump_env)
stdout, stderr = self.run_command(cmd, env=self.dump_env)
return stdout

def _restore_dump(self, dump):
Expand Down
31 changes: 17 additions & 14 deletions dbbackup/db/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ def _write_dump(self, fileobj):
cursor = self.connection.cursor()
cursor.execute(DUMP_TABLES)
for table_name, type, sql in cursor.fetchall():
if table_name.startswith('sqlite_') or table_name in self.exclude:
if table_name.startswith("sqlite_") or table_name in self.exclude:
continue
elif sql.startswith('CREATE TABLE'):
sql = sql.replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS')
elif sql.startswith("CREATE TABLE"):
sql = sql.replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS")
# Make SQL commands in 1 line
sql = sql.replace('\n ', '')
sql = sql.replace('\n)', ')')
sql = sql.replace("\n ", "")
sql = sql.replace("\n)", ")")
fileobj.write(f"{sql};\n".encode())
else:
fileobj.write(f"{sql};\n")
Expand All @@ -45,16 +45,19 @@ def _write_dump(self, fileobj):
column_names = [str(table_info[1]) for table_info in res.fetchall()]
q = """SELECT 'INSERT INTO "{0}" VALUES({1})' FROM "{0}";\n""".format(
table_name_ident,
",".join("""'||quote("{}")||'""".format(col.replace('"', '""'))
for col in column_names))
",".join(
"""'||quote("{}")||'""".format(col.replace('"', '""'))
for col in column_names
),
)
query_res = cursor.execute(q)
for row in query_res:
fileobj.write(f"{row[0]};\n".encode())
schema_res = cursor.execute(DUMP_ETC)
for name, type, sql in schema_res.fetchall():
if sql.startswith("CREATE INDEX"):
sql = sql.replace('CREATE INDEX', 'CREATE INDEX IF NOT EXISTS')
fileobj.write(f'{sql};\n'.encode())
sql = sql.replace("CREATE INDEX", "CREATE INDEX IF NOT EXISTS")
fileobj.write(f"{sql};\n".encode())
cursor.close()

def create_dump(self):
Expand All @@ -71,7 +74,7 @@ def restore_dump(self, dump):
cursor = self.connection.cursor()
for line in dump.readlines():
try:
cursor.execute(line.decode('UTF-8'))
cursor.execute(line.decode("UTF-8"))
except (OperationalError, IntegrityError) as err:
warnings.warn(f"Error in db restore: {err}")

Expand All @@ -83,14 +86,14 @@ class SqliteCPConnector(BaseDBConnector):
"""

def create_dump(self):
path = self.connection.settings_dict['NAME']
path = self.connection.settings_dict["NAME"]
dump = BytesIO()
with open(path, 'rb') as db_file:
with open(path, "rb") as db_file:
copyfileobj(db_file, dump)
dump.seek(0)
return dump

def restore_dump(self, dump):
path = self.connection.settings_dict['NAME']
with open(path, 'wb') as db_file:
path = self.connection.settings_dict["NAME"]
with open(path, "wb") as db_file:
copyfileobj(dump, db_file)
Loading

0 comments on commit 388cab2

Please sign in to comment.