From 5fde6f480cef64b8d5ef9fc31da3883b37730317 Mon Sep 17 00:00:00 2001 From: Roger Hunwicks Date: Tue, 27 Jan 2026 21:51:08 -0500 Subject: [PATCH 1/2] Add support for Python 3.14 and Django 6.0 Also updates linting tools to current versions. --- .github/workflows/test.yml | 4 ++-- binary_database_files/apps.py | 7 +++++-- binary_database_files/models.py | 2 +- binary_database_files/settings.py | 12 ++++++------ binary_database_files/storage.py | 1 + binary_database_files/tests/tests.py | 10 +++++----- binary_database_files/utils.py | 6 +++--- lint.sh | 2 +- pip-requirements.txt | 2 +- pyproject.toml | 8 ++++---- tox.ini | 5 ++++- 11 files changed, 33 insertions(+), 26 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 28d220d..c98fb17 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 @@ -21,7 +21,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install tox tox-gh-actions black==22.12.0 isort==5.12.0 ruff==0.0.270 + pip install tox tox-gh-actions black==26.1.0 isort==7.0.0 ruff==0.14.14 - name: Lint run: ./lint.sh diff --git a/binary_database_files/apps.py b/binary_database_files/apps.py index f869711..b798732 100644 --- a/binary_database_files/apps.py +++ b/binary_database_files/apps.py @@ -14,7 +14,10 @@ class DatabaseFilesAppConfig(AppConfig): @register() def check_settings(app_configs, **kwargs): errors = [] - if not settings.MEDIA_ROOT and settings.DATABASE_FILES_URL_METHOD_NAME == "URL_METHOD_1": + if ( + not settings.MEDIA_ROOT + and settings.DATABASE_FILES_URL_METHOD_NAME == "URL_METHOD_1" + ): errors.append( Error( "MEDIA_ROOT is not defined, yet you are using URL_METHOD_1 which serves media files from the filesystem.", @@ -30,4 +33,4 @@ def check_settings(app_configs, **kwargs): id="binary_database_files.E002", ) ) - return errors \ No newline at end of file + return errors diff --git a/binary_database_files/models.py b/binary_database_files/models.py index 0c48e54..3848a04 100644 --- a/binary_database_files/models.py +++ b/binary_database_files/models.py @@ -82,7 +82,7 @@ def dump_files(cls, debug=True, verbose=False): if verbose: print("Checking %i total files..." % (total,)) i = 0 - for (file_id, name, content_hash) in q.iterator(): + for file_id, name, content_hash in q.iterator(): i += 1 if verbose and not i % 100: print("%i of %i" % (i, total)) diff --git a/binary_database_files/settings.py b/binary_database_files/settings.py index 5b58055..58f1356 100644 --- a/binary_database_files/settings.py +++ b/binary_database_files/settings.py @@ -40,9 +40,9 @@ def URL_METHOD_2(name): DATABASE_FILES_URL_METHOD = settings.DATABASE_FILES_URL_METHOD = method -DB_FILES_DEFAULT_ENFORCE_ENCODING = ( - settings.DB_FILES_DEFAULT_ENFORCE_ENCODING -) = getattr(settings, "DB_FILES_DEFAULT_ENFORCE_ENCODING", True) +DB_FILES_DEFAULT_ENFORCE_ENCODING = settings.DB_FILES_DEFAULT_ENFORCE_ENCODING = ( + getattr(settings, "DB_FILES_DEFAULT_ENFORCE_ENCODING", True) +) DB_FILES_DEFAULT_ENCODING = settings.DB_FILES_DEFAULT_ENCODING = getattr( settings, "DB_FILES_DEFAULT_ENCODING", "ascii" @@ -52,8 +52,8 @@ def URL_METHOD_2(name): settings, "DB_FILES_DEFAULT_ERROR_METHOD", "ignore" ) -DB_FILES_DEFAULT_HASH_FN_TEMPLATE = ( - settings.DB_FILES_DEFAULT_HASH_FN_TEMPLATE -) = getattr(settings, "DB_FILES_DEFAULT_HASH_FN_TEMPLATE", "%s.hash") +DB_FILES_DEFAULT_HASH_FN_TEMPLATE = settings.DB_FILES_DEFAULT_HASH_FN_TEMPLATE = ( + getattr(settings, "DB_FILES_DEFAULT_HASH_FN_TEMPLATE", "%s.hash") +) DEFAULT_AUTO_FIELD = "django.db.models.AutoField" diff --git a/binary_database_files/storage.py b/binary_database_files/storage.py index 27a8ae9..fb8992b 100644 --- a/binary_database_files/storage.py +++ b/binary_database_files/storage.py @@ -1,4 +1,5 @@ """Custom storage backend that stores files in the database to facilitate scaling.""" + import os from io import BytesIO, UnsupportedOperation diff --git a/binary_database_files/tests/tests.py b/binary_database_files/tests/tests.py index 4d2f7f0..e5535f4 100644 --- a/binary_database_files/tests/tests.py +++ b/binary_database_files/tests/tests.py @@ -434,17 +434,17 @@ def test_serve_file_from_database(self): self.assertEqual(response["content-length"], "10") @override_settings( - MEDIA_ROOT=global_settings.MEDIA_ROOT, - DATABASE_FILES_URL_METHOD_NAME = "URL_METHOD_1", # default - DB_FILES_AUTO_EXPORT_DB_TO_FS = True, # default + MEDIA_ROOT=global_settings.MEDIA_ROOT, + DATABASE_FILES_URL_METHOD_NAME="URL_METHOD_1", # default + DB_FILES_AUTO_EXPORT_DB_TO_FS=True, # default ) def test_refuse_unset_media_root(self): # regression test for issue #65 where unset MEDIA_ROOT would result in serving the source code message_a = "(binary_database_files.E001) MEDIA_ROOT is not defined, yet you are using URL_METHOD_1 which serves media files from the filesystem" with self.assertRaisesMessage(SystemCheckError, message_a): - call_command("check") + call_command("check") message_b = "(binary_database_files.E002) MEDIA_ROOT is not defined, yet you are using DB_FILES_AUTO_EXPORT_DB_TO_FS which copies media files from the filesystem." with self.assertRaisesMessage(SystemCheckError, message_b): - call_command("check") + call_command("check") diff --git a/binary_database_files/utils.py b/binary_database_files/utils.py index e93cf0a..4604bce 100644 --- a/binary_database_files/utils.py +++ b/binary_database_files/utils.py @@ -74,7 +74,7 @@ def create_directory_for_file(full_path): else: os.makedirs(directory, exist_ok=True) except FileExistsError: - raise FileExistsError('%s exists and is not a directory.' % directory) + raise FileExistsError("%s exists and is not a directory." % directory) def write_file(name, content, overwrite=False): @@ -110,7 +110,7 @@ def write_file(name, content, overwrite=False): shutil.chown(fqfn, user=uname, group=gname) shutil.chown(hash_fn, user=uname, group=gname) except OSError as e: - logger.warning('Could not chown file %s: %s', fqfn, e) + logger.warning("Could not chown file %s: %s", fqfn, e) # Set permissions. perms = getattr(settings, "DATABASE_FILES_PERMS", settings.FILE_UPLOAD_PERMISSIONS) @@ -119,7 +119,7 @@ def write_file(name, content, overwrite=False): os.chmod(fqfn, perms) os.chmod(hash_fn, perms) except OSError as e: - logger.warning('Could not chmod file %s: %s', fqfn, e) + logger.warning("Could not chmod file %s: %s", fqfn, e) def get_file_hash(fin, force_encoding=None, encoding=None, errors=None, chunk_size=128): diff --git a/lint.sh b/lint.sh index 0683796..5dd8daa 100755 --- a/lint.sh +++ b/lint.sh @@ -1,4 +1,4 @@ #!/bin/bash -ruff binary_database_files +ruff check binary_database_files black --check binary_database_files isort --check binary_database_files \ No newline at end of file diff --git a/pip-requirements.txt b/pip-requirements.txt index 56059dd..dad414b 100644 --- a/pip-requirements.txt +++ b/pip-requirements.txt @@ -1 +1 @@ -Django>=2.2,<6 +Django>=2.2,<=6.0 diff --git a/pyproject.toml b/pyproject.toml index 61441d9..a6fe4ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.ruff] line-length = 119 # Allow longer lines for ruff than black and isort, for comments and docstrings -target-version = 'py310' +target-version = 'py314' exclude = [ '.eggs', # exclude a few common directories in the '.git', # root of the project @@ -18,16 +18,16 @@ exclude = [ 'dist', ] -[tool.ruff.mccabe] +[lint.mccabe] # Unlike Flake8, default to a complexity level of 10. max-complexity = 10 -[tool.ruff.flake8-quotes] +[lint.flake8-quotes] docstring-quotes = "double" [tool.black] line-length = 88 -target-version = ['py310'] +target-version = ['py314'] include = '\.pyi?$' exclude = ''' ( diff --git a/tox.ini b/tox.ini index d0d558a..8de44b0 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ ignore = W503, E203 # See https://github.com/PyCQA/pycodestyle/issues/373 max-line-length=160 [tox] -envlist = py{38,39,310,311,312}-django{42},py{310,311,312}-django{50},py{310,311,312,313}-django{51,52} +envlist = py{38,39,310,311,312}-django{42},py{310,311,312}-django{50},py{310,311,312,313}-django{51,52},py{314}-django{60} recreate = True [gh-actions] @@ -15,6 +15,7 @@ python = 3.11: py311 3.12: py312 3.13: py313 + 3.14: py314 [testenv] basepython = @@ -24,10 +25,12 @@ basepython = py311: python3.11 py312: python3.12 py313: python3.13 + py314: python3.14 deps = -r{toxinidir}/pip-requirements-test.txt django42: Django==4.2.* django50: Django==5.0.* django51: Django==5.1.* django52: Django==5.2.* + django60: Django==6.0.* commands = django-admin test --traceback --pythonpath=. --settings=binary_database_files.tests.settings binary_database_files.tests.tests.DatabaseFilesTestCase{env:TESTNAME:} From ada9e49dc03944ba6fa0ec2bb7ec070f3c3909b8 Mon Sep 17 00:00:00 2001 From: Roger Hunwicks Date: Tue, 27 Jan 2026 21:57:45 -0500 Subject: [PATCH 2/2] Remove support for python<3.10 --- .github/workflows/test.yml | 2 +- README.md | 5 ++--- tox.ini | 6 +----- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c98fb17..502cd43 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index 43e2c87..b638a46 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Based upon django-database-files by [Kimetrica](https://github.com/kimetrica/dja Requires: - * Django 2.2 - 4.0 + * Django 4.2+ Installation ------------ @@ -103,7 +103,7 @@ Code should be linted with: Tests require the Python development headers to be installed, which you can install on Ubuntu with: - sudo apt-get install python3.12-minimal python3.12-dev + sudo apt-get install python3.14-minimal python3.14-dev To run unittests across multiple Python versions, install: @@ -126,4 +126,3 @@ To build and deploy a versioned package to PyPI, verify [all unittests are passi python setup.py sdist bdist_wheel twine check dist/* twine upload dist/* - diff --git a/tox.ini b/tox.ini index 8de44b0..f1cb7f2 100644 --- a/tox.ini +++ b/tox.ini @@ -4,13 +4,11 @@ ignore = W503, E203 # See https://github.com/PyCQA/pycodestyle/issues/373 max-line-length=160 [tox] -envlist = py{38,39,310,311,312}-django{42},py{310,311,312}-django{50},py{310,311,312,313}-django{51,52},py{314}-django{60} +envlist = py{310,311,312}-django{42},py{310,311,312}-django{50},py{310,311,312,313}-django{51,52},py{314}-django{60} recreate = True [gh-actions] python = - 3.8: py38 - 3.9: py39 3.10: py310 3.11: py311 3.12: py312 @@ -19,8 +17,6 @@ python = [testenv] basepython = - py38: python3.8 - py39: python3.9 py310: python3.10 py311: python3.11 py312: python3.12