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.
This commit is contained in:
Claude
2025-12-27 01:07:30 +00:00
parent cff4077c23
commit ae2ab5b273
15 changed files with 51 additions and 27 deletions

2
.github/workflows/pip.yml vendored Normal file → Executable file
View File

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

4
.github/workflows/test.yml vendored Normal file → Executable file
View File

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

View File

@@ -3,7 +3,7 @@
import django.utils.timezone import django.utils.timezone
import signal_webhooks.fields import signal_webhooks.fields
import signal_webhooks.utils import signal_webhooks.utils
import uuid from archivebox import uuid_compat
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@@ -39,7 +39,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='apitoken', model_name='apitoken',
name='id', 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( migrations.AlterField(
model_name='outboundwebhook', model_name='outboundwebhook',
@@ -69,7 +69,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='outboundwebhook', model_name='outboundwebhook',
name='id', 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( migrations.AlterField(
model_name='outboundwebhook', model_name='outboundwebhook',

2
archivebox/api/models.py Normal file → Executable file
View File

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

3
archivebox/base_models/models.py Normal file → Executable file
View File

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

View File

@@ -3,7 +3,7 @@
import archivebox.base_models.models import archivebox.base_models.models
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
import uuid from archivebox import uuid_compat
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@@ -52,7 +52,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='archiveresult', model_name='archiveresult',
name='uuid', 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( migrations.AlterField(
model_name='snapshot', model_name='snapshot',
@@ -77,7 +77,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='snapshot', model_name='snapshot',
name='id', 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( # migrations.AlterField(
# model_name='snapshot', # model_name='snapshot',

2
archivebox/core/models.py Normal file → Executable file
View File

@@ -1,7 +1,7 @@
__package__ = 'archivebox.core' __package__ = 'archivebox.core'
from typing import Optional, Dict, Iterable, Any, List, TYPE_CHECKING 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 datetime import datetime, timedelta
from django_stubs_ext.db.models import TypedModelMeta from django_stubs_ext.db.models import TypedModelMeta

6
archivebox/crawls/migrations/0002_drop_seed_model.py Normal file → Executable file
View File

@@ -3,7 +3,7 @@
import archivebox.base_models.models import archivebox.base_models.models
import django.db.models.deletion import django.db.models.deletion
import pathlib import pathlib
import uuid from archivebox import uuid_compat
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@@ -33,7 +33,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='crawl', model_name='crawl',
name='id', 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( migrations.AlterField(
model_name='crawl', model_name='crawl',
@@ -53,7 +53,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='crawlschedule', model_name='crawlschedule',
name='id', 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( migrations.DeleteModel(
name='Seed', name='Seed',

2
archivebox/crawls/models.py Normal file → Executable file
View File

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

View File

@@ -1,7 +1,7 @@
# Generated by Django 6.0 on 2025-12-25 09:34 # Generated by Django 6.0 on 2025-12-25 09:34
import django.db.models.deletion import django.db.models.deletion
import uuid from archivebox import uuid_compat
from django.db import migrations, models from django.db import migrations, models
@@ -35,7 +35,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='dependency', model_name='dependency',
name='id', 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( migrations.AlterField(
model_name='installedbinary', model_name='installedbinary',
@@ -45,7 +45,7 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='installedbinary', model_name='installedbinary',
name='id', 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( migrations.AlterField(
model_name='machine', model_name='machine',
@@ -55,11 +55,11 @@ class Migration(migrations.Migration):
migrations.AlterField( migrations.AlterField(
model_name='machine', model_name='machine',
name='id', 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( migrations.AlterField(
model_name='networkinterface', model_name='networkinterface',
name='id', 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),
), ),
] ]

2
archivebox/machine/models.py Normal file → Executable file
View File

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

0
archivebox/tests/tests_migrations.py Normal file → Executable file
View File

19
archivebox/uuid_compat.py Executable file
View File

@@ -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']

View File

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

12
pyproject.toml Normal file → Executable file
View File

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