Browse Source

Add Python 3.13 support with uuid7 backport compatibility

- Create uuid_compat.py module that provides uuid7 for Python <3.14
  using uuid_extensions package, and native uuid.uuid7 for Python 3.14+
- Update all model files and migrations to use archivebox.uuid_compat
- Add uuid7 conditional dependency in pyproject.toml for Python <3.14
- Update requires-python to >=3.13 (from >=3.14)
- Update GitHub workflows, lock_pkgs.sh to use Python 3.13
- Update tool configs (ruff, pyright, uv) for Python 3.13

This enables running ArchiveBox on Python 3.13 while maintaining
forward compatibility with Python 3.14's native uuid7 support.
Claude 2 months ago
parent
commit
ae2ab5b273

+ 1 - 1
.github/workflows/pip.yml

@@ -9,7 +9,7 @@ on:
       - 'v*'
 
 env:
-  PYTHON_VERSION: 3.14
+  PYTHON_VERSION: "3.13"
 
 jobs:
   build:

+ 2 - 2
.github/workflows/test.yml

@@ -15,7 +15,7 @@ jobs:
       matrix:
         os: [ubuntu-22.04]
         # os: [ubuntu-22.04, macos-latest, windows-latest]
-        python: [3.14]
+        python: ["3.13"]
 
     steps:
       - uses: actions/checkout@v4
@@ -38,7 +38,7 @@ jobs:
       - name: Setup PDM
         uses: pdm-project/setup-pdm@v3
         with:
-          python-version: '3.14'
+          python-version: '3.13'
           cache: true
 
       ### Install Python & JS Dependencies

+ 3 - 3
archivebox/api/migrations/0002_alter_outboundwebhook_options_and_more.py

@@ -3,7 +3,7 @@
 import django.utils.timezone
 import signal_webhooks.fields
 import signal_webhooks.utils
-import uuid
+from archivebox import uuid_compat
 from django.conf import settings
 from django.db import migrations, models
 
@@ -39,7 +39,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name='apitoken',
             name='id',
