diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c3efa100..946c66ee 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -24,7 +24,7 @@ Bootstrap, build, and test the repository: **Interactive Development Shell:** -- `hatch shell [ENV_NAME]` - Enter an interactive shell environment with all dependencies installed. ENV_NAME is optional and defaults to the main environment. Use `hatch shell functional` for the functional test environment, `hatch shell lint` for the linting environment, etc. +- `hatch shell [ENV_NAME]` - Enter an interactive shell environment with all dependencies installed. ENV_NAME is optional and defaults to the main environment. Use `hatch shell functional` for the functional test environment. Build documentation: @@ -101,14 +101,14 @@ Modern development process using Hatch: 2. **Make your changes** to the codebase 3. **Run unit tests**: `hatch test` (≈30s) **All must pass - failures are never expected or allowed.** 4. **Run functional tests**: `hatch run functional:all -v` (≈10–15s) -5. **Run linting**: `hatch run lint:check` (5 seconds) +5. **Run linting**: `hatch fmt --check` (2 seconds) 6. **Auto-format code**: `hatch fmt` (2 seconds) 7. **Test documentation**: `hatch run docs:build` (2 seconds) 8. **Update documentation** when making changes to Python source code (required) 9. **Add changelog entry** for all significant changes (bug fixes, new features, breaking changes) to `CHANGELOG.md` under the "Unreleased" section. 10. **Lint the changelog** by running `python scripts/validate_changelog.py` (or inside any hatch shell) to ensure format correctness -Always run `hatch run lint:check` before committing. The CI (.github/workflows/build.yml) includes comprehensive checks across all supported Python/Django combinations. +Always run `hatch fmt --check` before committing. The CI (.github/workflows/ci.yml) includes comprehensive checks across all supported Python/Django combinations. **IMPORTANT**: Documentation must be updated whenever changes are made to Python source code. This is enforced as part of the development workflow. @@ -226,7 +226,7 @@ Development dependencies (managed by hatch): ## CI/CD Pipeline -Modern GitHub Actions workflow (.github/workflows/build.yml): +Modern GitHub Actions workflow (.github/workflows/ci.yml): - **Lint Python**: Code quality checks (temporarily set to pass) - **Test Python**: Matrix testing across Python 3.9-3.13 with coverage diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4826173e..ec560304 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,66 +1,66 @@ --- repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: "v5.0.0" - hooks: - - id: check-merge-conflict - - id: end-of-file-fixer - exclude: ^docs/[^/]*\.svg$ - - id: requirements-txt-fixer - - id: trailing-whitespace - types: [python] - - id: check-case-conflict - - id: check-json - - id: check-xml - - id: check-toml - - id: check-xml - - id: check-yaml - - id: debug-statements - - id: check-added-large-files - - id: check-symlinks - - id: debug-statements - - id: detect-aws-credentials - args: ["--allow-missing-credentials"] - - id: detect-private-key - exclude: ^examples|(?:tests/ssl)/ - - repo: https://github.com/hadialqattan/pycln - rev: v2.5.0 - hooks: - - id: pycln - args: ["--all"] - - repo: https://github.com/asottile/yesqa - rev: v1.5.0 - hooks: - - id: yesqa - - repo: https://github.com/pycqa/isort - rev: "6.0.1" - hooks: - - id: isort - args: ["--profile", "black"] - - repo: https://github.com/psf/black - rev: "25.1.0" - hooks: - - id: black - - repo: https://github.com/asottile/pyupgrade - rev: "v3.20.0" - hooks: - - id: pyupgrade - args: ["--py39-plus", "--keep-mock"] - - repo: https://github.com/hhatto/autopep8 - rev: "v2.3.2" - hooks: - - id: autopep8 - - repo: https://github.com/PyCQA/flake8 - rev: "7.2.0" - hooks: - - id: flake8 - exclude: "^docs/" - - repo: https://github.com/Lucas-C/pre-commit-hooks-markup - rev: "v1.0.1" - hooks: - - id: rst-linter - files: ^[^/]+[.]rst$ - - repo: https://github.com/adrienverge/yamllint - rev: "v1.37.1" - hooks: - - id: yamllint + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: "v5.0.0" + hooks: + - id: check-merge-conflict + - id: end-of-file-fixer + exclude: ^docs/[^/]*\.svg$ + - id: requirements-txt-fixer + - id: trailing-whitespace + types: [python] + - id: check-case-conflict + - id: check-json + - id: check-xml + - id: check-toml + - id: check-xml + - id: check-yaml + - id: debug-statements + - id: check-added-large-files + - id: check-symlinks + - id: debug-statements + - id: detect-aws-credentials + args: ["--allow-missing-credentials"] + - id: detect-private-key + exclude: ^examples|(?:tests/ssl)/ + - repo: https://github.com/hadialqattan/pycln + rev: v2.5.0 + hooks: + - id: pycln + args: ["--all"] + - repo: https://github.com/asottile/yesqa + rev: v1.5.0 + hooks: + - id: yesqa + - repo: https://github.com/pycqa/isort + rev: "6.0.1" + hooks: + - id: isort + args: ["--profile", "black"] + - repo: https://github.com/psf/black + rev: "25.1.0" + hooks: + - id: black + - repo: https://github.com/asottile/pyupgrade + rev: "v3.20.0" + hooks: + - id: pyupgrade + args: ["--py39-plus", "--keep-mock"] + - repo: https://github.com/hhatto/autopep8 + rev: "v2.3.2" + hooks: + - id: autopep8 + - repo: https://github.com/PyCQA/flake8 + rev: "7.2.0" + hooks: + - id: flake8 + exclude: "^docs/" + - repo: https://github.com/Lucas-C/pre-commit-hooks-markup + rev: "v1.0.1" + hooks: + - id: rst-linter + files: ^[^/]+[.]rst$ + - repo: https://github.com/adrienverge/yamllint + rev: "v1.37.1" + hooks: + - id: yamllint diff --git a/CHANGELOG.md b/CHANGELOG.md index c147755e..e1998e9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,32 +15,33 @@ Don't forget to remove deprecated code on each major release! ## [Unreleased] ### Added + - Added support for custom metadata writing and validation during operations via `DBBACKUP_BACKUP_METADATA_SETTER` and `DBBACKUP_RESTORE_METADATA_VALIDATOR` settings. ## [5.1.2] - 2026-01-14 ### Fixed -- Fixed a bug where metadata files were not being removed during clean-up. +- Fixed a bug where metadata files were not being removed during clean-up. ## [5.1.1] - 2026-01-07 ### Fixed -- Ensure `dbbackup` metadata file is always written as bytes to support storage backends that enforce bytes content (e.g. Google Cloud Storage). +- Ensure `dbbackup` metadata file is always written as bytes to support storage backends that enforce bytes content (e.g. Google Cloud Storage). ## [5.1.0] - 2025-12-17 ### Fixed -- Prevent restoring a backup from a different database connector (e.g. Postgres backup to SQLite) by adding an additional metadata file to all new backups. -- Fixed compressed media backup restoration by using `gzip.GzipFile` instead of `tarfile`'s gzip decompression algorithm. +- Prevent restoring a backup from a different database connector (e.g. Postgres backup to SQLite) by adding an additional metadata file to all new backups. +- Fixed compressed media backup restoration by using `gzip.GzipFile` instead of `tarfile`'s gzip decompression algorithm. ## [5.0.1] - 2025-11-07 ### Security -- To prevent accidental media exports, this package will now generate an exception if utilizing the legacy `DBBACKUP_STORAGE` or `DBBACKUP_STORAGE_OPTIONS` settings. These settings have been removed in favor of using Django's built-in `STORAGES` setting. Please refer to the documentation for more information on how to migrate your configuration. +- To prevent accidental media exports, this package will now generate an exception if utilizing the legacy `DBBACKUP_STORAGE` or `DBBACKUP_STORAGE_OPTIONS` settings. These settings have been removed in favor of using Django's built-in `STORAGES` setting. Please refer to the documentation for more information on how to migrate your configuration. ## [5.0.0] - 2025-08-30 @@ -54,298 +55,298 @@ Don't forget to remove deprecated code on each major release! ### Added -- Implement new `SqliteBackupConnector` to backup SQLite3 databases using the `.backup` command (safe to execute on databases with active connections). -- Verified full Windows compatibility via new CI workflows. -- Add Django Signals support for backup and restore operations. New signals include `pre_backup`, `post_backup`, `pre_restore`, `post_restore`, `pre_media_backup`, `post_media_backup`, `pre_media_restore`, and `post_media_restore`. -- New `DjangoConnector` that provides database-agnostic backup and restore functionality using Django's built-in `dumpdata` and `loaddata` management commands. +- Implement new `SqliteBackupConnector` to backup SQLite3 databases using the `.backup` command (safe to execute on databases with active connections). +- Verified full Windows compatibility via new CI workflows. +- Add Django Signals support for backup and restore operations. New signals include `pre_backup`, `post_backup`, `pre_restore`, `post_restore`, `pre_media_backup`, `post_media_backup`, `pre_media_restore`, and `post_media_restore`. +- New `DjangoConnector` that provides database-agnostic backup and restore functionality using Django's built-in `dumpdata` and `loaddata` management commands. ### Changed -- This repository has been transferred out of Jazzband due to logistical concerns. -- Improve error message for missing database tools (`pg_dump`, `mysqldump`, etc.) to provide guidance instead of generic "No such file or directory" errors. -- Changed default SQLite connector from `SqliteConnector` to `SqliteBackupConnector` to adhere to best practices. -- Set default `IF_EXISTS` to `True` for PostgreSQL connectors (`PgDumpConnector` and `PgDumpBinaryConnector`) to reduce restore errors when objects are absent. -- If `PASSWORD` is set to `None` for PostgreSQL connectors, the `--no-password` flag is now automatically used. +- This repository has been transferred out of Jazzband due to logistical concerns. +- Improve error message for missing database tools (`pg_dump`, `mysqldump`, etc.) to provide guidance instead of generic "No such file or directory" errors. +- Changed default SQLite connector from `SqliteConnector` to `SqliteBackupConnector` to adhere to best practices. +- Set default `IF_EXISTS` to `True` for PostgreSQL connectors (`PgDumpConnector` and `PgDumpBinaryConnector`) to reduce restore errors when objects are absent. +- If `PASSWORD` is set to `None` for PostgreSQL connectors, the `--no-password` flag is now automatically used. ### Removed -- Drop support for end-of-life Python 3.7 and 3.8. -- Drop support for end-of-life Django 3.2. -- Drop `pytz` dependency in favor of Python's standard library `zoneinfo` (following Django 4.0+ timezone implementation). -- Drop support for `DBBACKUP_STORAGE` AND `DBBACKUP_STORAGE_OPTIONS` settings, use Django's `STORAGES['dbbackup']` setting instead. -- Remove deprecated `DBBACKUP_FAILURE_RECIPIENTS` setting, use `DBBACKUP_ADMINS` instead. -- Remove deprecated code for legacy Django version compatibility. +- Drop support for end-of-life Python 3.7 and 3.8. +- Drop support for end-of-life Django 3.2. +- Drop `pytz` dependency in favor of Python's standard library `zoneinfo` (following Django 4.0+ timezone implementation). +- Drop support for `DBBACKUP_STORAGE` AND `DBBACKUP_STORAGE_OPTIONS` settings, use Django's `STORAGES['dbbackup']` setting instead. +- Remove deprecated `DBBACKUP_FAILURE_RECIPIENTS` setting, use `DBBACKUP_ADMINS` instead. +- Remove deprecated code for legacy Django version compatibility. ### Fixed -- Fixed `-O` flag to properly handle S3 URIs. Now `python manage.py dbbackup -O s3://bucket/path/` and `python manage.py mediabackup -O s3://bucket/path/` correctly route S3 URIs to the storage backend instead of attempting to write to local filesystem. -- Fix issues with parsing excess whitespace within `dbbackup -d ""` -- Fix encryption support when using `gnupg==5.x`. -- Resolve SQLite backup temporary file locking issues on Windows. -- Fix MediaRestore path corruption for files containing "media" in their paths. -- Fix FTP storage restore issue where file objects without `fileno()` support caused `io.UnsupportedOperation` error during database restore operations. -- Fix SQLite restore failing when multi-line `TextField` content contains semicolons. -- Fix SQLite `no such table` errors. -- Fix SQLite `UNIQUE constraint` errors. -- Fix SQLite `index`/`trigger`/`view` ` already exists` errors. -- Fix PostgreSQL restore errors with identity columns by automatically enabling `--if-exists` when using `--clean` in `PgDumpBinaryConnector`. +- Fixed `-O` flag to properly handle S3 URIs. Now `python manage.py dbbackup -O s3://bucket/path/` and `python manage.py mediabackup -O s3://bucket/path/` correctly route S3 URIs to the storage backend instead of attempting to write to local filesystem. +- Fix issues with parsing excess whitespace within `dbbackup -d ""` +- Fix encryption support when using `gnupg==5.x`. +- Resolve SQLite backup temporary file locking issues on Windows. +- Fix MediaRestore path corruption for files containing "media" in their paths. +- Fix FTP storage restore issue where file objects without `fileno()` support caused `io.UnsupportedOperation` error during database restore operations. +- Fix SQLite restore failing when multi-line `TextField` content contains semicolons. +- Fix SQLite `no such table` errors. +- Fix SQLite `UNIQUE constraint` errors. +- Fix SQLite `index`/`trigger`/`view` ` already exists` errors. +- Fix PostgreSQL restore errors with identity columns by automatically enabling `--if-exists` when using `--clean` in `PgDumpBinaryConnector`. ### Security -- Use environment variable for PostgreSQL password to prevent password leakage in logs/emails. +- Use environment variable for PostgreSQL password to prevent password leakage in logs/emails. ## [4.3.0] - 2025-05-09 ### Added -- Add generic `--pg-options` to pass custom options to postgres. -- Add option `--if-exists` for `pg_dump` command. -- Support Python 3.13 and Django 5.2. +- Add generic `--pg-options` to pass custom options to postgres. +- Add option `--if-exists` for `pg_dump` command. +- Support Python 3.13 and Django 5.2. ### Fixed -- Empty string as HOST for postgres unix domain socket connection is now supported. +- Empty string as HOST for postgres unix domain socket connection is now supported. ## [4.2.1] - 2024-08-23 ### Added -- Add `--no-drop` option to `dbrestore` command to prevent dropping tables before restoring data. +- Add `--no-drop` option to `dbrestore` command to prevent dropping tables before restoring data. ### Fixed -- Fix bug where sqlite `dbrestore` would fail if field data contains the line break character. +- Fix bug where sqlite `dbrestore` would fail if field data contains the line break character. ## [4.2.0] - 2024-08-22 ### Added -- Add PostgreSQL Schema support. -- Add support for new `STORAGES` (Django 4.2+) setting under the 'dbbackup' alias. +- Add PostgreSQL Schema support. +- Add support for new `STORAGES` (Django 4.2+) setting under the 'dbbackup' alias. ### Changed -- Set postgres default database `HOST` to `"localhost"`. -- Add warning for filenames with slashes in them. +- Set postgres default database `HOST` to `"localhost"`. +- Add warning for filenames with slashes in them. ### Removed -- Remove usage of deprecated `get_storage_class` function in newer Django versions. +- Remove usage of deprecated `get_storage_class` function in newer Django versions. ### Fixed -- Fix restore of database from S3 storage by reintroducing `inputfile.seek(0)` to `utils.uncompress_file`. -- Fix bug where dbbackup management commands would not respect `settings.py:DBBACKUP_DATABASES`. +- Fix restore of database from S3 storage by reintroducing `inputfile.seek(0)` to `utils.uncompress_file`. +- Fix bug where dbbackup management commands would not respect `settings.py:DBBACKUP_DATABASES`. ## [4.1.0] - 2024-01-14 ### Added -- Support Python 3.11 and 3.12. -- Support Django 4.1, 4.2, and 5.0. +- Support Python 3.11 and 3.12. +- Support Django 4.1, 4.2, and 5.0. ### Changed -- Update documentation for backup directory consistency and update links. +- Update documentation for backup directory consistency and update links. ### Removed -- Drop python 3.6. +- Drop python 3.6. ### Fixed -- Fix restore fail after editing filename. -- `RESTORE_PREFIX` for `RESTORE_SUFFIX`. +- Fix restore fail after editing filename. +- `RESTORE_PREFIX` for `RESTORE_SUFFIX`. ## [4.0.2] - 2022-09-27 ### Added -- Support for prometheus wrapped databases. +- Support for prometheus wrapped databases. ### Fixed -- Backup of SQLite fail if there are Virtual Tables (e.g. FTS tables). -- Fix broken `unencrypt_file` function in `python-gnupg`. +- Backup of SQLite fail if there are Virtual Tables (e.g. FTS tables). +- Fix broken `unencrypt_file` function in `python-gnupg`. ## [4.0.1] - 2022-07-09 ### Added -- Add authentication database support for MongoDB. -- Explicitly support Python 3.6+. -- Add support for exclude tables data in the command interface. -- Enable functional tests in CI. +- Add authentication database support for MongoDB. +- Explicitly support Python 3.6+. +- Add support for exclude tables data in the command interface. +- Enable functional tests in CI. ### Changed -- Replace `ugettext_lazy` with `gettext_lazy`. -- Changed logging settings from `settings.py` to late init. -- Use `exclude-table-data` instead of `exclude-table`. -- Move author and version information into `setup.py` to allow building package in isolated environment (e.g. with the `build` package). -- As of this version, dbbackup is now within Jazzband! This version tests our Jazzband release CI, and adds miscellaneous refactoring/cleanup. -- Update `settings.py` comment. -- Jazzband transfer tasks. -- Refactoring and tooling. +- Replace `ugettext_lazy` with `gettext_lazy`. +- Changed logging settings from `settings.py` to late init. +- Use `exclude-table-data` instead of `exclude-table`. +- Move author and version information into `setup.py` to allow building package in isolated environment (e.g. with the `build` package). +- As of this version, dbbackup is now within Jazzband! This version tests our Jazzband release CI, and adds miscellaneous refactoring/cleanup. +- Update `settings.py` comment. +- Jazzband transfer tasks. +- Refactoring and tooling. ### Removed -- Remove six dependency. -- Drop support for end of life Django versions. Currently support 2.2, 3.2, 4.0. +- Remove six dependency. +- Drop support for end of life Django versions. Currently support 2.2, 3.2, 4.0. ### Fixed -- Fix `RemovedInDjango41Warning` related to `default_app_config`. -- Fix authentication error when postgres is password protected. -- Documentation fixes. -- Fix GitHub Actions configuration. +- Fix `RemovedInDjango41Warning` related to `default_app_config`. +- Fix authentication error when postgres is password protected. +- Documentation fixes. +- Fix GitHub Actions configuration. ## [3.3.0] - 2020-04-14 ### Added -- `"output-filename"` in `mediabackup` command. -- Updates to include SFTP storage. +- `"output-filename"` in `mediabackup` command. +- Updates to include SFTP storage. ### Fixed -- Fixes for test infrastructure and mongodb support. -- sqlite3: don't throw warnings if table already exists. -- Fixes for django v3 and update travis. -- Restoring from FTP. -- Fix management commands when using Postgres on non-latin Windows. -- Fix improper database name selection when performing a restore. +- Fixes for test infrastructure and mongodb support. +- sqlite3: don't throw warnings if table already exists. +- Fixes for django v3 and update travis. +- Restoring from FTP. +- Fix management commands when using Postgres on non-latin Windows. +- Fix improper database name selection when performing a restore. ## [3.2.0] - 2017-09-18 ### Added -- `PgDumpBinaryConnector` (binary `pg_dump` integration) with related functional tests. -- Option to keep specific old backups (custom clean old backups logic). +- `PgDumpBinaryConnector` (binary `pg_dump` integration) with related functional tests. +- Option to keep specific old backups (custom clean old backups logic). ### Changed -- Updated PostgreSQL documentation and help text for clarity. +- Updated PostgreSQL documentation and help text for clarity. ### Fixed -- SFTP storage file attribute error ("SFTPStorageFile object has no attribute name"). -- Escaping of passwords passed to commands. -- Corrected management command help text after flag logic change. +- SFTP storage file attribute error ("SFTPStorageFile object has no attribute name"). +- Escaping of passwords passed to commands. +- Corrected management command help text after flag logic change. ## [3.1.3] - 2016-11-25 ### Fixed -- Reverted a regression in `pg_dump` database name handling introduced shortly before. +- Reverted a regression in `pg_dump` database name handling introduced shortly before. ## [3.1.2] - 2016-11-25 ### Fixed -- Correct `pg_dump` invocation: proper username and database argument handling. +- Correct `pg_dump` invocation: proper username and database argument handling. ## [3.1.1] - 2016-11-16 ### Fixed -- Unicode handling issues with SQLite backups. +- Unicode handling issues with SQLite backups. ## [3.1.0] - 2016-11-15 ### Added -- Support for inheriting parent environment variables in command connectors (`USE_PARENT_ENV`). +- Support for inheriting parent environment variables in command connectors (`USE_PARENT_ENV`). ### Changed -- Complete revamp of logging and error email notification system (more structured logging & tests). +- Complete revamp of logging and error email notification system (more structured logging & tests). ## [3.0.4] - 2016-11-14 ### Added -- Ability to link / register custom connectors. +- Ability to link / register custom connectors. ### Changed -- Use naïve (timezone-unaware) `datetime` in backup filenames for broader compatibility. +- Use naïve (timezone-unaware) `datetime` in backup filenames for broader compatibility. ### Fixed -- `mediabackup` timeout issue. -- Improved PostgreSQL `dbrestore` error recognition. +- `mediabackup` timeout issue. +- Improved PostgreSQL `dbrestore` error recognition. ## [3.0.3] - 2016-09-15 ### Added -- Server name filter for database and media backup/restore. -- Ability to select multiple databases for backup. +- Server name filter for database and media backup/restore. +- Ability to select multiple databases for backup. ### Changed -- Improved filename generation logic. +- Improved filename generation logic. ### Fixed -- Database filter logic and clean backup behavior. +- Database filter logic and clean backup behavior. ## [3.0.2] - 2016-08-06 ### Fixed -- Disabled Django loggers inadvertently affecting application logging. +- Disabled Django loggers inadvertently affecting application logging. ## [3.0.1] - 2016-08-04 ### Added -- New connector architecture with dedicated connectors for PostgreSQL, MySQL, SQLite (copy) and MongoDB. -- Media backup & restore system overhaul (per-file processing, media restore command & tests). -- Exclude table/data options, prefix & suffix options for connector commands. -- Environment variable control for command execution. -- GIS database engine mapping support. -- Functional, integration and upgrade test suites; app & system checks integration. +- New connector architecture with dedicated connectors for PostgreSQL, MySQL, SQLite (copy) and MongoDB. +- Media backup & restore system overhaul (per-file processing, media restore command & tests). +- Exclude table/data options, prefix & suffix options for connector commands. +- Environment variable control for command execution. +- GIS database engine mapping support. +- Functional, integration and upgrade test suites; app & system checks integration. ### Changed -- Refactored and unified code between database & media backup/restore commands. -- Renamed `mediabackup` option (`--no-compress` replaced by explicit `--compress`). +- Refactored and unified code between database & media backup/restore commands. +- Renamed `mediabackup` option (`--no-compress` replaced by explicit `--compress`). ### Removed -- Legacy `DBCommand` code in favor of new connector system. +- Legacy `DBCommand` code in favor of new connector system. ## [2.5.0] - 2016-03-29 ### Added -- `--filename` and `--path` options for `dbbackup` / `dbrestore` commands. -- Binary size unit prefixes in output. +- `--filename` and `--path` options for `dbbackup` / `dbrestore` commands. +- Binary size unit prefixes in output. ### Changed -- Dropbox storage updated to OAuth2 & Python 3 compatibility. +- Dropbox storage updated to OAuth2 & Python 3 compatibility. ### Removed -- `DBBACKUP_DROPBOX_ACCESS_TYPE` setting (deprecated by OAuth2 changes). +- `DBBACKUP_DROPBOX_ACCESS_TYPE` setting (deprecated by OAuth2 changes). ### Fixed -- NameError for missing `warnings` import. -- Wildcard handling in generated filenames; proper server name derivation for SQLite paths. +- NameError for missing `warnings` import. +- Wildcard handling in generated filenames; proper server name derivation for SQLite paths. ## [2.3.3] - 2015-10-05 ### Changed -- Initial copy from BitBucket to GitHub. +- Initial copy from BitBucket to GitHub. ### Fixed -- Miscellaneous maintenance and minor bug fixes. +- Miscellaneous maintenance and minor bug fixes. [Unreleased]: https://github.com/Archmonger/django-dbbackup/compare/5.1.2...HEAD [5.1.2]: https://github.com/Archmonger/django-dbbackup/compare/5.1.1...5.1.2 diff --git a/LICENSE.md b/LICENSE.md index 46585fee..1d7e7b2d 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -6,8 +6,8 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -* Neither the name django-dbbackup nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +- Neither the name django-dbbackup nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 106cf725..75ffd8dd 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,12 @@ This Django application provides management commands to help backup and restore The main features include: -- Secure your backup with GPG signature and encryption. -- Archive with compression. -- Easily manage remote archiving. -- Keep your development database up to date. -- Set up automated backups with Crontab or Celery. -- Manually backup and restore via Django management commands. +- Secure your backup with GPG signature and encryption. +- Archive with compression. +- Easily manage remote archiving. +- Keep your development database up to date. +- Set up automated backups with Crontab or Celery. +- Manually backup and restore via Django management commands. diff --git a/dbbackup/checks.py b/dbbackup/checks.py index d6ecf2ee..04891062 100644 --- a/dbbackup/checks.py +++ b/dbbackup/checks.py @@ -1,7 +1,7 @@ import re from datetime import datetime -from django.core.checks import Tags, Warning, register # noqa: A004 +from django.core.checks import Tags, Warning, register from dbbackup import settings @@ -72,7 +72,7 @@ def _check_filename_template(filename_template, check_code, content_type) -> lis } filename_template = filename_template(params) - if "/" in filename_template: + if isinstance(filename_template, str) and "/" in filename_template: return [check_code] return [] diff --git a/docs/src/contributing.md b/docs/src/contributing.md index 182ccba2..61a9a82b 100644 --- a/docs/src/contributing.md +++ b/docs/src/contributing.md @@ -7,9 +7,9 @@ welcome. This guide explains how to develop, test, and propose changes. If you plan to make code changes to this repository, you will need to install the following dependencies first: -- [Git](https://git-scm.com/downloads) -- [Python 3.9+](https://www.python.org/downloads/) -- [Hatch](https://hatch.pypa.io/latest/) +- [Git](https://git-scm.com/downloads) +- [Python 3.9+](https://www.python.org/downloads/) +- [Hatch](https://hatch.pypa.io/latest/) Once you finish installing these dependencies, you can clone this repository: @@ -42,17 +42,16 @@ cd django-dbbackup ### Linting and Formatting -| Command | Description | -| ----------------------------- | ------------------------------------------------------------------------------------------ | -| `hatch run lint:format` | Run formatters to fix code style | -| `hatch run lint:format-check` | Check code formatting without making changes | -| `hatch run lint:check` | Run all linters | -| `hatch run precommit:check` | Run all [`pre-commit`](https://pre-commit.com/) checks configured within this repository | -| `hatch run precommit:update` | Update the [`pre-commit`](https://pre-commit.com/) hooks configured within this repository | +| Command | Description | +| ---------------------------- | ------------------------------------------------------------------------------------------ | +| `hatch fmt` | Run formatters to fix code style | +| `hatch fmt --check` | Check code formatting without making changes | +| `hatch run precommit:check` | Run all [`pre-commit`](https://pre-commit.com/) checks configured within this repository | +| `hatch run precommit:update` | Update the [`pre-commit`](https://pre-commit.com/) hooks configured within this repository | ??? tip "Configure your IDE for linting" - This repository uses `ruff` and `pylint` for linting and formatting. + This repository uses `ruff` for linting and formatting. You can install `ruff` as a plugin to your preferred code editor to create a similar environment. @@ -83,10 +82,10 @@ Track bugs, feature proposals, and questions via [GitHub Issues](https://github.com/Archmonger/django-dbbackup/issues). Open an issue if: -- You have an improvement idea -- You found a bug -- You've got a question -- More generally something seems wrong for you +- You have an improvement idea +- You found a bug +- You've got a question +- More generally something seems wrong for you ## Make a patch @@ -95,8 +94,8 @@ all code changes. To streamline review: 1. Fork the project and make a new branch 2. Make your changes with tests if possible and documentation if needed -3. Run `hatch test` and `hatch run functional:test` to verify your changes -4. Run `hatch run lint:check` to ensure code quality +3. Run `hatch test` and `hatch run functional:all` to verify your changes +4. Run `hatch fmt` to ensure code quality 5. Push changes to your fork repository and test it with GitHub Actions 6. If it succeeds, open a pull request 7. Follow up politely if there's no feedback after a few days @@ -132,7 +131,7 @@ Database password Database host -**`MEDIA_ROOT`** - Default= `tempfile.mkdtemp()` +**`MEDIA_ROOT`** - Default: `tmp/media/` Django `MEDIA_ROOT`; override to test media backup from a custom path. diff --git a/docs/src/databases.md b/docs/src/databases.md index 40bc6118..464dcac2 100644 --- a/docs/src/databases.md +++ b/docs/src/databases.md @@ -2,11 +2,11 @@ The following databases are supported by this application: -- SQLite -- MySQL -- PostgreSQL -- MongoDB -- ... and any other Django-supported database (via `DjangoConnector`) +- SQLite +- MySQL +- PostgreSQL +- MongoDB +- ... and any other Django-supported database (via `DjangoConnector`) By default DBBackup reuses connection details from `settings.DATABASES`. Sometimes you want different credentials or a different host (e.g. read-only @@ -176,10 +176,10 @@ DATABASES = { All supported built-in connectors are described in more detail below. Following database wrappers from `django-prometheus` are supported: -- `django_prometheus.db.backends.postgresql` -- `django_prometheus.db.backends.sqlite3` -- `django_prometheus.db.backends.mysql` -- `django_prometheus.db.backends.postgis` +- `django_prometheus.db.backends.postgresql` +- `django_prometheus.db.backends.sqlite3` +- `django_prometheus.db.backends.mysql` +- `django_prometheus.db.backends.postgis` ## Django Connector @@ -197,24 +197,24 @@ DBBACKUP_CONNECTORS = { ### Key Features -- **Universal compatibility**: Works with any database backend supported by Django -- **No external dependencies**: Uses Django's serialization system -- **Model-level backups**: Preserves foreign key relationships and data integrity -- **JSON format**: Creates human-readable backups in JSON format +- **Universal compatibility**: Works with any database backend supported by Django +- **No external dependencies**: Uses Django's serialization system +- **Model-level backups**: Preserves foreign key relationships and data integrity +- **JSON format**: Creates human-readable backups in JSON format ### When to Use The Django connector is ideal for: -- Oracle databases (used by default) -- Custom or third-party database backends not explicitly supported -- Development environments where simplicity is preferred -- Cases where external database tools are not available +- Oracle databases (used by default) +- Custom or third-party database backends not explicitly supported +- Development environments where simplicity is preferred +- Cases where external database tools are not available ### Limitations -- **Performance**: Slower than native database tools for large datasets -- **Database structure**: Only provides backups of data; not database schema, indices, or procedures +- **Performance**: Slower than native database tools for large datasets +- **Database structure**: Only provides backups of data; not database schema, indices, or procedures ### File Extension diff --git a/docs/src/index.md b/docs/src/index.md index 6adbd015..bb2987e6 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -8,9 +8,9 @@ hide: ## Other Resources -- [GitHub repository](https://github.com/Archmonger/django-dbbackup) -- [PyPI project](https://pypi.org/pypi/django-dbbackup/) -- [Documentation](https://archmonger.github.io/django-dbbackup/) -- [GitHub issues](https://github.com/Archmonger/django-dbbackup/issues) -- [GitHub discussions](https://github.com/Archmonger/django-dbbackup/discussions) -- [GitHub pull requests](https://github.com/Archmonger/django-dbbackup/pulls) +- [GitHub repository](https://github.com/Archmonger/django-dbbackup) +- [PyPI project](https://pypi.org/pypi/django-dbbackup/) +- [Documentation](https://archmonger.github.io/django-dbbackup/) +- [GitHub issues](https://github.com/Archmonger/django-dbbackup/issues) +- [GitHub discussions](https://github.com/Archmonger/django-dbbackup/discussions) +- [GitHub pull requests](https://github.com/Archmonger/django-dbbackup/pulls) diff --git a/tests/commands/test_dbrestore_metadata.py b/tests/commands/test_dbrestore_metadata.py index d0a22935..eae698a7 100644 --- a/tests/commands/test_dbrestore_metadata.py +++ b/tests/commands/test_dbrestore_metadata.py @@ -123,10 +123,7 @@ def test_metadata_mismatch_custom_valid(self): """Test that built-in validation runs before custom validation.""" importlib.reload(dbbackup.settings) # Setup metadata with different engine (primary fail) but valid custom data - metadata = { - "engine": "django.db.backends.postgresql", - "CSMT_VAL": "aabbcc-1122-3344_eu-west" - } + metadata = {"engine": "django.db.backends.postgresql", "CSMT_VAL": "aabbcc-1122-3344_eu-west"} self.command.storage.read_file.return_value = Mock(read=lambda: json.dumps(metadata)) # Should raise CommandError due to engine mismatch (primary), not custom validation diff --git a/tests/test_connectors/test_sqlite.py b/tests/test_connectors/test_sqlite.py index 63c41e24..c6b786d6 100644 --- a/tests/test_connectors/test_sqlite.py +++ b/tests/test_connectors/test_sqlite.py @@ -123,9 +123,9 @@ def test_restore_dump_warns_only_for_serious_errors(self): assert len(dbbackup_warnings) > 0, "Should warn about 'no such table' error" warning_messages = [str(w.message) for w in dbbackup_warnings] - assert any("no such table" in msg.lower() for msg in warning_messages), ( - f"Should warn about 'no such table', got: {warning_messages}" - ) + assert any( + "no such table" in msg.lower() for msg in warning_messages + ), f"Should warn about 'no such table', got: {warning_messages}" def test_create_dump_with_virtual_tables(self): with connection.cursor() as c: diff --git a/tests/test_settings.py b/tests/test_settings.py index 51cdea8a..61be4ef5 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -16,7 +16,7 @@ def test_admins_setting(self): importlib.reload(dbbackup.settings) # Check that ADMINS is set to DBBACKUP_ADMIN - assert dbbackup.settings.ADMINS == ["admin@example.com"] + assert ["admin@example.com"] == dbbackup.settings.ADMINS def test_deprecated_dbbackup_storage_raises(self): """Importing dbbackup.settings raises if DBBACKUP_STORAGE is set.""" diff --git a/tests/test_storage.py b/tests/test_storage.py index dafb7064..e33818d6 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -231,15 +231,13 @@ def test_clean_includes_metadata(self): self.storage.clean_old_backups(keep_number=1) deleted_files = sorted(HANDLED_FILES["deleted_files"]) - expected_deleted = sorted( - [ - "2015-02-06-042810.bak", - "2015-02-06-042810.bak.metadata", - "2015-02-07-042810.bak", - "2015-02-07-042810.bak.metadata", - ] - ) - self.assertEqual(deleted_files, expected_deleted) + expected_deleted = sorted([ + "2015-02-06-042810.bak", + "2015-02-06-042810.bak.metadata", + "2015-02-07-042810.bak", + "2015-02-07-042810.bak.metadata", + ]) + assert deleted_files == expected_deleted class StorageEdgeCasesTest(TestCase): diff --git a/tests/test_user_metadata.py b/tests/test_user_metadata.py index 7fa84e65..1b1e16ba 100644 --- a/tests/test_user_metadata.py +++ b/tests/test_user_metadata.py @@ -51,20 +51,24 @@ def dummy_validator(metadata): return None if val := metadata.get("CSMT_VAL", ""): if not val.startswith("aabbcc"): - raise ValueError("CSMT_VAL must start with 'aabbcc'") + msg = "CSMT_VAL must start with 'aabbcc'" + raise ValueError(msg) if not val.endswith("eu-west"): return False return True def broken_validator(metadata): - print(1 / 0) # Always raises ZeroDivisionError - return True + msg = "This validator is broken" + raise ValueError(msg) + def non_bool_validator(metadata): return "not a boolean" -DEFAULT_META = {'ENGINE': 'django.db.backends.sqlite3'} + +DEFAULT_META = {"ENGINE": "django.db.backends.sqlite3"} + # Actual tests class CustomMetadataTest(TestCase): @@ -81,7 +85,7 @@ def test_metadata_setter_valid_callable(self): """Test that setting DBBACKUP_BACKUP_METADATA_SETTER works with a callable.""" importlib.reload(dbbackup.settings) - assert dbbackup.settings.BACKUP_METADATA_SETTER == dummy_setter + assert dummy_setter == dbbackup.settings.BACKUP_METADATA_SETTER assert dbbackup.utils.get_user_metadata(DEFAULT_META) == {"CSMT_VAL": TEST_VAL} @override_settings(DBBACKUP_BACKUP_METADATA_SETTER="tests.test_user_metadata.setter_returning_none") @@ -105,7 +109,7 @@ def test_metadata_setter_invalid_1(self): """Test that various setter missconfigurations do not work - Non-existent module.""" importlib.reload(dbbackup.settings) - with pytest.raises(ImportError, match="Could not import module 'non.existent': No module named 'non'"): + with pytest.raises(ImportError, match=r"Could not import module 'non\.existent': No module named 'non'"): dbbackup.utils.get_user_metadata(DEFAULT_META) @override_settings(DBBACKUP_BACKUP_METADATA_SETTER="tests.test_user_metadata.TEST_VAL") @@ -113,7 +117,7 @@ def test_metadata_setter_invalid_2(self): """Test that various setter missconfigurations do not work - Non-callable object.""" importlib.reload(dbbackup.settings) - with pytest.raises(TypeError, match="The object at 'tests.test_user_metadata.TEST_VAL' is not callable."): + with pytest.raises(TypeError, match=r"The object at 'tests\.test_user_metadata\.TEST_VAL' is not callable\."): dbbackup.utils.get_user_metadata(DEFAULT_META) @override_settings(DBBACKUP_BACKUP_METADATA_SETTER="tests.test_user_metadata.broken_setter") @@ -121,7 +125,7 @@ def test_metadata_setter_invalid_3(self): """Test that various setter missconfigurations do not work - Wrong return type.""" importlib.reload(dbbackup.settings) - with pytest.raises(ValueError, match="DBBACKUP_BACKUP_METADATA_SETTER must return a dictionary."): + with pytest.raises(ValueError, match=r"DBBACKUP_BACKUP_METADATA_SETTER must return a dictionary\."): dbbackup.utils.get_user_metadata(DEFAULT_META) @override_settings(DBBACKUP_BACKUP_METADATA_SETTER="tests.test_user_metadata.anotherbroken_setter") @@ -164,7 +168,7 @@ def test_metadata_validator_invalid_1(self): """Test that various validator missconfigurations do not work - Non-existent module.""" importlib.reload(dbbackup.settings) - with pytest.raises(ImportError, match="Could not import module 'non.existent': No module named 'non'"): + with pytest.raises(ImportError, match=r"Could not import module 'non\.existent': No module named 'non'"): dbbackup.utils.validate_user_metadata({"CSMT_VAL": TEST_VAL}) @override_settings(DBBACKUP_RESTORE_METADATA_VALIDATOR="tests.test_user_metadata.broken_validator") @@ -174,7 +178,6 @@ def test_metadata_validator_invalid_2(self): with pytest.raises(ValueError, match="Error during custom metadata validation:"): assert dbbackup.utils.validate_user_metadata({"CSMT_VAL": TEST_VAL}) is False - assert False, "Should not reach this point" @override_settings(DBBACKUP_RESTORE_METADATA_VALIDATOR="tests.test_user_metadata.non_bool_validator") def test_metadata_validator_invalid_3(self): @@ -183,4 +186,3 @@ def test_metadata_validator_invalid_3(self): with pytest.raises(TypeError, match="DBBACKUP_RESTORE_METADATA_VALIDATOR must return a boolean or None"): assert dbbackup.utils.validate_user_metadata({"CSMT_VAL": TEST_VAL}) is False - assert False, "Should not reach this point"