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
No known key found for this signature in database
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*'
env:
PYTHON_VERSION: 3.14
PYTHON_VERSION: "3.13"
jobs:
build:

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

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

View File

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

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

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

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

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

View File

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

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

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

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 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',

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

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

View File

@ -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),
),
]

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

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

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 "[+] 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)"

12
pyproject.toml Normal file → Executable file
View File

@ -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 = "pyproject.toml@archivebox.io"}]
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"