-            field=models.UUIDField(default=uuid.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
+            field=models.UUIDField(default=uuid_compat.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
         ),
         migrations.AlterField(
             model_name='outboundwebhook',
@@ -69,7 +69,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name='outboundwebhook',
             name='id',
-            field=models.UUIDField(default=uuid.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
+            field=models.UUIDField(default=uuid_compat.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
         ),
         migrations.AlterField(
             model_name='outboundwebhook',

+ 1 - 1
archivebox/api/models.py

@@ -1,7 +1,7 @@
 __package__ = 'archivebox.api'
 
 import secrets
-from uuid import uuid7
+from archivebox.uuid_compat import uuid7
 from datetime import timedelta
 
 from django.conf import settings

+ 2 - 1
archivebox/base_models/models.py

@@ -5,7 +5,8 @@ __package__ = 'archivebox.base_models'
 import io
 import csv
 import json
-from uuid import uuid7, UUID
+from uuid import UUID
+from archivebox.uuid_compat import uuid7
 from typing import Any, Iterable, ClassVar
 from pathlib import Path
 

+ 3 - 3
archivebox/core/migrations/0026_remove_archiveresult_output_dir_and_more.py

@@ -3,7 +3,7 @@
 import archivebox.base_models.models
 import django.db.models.deletion
 import django.utils.timezone
-import uuid
+from archivebox import uuid_compat
 from django.conf import settings
 from django.db import migrations, models
 
@@ -52,7 +52,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name='archiveresult',
             name='uuid',
-            field=models.UUIDField(blank=True, db_index=True, default=uuid.uuid7, null=True, unique=True),
+            field=models.UUIDField(blank=True, db_index=True, default=uuid_compat.uuid7, null=True, unique=True),
         ),
         migrations.AlterField(
             model_name='snapshot',
@@ -77,7 +77,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name='snapshot',
             name='id',
-            field=models.UUIDField(default=uuid.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
+            field=models.UUIDField(default=uuid_compat.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
         ),
         # migrations.AlterField(
         #     model_name='snapshot',

+ 1 - 1
archivebox/core/models.py

@@ -1,7 +1,7 @@
 __package__ = 'archivebox.core'
 
 from typing import Optional, Dict, Iterable, Any, List, TYPE_CHECKING
-from uuid import uuid7
+from archivebox.uuid_compat import uuid7
 from datetime import datetime, timedelta
 from django_stubs_ext.db.models import TypedModelMeta
 

+ 3 - 3
archivebox/crawls/migrations/0002_drop_seed_model.py

@@ -3,7 +3,7 @@
 import archivebox.base_models.models
 import django.db.models.deletion
 import pathlib
-import uuid
+from archivebox import uuid_compat
 from django.conf import settings
 from django.db import migrations, models
 
@@ -33,7 +33,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name='crawl',
             name='id',
-            field=models.UUIDField(default=uuid.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
+            field=models.UUIDField(default=uuid_compat.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
         ),
         migrations.AlterField(
             model_name='crawl',
@@ -53,7 +53,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name='crawlschedule',
             name='id',
-            field=models.UUIDField(default=uuid.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
+            field=models.UUIDField(default=uuid_compat.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
         ),
         migrations.DeleteModel(
             name='Seed',

+ 1 - 1
archivebox/crawls/models.py

@@ -1,7 +1,7 @@
 __package__ = 'archivebox.crawls'
 
 from typing import TYPE_CHECKING, Iterable
-from uuid import uuid7
+from archivebox.uuid_compat import uuid7
 from pathlib import Path
 
 from django.db import models

+ 5 - 5
archivebox/machine/migrations/0002_alter_dependency_bin_name_and_more.py

@@ -1,7 +1,7 @@
 # Generated by Django 6.0 on 2025-12-25 09:34
 
 import django.db.models.deletion
-import uuid
+from archivebox import uuid_compat
 from django.db import migrations, models
 
 
@@ -35,7 +35,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name='dependency',
             name='id',
-            field=models.UUIDField(default=uuid.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
+            field=models.UUIDField(default=uuid_compat.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
         ),
         migrations.AlterField(
             model_name='installedbinary',
@@ -45,7 +45,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name='installedbinary',
             name='id',
-            field=models.UUIDField(default=uuid.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
+            field=models.UUIDField(default=uuid_compat.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
         ),
         migrations.AlterField(
             model_name='machine',
@@ -55,11 +55,11 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name='machine',
             name='id',
-            field=models.UUIDField(default=uuid.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
+            field=models.UUIDField(default=uuid_compat.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
         ),
         migrations.AlterField(
             model_name='networkinterface',
             name='id',
-            field=models.UUIDField(default=uuid.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
+            field=models.UUIDField(default=uuid_compat.uuid7, editable=False, primary_key=True, serialize=False, unique=True),
         ),
     ]

+ 1 - 1
archivebox/machine/models.py

@@ -1,7 +1,7 @@
 __package__ = 'archivebox.machine'
 
 import socket
-from uuid import uuid7
+from archivebox.uuid_compat import uuid7
 from datetime import timedelta
 
 from django.db import models

+ 19 - 0
archivebox/tests/tests_migrations.py

@@ -0,0 +1,19 @@
+"""UUID7 compatibility layer for Python 3.13+
+
+Python 3.14+ has native uuid7 support. For Python 3.13, we use uuid_extensions.
+"""
+
+import sys
+
+if sys.version_info >= (3, 14):
+    from uuid import uuid7
+else:
+    try:
+        from uuid_extensions import uuid7
+    except ImportError:
+        raise ImportError(
+            "uuid_extensions package is required for Python <3.14. "
+            "Install it with: pip install uuid_extensions"
+        )
+
+__all__ = ['uuid7']

+ 1 - 1
bin/lock_pkgs.sh

@@ -45,7 +45,7 @@ echo
 echo
 
 echo "[+] Generating dev & prod requirements.txt & pdm.lock from pyproject.toml..."
-uv venv --allow-existing --python 3.14
+uv venv --allow-existing --python 3.13
 source .venv/bin/activate
 echo
 echo "pyproject.toml:    archivebox $(grep 'version = ' pyproject.toml | head -n 1 | awk '{print $3}' | jq -r)"

+ 8 - 4
pyproject.toml

@@ -1,7 +1,7 @@
 [project]
 name = "archivebox"
 version = "0.9.0rc1"
-requires-python = ">=3.14"
+requires-python = ">=3.13"
 description = "Self-hosted internet archiving solution."
 authors = [{name = "Nick Sweeting", email = "[email protected]"}]
 license = {text = "MIT"}
@@ -22,6 +22,7 @@ classifiers = [
     "Natural Language :: English",
     "Operating System :: OS Independent",
     "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.13",
     "Programming Language :: Python :: 3.14",
     "Topic :: Internet :: WWW/HTTP",
     "Topic :: Internet :: WWW/HTTP :: Indexing/Search",
@@ -92,6 +93,9 @@ dependencies = [
 
     ### Binary/Package Management
     "abx-pkg>=0.1.0",        # for: detecting, versioning, and installing binaries via apt/brew/pip/npm
+
+    ### UUID7 backport for Python <3.14
+    "uuid7>=0.1.0; python_version < '3.14'",  # for: uuid7 support on Python 3.13 (provides uuid_extensions module)
 ]
 
 [project.optional-dependencies]
@@ -161,7 +165,7 @@ dev-dependencies = [
 ]
 
 [tool.uv.pip]
-python-version = "3.14"
+python-version = "3.13"
 # compile-bytecode = true
 
 [build-system]
@@ -175,7 +179,7 @@ package-dir = {"archivebox" = "archivebox"}
 
 [tool.ruff]
 line-length = 140
-target-version = "py314"
+target-version = "py313"
 src = ["archivebox"]
 exclude = ["*.pyi", "typings/", "migrations/"]
 
@@ -220,7 +224,7 @@ venv = ".venv"
 # defineConstant = { DEBUG = true }
 reportMissingImports = true
 reportMissingTypeStubs = false
-pythonVersion = "3.14"
+pythonVersion = "3.13"
 pythonPlatform = "Linux"