mirror of
https://github.com/ArchiveBox/ArchiveBox
synced 2024-11-10 06:34:16 +00:00
api v1
This commit is contained in:
parent
1d49bee90b
commit
5f9aac18f2
15 changed files with 2877 additions and 2395 deletions
0
archivebox/api/__init__.py
Normal file
0
archivebox/api/__init__.py
Normal file
5
archivebox/api/apps.py
Normal file
5
archivebox/api/apps.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class APIConfig(AppConfig):
|
||||||
|
name = 'api'
|
184
archivebox/api/archive.py
Normal file
184
archivebox/api/archive.py
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
# archivebox_api.py
|
||||||
|
from typing import List, Optional
|
||||||
|
from enum import Enum
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from ninja import Router
|
||||||
|
from main import (
|
||||||
|
add,
|
||||||
|
remove,
|
||||||
|
update,
|
||||||
|
list_all,
|
||||||
|
ONLY_NEW,
|
||||||
|
) # Assuming these functions are defined in main.py
|
||||||
|
|
||||||
|
|
||||||
|
# Schemas
|
||||||
|
|
||||||
|
class StatusChoices(str, Enum):
|
||||||
|
indexed = 'indexed'
|
||||||
|
archived = 'archived'
|
||||||
|
unarchived = 'unarchived'
|
||||||
|
present = 'present'
|
||||||
|
valid = 'valid'
|
||||||
|
invalid = 'invalid'
|
||||||
|
duplicate = 'duplicate'
|
||||||
|
orphaned = 'orphaned'
|
||||||
|
corrupted = 'corrupted'
|
||||||
|
unrecognized = 'unrecognized'
|
||||||
|
|
||||||
|
|
||||||
|
class AddURLSchema(BaseModel):
|
||||||
|
urls: List[str]
|
||||||
|
tag: str = ""
|
||||||
|
depth: int = 0
|
||||||
|
update: bool = not ONLY_NEW # Default to the opposite of ONLY_NEW
|
||||||
|
update_all: bool = False
|
||||||
|
index_only: bool = False
|
||||||
|
overwrite: bool = False
|
||||||
|
init: bool = False
|
||||||
|
extractors: str = ""
|
||||||
|
parser: str = "auto"
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveURLSchema(BaseModel):
|
||||||
|
yes: bool = False
|
||||||
|
delete: bool = False
|
||||||
|
before: Optional[float] = None
|
||||||
|
after: Optional[float] = None
|
||||||
|
filter_type: str = "exact"
|
||||||
|
filter_patterns: Optional[List[str]] = None
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateSchema(BaseModel):
|
||||||
|
resume: Optional[float] = None
|
||||||
|
only_new: Optional[bool] = None
|
||||||
|
index_only: Optional[bool] = False
|
||||||
|
overwrite: Optional[bool] = False
|
||||||
|
before: Optional[float] = None
|
||||||
|
after: Optional[float] = None
|
||||||
|
status: Optional[StatusChoices] = None
|
||||||
|
filter_type: Optional[str] = 'exact'
|
||||||
|
filter_patterns: Optional[List[str]] = None
|
||||||
|
extractors: Optional[str] = ""
|
||||||
|
|
||||||
|
|
||||||
|
class ListAllSchema(BaseModel):
|
||||||
|
filter_patterns: Optional[List[str]] = None
|
||||||
|
filter_type: str = 'exact'
|
||||||
|
status: Optional[StatusChoices] = None
|
||||||
|
after: Optional[float] = None
|
||||||
|
before: Optional[float] = None
|
||||||
|
sort: Optional[str] = None
|
||||||
|
csv: Optional[str] = None
|
||||||
|
json: bool = False
|
||||||
|
html: bool = False
|
||||||
|
with_headers: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
# API Router
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/add", response={200: dict})
|
||||||
|
def api_add(request, payload: AddURLSchema):
|
||||||
|
try:
|
||||||
|
result = add(
|
||||||
|
urls=payload.urls,
|
||||||
|
tag=payload.tag,
|
||||||
|
depth=payload.depth,
|
||||||
|
update=payload.update,
|
||||||
|
update_all=payload.update_all,
|
||||||
|
index_only=payload.index_only,
|
||||||
|
overwrite=payload.overwrite,
|
||||||
|
init=payload.init,
|
||||||
|
extractors=payload.extractors,
|
||||||
|
parser=payload.parser,
|
||||||
|
)
|
||||||
|
# Currently the add function returns a list of ALL items in the DB, ideally only return new items
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": "URLs added successfully.",
|
||||||
|
"result": str(result),
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
# Handle exceptions raised by the add function or during processing
|
||||||
|
return {"status": "error", "message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/remove", response={200: dict})
|
||||||
|
def api_remove(request, payload: RemoveURLSchema):
|
||||||
|
try:
|
||||||
|
result = remove(
|
||||||
|
yes=payload.yes,
|
||||||
|
delete=payload.delete,
|
||||||
|
before=payload.before,
|
||||||
|
after=payload.after,
|
||||||
|
filter_type=payload.filter_type,
|
||||||
|
filter_patterns=payload.filter_patterns,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": "URLs removed successfully.",
|
||||||
|
"result": result,
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
# Handle exceptions raised by the remove function or during processing
|
||||||
|
return {"status": "error", "message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/update", response={200: dict})
|
||||||
|
def api_update(request, payload: UpdateSchema):
|
||||||
|
try:
|
||||||
|
result = update(
|
||||||
|
resume=payload.resume,
|
||||||
|
only_new=payload.only_new,
|
||||||
|
index_only=payload.index_only,
|
||||||
|
overwrite=payload.overwrite,
|
||||||
|
before=payload.before,
|
||||||
|
after=payload.after,
|
||||||
|
status=payload.status,
|
||||||
|
filter_type=payload.filter_type,
|
||||||
|
filter_patterns=payload.filter_patterns,
|
||||||
|
extractors=payload.extractors,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": "Archive updated successfully.",
|
||||||
|
"result": result,
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
# Handle exceptions raised by the update function or during processing
|
||||||
|
return {"status": "error", "message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/list_all", response={200: dict})
|
||||||
|
def api_list_all(request, payload: ListAllSchema):
|
||||||
|
try:
|
||||||
|
result = list_all(
|
||||||
|
filter_patterns=payload.filter_patterns,
|
||||||
|
filter_type=payload.filter_type,
|
||||||
|
status=payload.status,
|
||||||
|
after=payload.after,
|
||||||
|
before=payload.before,
|
||||||
|
sort=payload.sort,
|
||||||
|
csv=payload.csv,
|
||||||
|
json=payload.json,
|
||||||
|
html=payload.html,
|
||||||
|
with_headers=payload.with_headers,
|
||||||
|
)
|
||||||
|
# TODO: This is kind of bad, make the format a choice field
|
||||||
|
if payload.json:
|
||||||
|
return {"status": "success", "format": "json", "data": result}
|
||||||
|
elif payload.html:
|
||||||
|
return {"status": "success", "format": "html", "data": result}
|
||||||
|
elif payload.csv:
|
||||||
|
return {"status": "success", "format": "csv", "data": result}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"message": "List generated successfully.",
|
||||||
|
"data": result,
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
# Handle exceptions raised by the list_all function or during processing
|
||||||
|
return {"status": "error", "message": str(e)}
|
48
archivebox/api/auth.py
Normal file
48
archivebox/api/auth.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
from django.contrib.auth import authenticate
|
||||||
|
from ninja import Form, Router, Schema
|
||||||
|
from ninja.security import HttpBearer
|
||||||
|
|
||||||
|
from api.models import Token
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalAuth(HttpBearer):
|
||||||
|
def authenticate(self, request, token):
|
||||||
|
try:
|
||||||
|
return Token.objects.get(token=token).user
|
||||||
|
except Token.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AuthSchema(Schema):
|
||||||
|
email: str
|
||||||
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/authenticate", auth=None) # overriding global auth
|
||||||
|
def get_token(request, auth_data: AuthSchema):
|
||||||
|
user = authenticate(username=auth_data.email, password=auth_data.password)
|
||||||
|
if user:
|
||||||
|
# Assuming a user can have multiple tokens and you want to create a new one every time
|
||||||
|
new_token = Token.objects.create(user=user)
|
||||||
|
return {"token": new_token.token, "expires": new_token.expiry_as_iso8601}
|
||||||
|
else:
|
||||||
|
return {"error": "Invalid credentials"}
|
||||||
|
|
||||||
|
|
||||||
|
class TokenValidationSchema(Schema):
|
||||||
|
token: str
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/validate_token", auth=None) # No authentication required for this endpoint
|
||||||
|
def validate_token(request, token_data: TokenValidationSchema):
|
||||||
|
try:
|
||||||
|
# Attempt to authenticate using the provided token
|
||||||
|
user = GlobalAuth().authenticate(request, token_data.token)
|
||||||
|
if user:
|
||||||
|
return {"status": "valid"}
|
||||||
|
else:
|
||||||
|
return {"status": "invalid"}
|
||||||
|
except Token.DoesNotExist:
|
||||||
|
return {"status": "invalid"}
|
28
archivebox/api/migrations/0001_initial.py
Normal file
28
archivebox/api/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 3.1.14 on 2024-04-09 18:52
|
||||||
|
|
||||||
|
import api.models
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Token',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('token', models.CharField(default=auth.models.hex_uuid, max_length=32, unique=True)),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('expiry', models.DateTimeField(blank=True, null=True)),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tokens', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
0
archivebox/api/migrations/__init__.py
Normal file
0
archivebox/api/migrations/__init__.py
Normal file
30
archivebox/api/models.py
Normal file
30
archivebox/api/models.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import uuid
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
def hex_uuid():
|
||||||
|
return uuid.uuid4().hex
|
||||||
|
|
||||||
|
|
||||||
|
class Token(models.Model):
|
||||||
|
user = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="tokens"
|
||||||
|
)
|
||||||
|
token = models.CharField(max_length=32, default=hex_uuid, unique=True)
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
expiry = models.DateTimeField(null=True, blank=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def expiry_as_iso8601(self):
|
||||||
|
"""Returns the expiry date of the token in ISO 8601 format or a date 100 years in the future if none."""
|
||||||
|
expiry_date = (
|
||||||
|
self.expiry if self.expiry else timezone.now() + timedelta(days=365 * 100)
|
||||||
|
)
|
||||||
|
return expiry_date.isoformat()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.token
|
27
archivebox/api/tests.py
Normal file
27
archivebox/api/tests.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
from ninja.testing import TestClient
|
||||||
|
from archivebox.api.archive import router as archive_router
|
||||||
|
|
||||||
|
class ArchiveBoxAPITestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = TestClient(archive_router)
|
||||||
|
|
||||||
|
def test_add_endpoint(self):
|
||||||
|
response = self.client.post("/add", json={"urls": ["http://example.com"], "tag": "test"})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.json()["status"], "success")
|
||||||
|
|
||||||
|
def test_remove_endpoint(self):
|
||||||
|
response = self.client.post("/remove", json={"filter_patterns": ["http://example.com"]})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.json()["status"], "success")
|
||||||
|
|
||||||
|
def test_update_endpoint(self):
|
||||||
|
response = self.client.post("/update", json={})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.json()["status"], "success")
|
||||||
|
|
||||||
|
def test_list_all_endpoint(self):
|
||||||
|
response = self.client.post("/list_all", json={})
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertTrue("success" in response.json()["status"])
|
|
@ -61,6 +61,7 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
|
|
||||||
'core',
|
'core',
|
||||||
|
'api',
|
||||||
|
|
||||||
'django_extensions',
|
'django_extensions',
|
||||||
]
|
]
|
||||||
|
|
|
@ -8,6 +8,13 @@ from django.views.generic.base import RedirectView
|
||||||
|
|
||||||
from core.views import HomepageView, SnapshotView, PublicIndexView, AddView, HealthCheckView
|
from core.views import HomepageView, SnapshotView, PublicIndexView, AddView, HealthCheckView
|
||||||
|
|
||||||
|
from ninja import NinjaAPI
|
||||||
|
from api.auth import GlobalAuth
|
||||||
|
|
||||||
|
api = NinjaAPI(auth=GlobalAuth())
|
||||||
|
api.add_router("/auth/", "api.auth.router")
|
||||||
|
api.add_router("/archive/", "api.archive.router")
|
||||||
|
|
||||||
# GLOBAL_CONTEXT doesn't work as-is, disabled for now: https://github.com/ArchiveBox/ArchiveBox/discussions/1306
|
# GLOBAL_CONTEXT doesn't work as-is, disabled for now: https://github.com/ArchiveBox/ArchiveBox/discussions/1306
|
||||||
# from config import VERSION, VERSIONS_AVAILABLE, CAN_UPGRADE
|
# from config import VERSION, VERSIONS_AVAILABLE, CAN_UPGRADE
|
||||||
# GLOBAL_CONTEXT = {'VERSION': VERSION, 'VERSIONS_AVAILABLE': VERSIONS_AVAILABLE, 'CAN_UPGRADE': CAN_UPGRADE}
|
# GLOBAL_CONTEXT = {'VERSION': VERSION, 'VERSIONS_AVAILABLE': VERSIONS_AVAILABLE, 'CAN_UPGRADE': CAN_UPGRADE}
|
||||||
|
@ -36,6 +43,8 @@ urlpatterns = [
|
||||||
path('accounts/', include('django.contrib.auth.urls')),
|
path('accounts/', include('django.contrib.auth.urls')),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
|
||||||
|
path("api/", api.urls),
|
||||||
|
|
||||||
# do not add extra_context like this as not all admin views (e.g. ModelAdmin.autocomplete_view accept extra kwargs)
|
# do not add extra_context like this as not all admin views (e.g. ModelAdmin.autocomplete_view accept extra kwargs)
|
||||||
# path('admin/', admin.site.urls, {'extra_context': GLOBAL_CONTEXT}),
|
# path('admin/', admin.site.urls, {'extra_context': GLOBAL_CONTEXT}),
|
||||||
|
|
||||||
|
|
0
archivebox/index.sqlite3
Normal file
0
archivebox/index.sqlite3
Normal file
4764
package-lock.json
generated
4764
package-lock.json
generated
File diff suppressed because it is too large
Load diff
106
pdm.lock
106
pdm.lock
|
@ -5,7 +5,7 @@
|
||||||
groups = ["default", "dev", "ldap", "sonic"]
|
groups = ["default", "dev", "ldap", "sonic"]
|
||||||
strategy = ["cross_platform"]
|
strategy = ["cross_platform"]
|
||||||
lock_version = "4.4.1"
|
lock_version = "4.4.1"
|
||||||
content_hash = "sha256:4ba1c25daa30a36c5b3ffdb563d5024c2ab15042758f4fbc3f375dedb35d1bdf"
|
content_hash = "sha256:dc660e955b3c3d913b044e8e3580f951cf8701aa6d4a8ca233114f6b4230e3fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alabaster"
|
name = "alabaster"
|
||||||
|
@ -17,6 +17,16 @@ files = [
|
||||||
{file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"},
|
{file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-types"
|
||||||
|
version = "0.6.0"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "Reusable constraint types to use with typing.Annotated"
|
||||||
|
files = [
|
||||||
|
{file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
|
||||||
|
{file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "asgiref"
|
name = "asgiref"
|
||||||
version = "3.7.2"
|
version = "3.7.2"
|
||||||
|
@ -420,6 +430,20 @@ files = [
|
||||||
{file = "django_extensions-3.1.5-py3-none-any.whl", hash = "sha256:9238b9e016bb0009d621e05cf56ea8ce5cce9b32e91ad2026996a7377ca28069"},
|
{file = "django_extensions-3.1.5-py3-none-any.whl", hash = "sha256:9238b9e016bb0009d621e05cf56ea8ce5cce9b32e91ad2026996a7377ca28069"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-ninja"
|
||||||
|
version = "1.1.0"
|
||||||
|
requires_python = ">=3.7"
|
||||||
|
summary = "Django Ninja - Fast Django REST framework"
|
||||||
|
dependencies = [
|
||||||
|
"Django>=3.1",
|
||||||
|
"pydantic<3.0.0,>=2.0",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "django_ninja-1.1.0-py3-none-any.whl", hash = "sha256:6330c3497061d9fd1f43c1200f85c13aab7687110e2899f8304e5aa476c10b44"},
|
||||||
|
{file = "django_ninja-1.1.0.tar.gz", hash = "sha256:87bff046416a2653ed2fbef1408e101292bf8170684821bac82accfd73bef059"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-stubs"
|
name = "django-stubs"
|
||||||
version = "4.2.7"
|
version = "4.2.7"
|
||||||
|
@ -1025,6 +1049,86 @@ files = [
|
||||||
{file = "pycryptodomex-3.19.1.tar.gz", hash = "sha256:0b7154aff2272962355f8941fd514104a88cb29db2d8f43a29af900d6398eb1c"},
|
{file = "pycryptodomex-3.19.1.tar.gz", hash = "sha256:0b7154aff2272962355f8941fd514104a88cb29db2d8f43a29af900d6398eb1c"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic"
|
||||||
|
version = "2.6.4"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = "Data validation using Python type hints"
|
||||||
|
dependencies = [
|
||||||
|
"annotated-types>=0.4.0",
|
||||||
|
"pydantic-core==2.16.3",
|
||||||
|
"typing-extensions>=4.6.1",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"},
|
||||||
|
{file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-core"
|
||||||
|
version = "2.16.3"
|
||||||
|
requires_python = ">=3.8"
|
||||||
|
summary = ""
|
||||||
|
dependencies = [
|
||||||
|
"typing-extensions!=4.7.0,>=4.6.0",
|
||||||
|
]
|
||||||
|
files = [
|
||||||
|
{file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"},
|
||||||
|
{file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"},
|
||||||
|
{file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"},
|
||||||
|
{file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyflakes"
|
name = "pyflakes"
|
||||||
version = "3.2.0"
|
version = "3.2.0"
|
||||||
|
|
|
@ -17,6 +17,7 @@ dependencies = [
|
||||||
"w3lib>=1.22.0",
|
"w3lib>=1.22.0",
|
||||||
"yt-dlp>=2023.10.13",
|
"yt-dlp>=2023.10.13",
|
||||||
# "playwright>=1.39.0; platform_machine != 'armv7l'",
|
# "playwright>=1.39.0; platform_machine != 'armv7l'",
|
||||||
|
"django-ninja>=1.1.0",
|
||||||
]
|
]
|
||||||
requires-python = ">=3.9,<3.12"
|
requires-python = ">=3.9,<3.12"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
|
@ -1,54 +1,121 @@
|
||||||
# This file is @generated by PDM.
|
# This file is @generated by PDM.
|
||||||
# Please do not edit it manually.
|
# Please do not edit it manually.
|
||||||
|
|
||||||
|
alabaster==0.7.13
|
||||||
|
annotated-types==0.6.0
|
||||||
asgiref==3.7.2
|
asgiref==3.7.2
|
||||||
asttokens==2.4.1
|
asttokens==2.4.1
|
||||||
|
babel==2.14.0
|
||||||
|
blinker==1.7.0
|
||||||
brotli==1.1.0; implementation_name == "cpython"
|
brotli==1.1.0; implementation_name == "cpython"
|
||||||
brotlicffi==1.1.0.0; implementation_name != "cpython"
|
brotlicffi==1.1.0.0; implementation_name != "cpython"
|
||||||
|
cachecontrol==0.13.1
|
||||||
certifi==2023.11.17
|
certifi==2023.11.17
|
||||||
cffi==1.16.0; implementation_name != "cpython"
|
cffi==1.16.0; implementation_name != "cpython"
|
||||||
charset-normalizer==3.3.2
|
charset-normalizer==3.3.2
|
||||||
colorama==0.4.6; sys_platform == "win32"
|
colorama==0.4.6; sys_platform == "win32"
|
||||||
|
commonmark==0.9.1
|
||||||
croniter==2.0.1
|
croniter==2.0.1
|
||||||
dateparser==1.2.0
|
dateparser==1.2.0
|
||||||
decorator==5.1.1
|
decorator==5.1.1
|
||||||
|
dep-logic==0.0.4
|
||||||
|
distlib==0.3.8
|
||||||
django==3.1.14
|
django==3.1.14
|
||||||
django-auth-ldap==4.1.0
|
django-auth-ldap==4.1.0
|
||||||
|
django-debug-toolbar==3.2.4
|
||||||
django-extensions==3.1.5
|
django-extensions==3.1.5
|
||||||
|
django-ninja==1.1.0
|
||||||
|
django-stubs==4.2.7
|
||||||
|
django-stubs-ext==4.2.7
|
||||||
|
djdt-flamegraph==0.2.13
|
||||||
|
docutils==0.20.1
|
||||||
exceptiongroup==1.2.0; python_version < "3.11"
|
exceptiongroup==1.2.0; python_version < "3.11"
|
||||||
executing==2.0.1
|
executing==2.0.1
|
||||||
|
filelock==3.13.1
|
||||||
|
findpython==0.4.1
|
||||||
|
flake8==7.0.0
|
||||||
|
homebrew-pypi-poet==0.10.0
|
||||||
idna==3.6
|
idna==3.6
|
||||||
|
imagesize==1.4.1
|
||||||
|
importlib-metadata==7.0.1; python_version < "3.10"
|
||||||
|
iniconfig==2.0.0
|
||||||
|
installer==0.7.0
|
||||||
|
ipdb==0.13.13
|
||||||
ipython==8.18.1
|
ipython==8.18.1
|
||||||
jedi==0.19.1
|
jedi==0.19.1
|
||||||
|
Jinja2==3.1.2
|
||||||
|
markdown-it-py==3.0.0
|
||||||
|
MarkupSafe==2.1.3
|
||||||
matplotlib-inline==0.1.6
|
matplotlib-inline==0.1.6
|
||||||
|
mccabe==0.7.0
|
||||||
|
mdurl==0.1.2
|
||||||
|
msgpack==1.0.7
|
||||||
mutagen==1.47.0
|
mutagen==1.47.0
|
||||||
|
mypy==1.8.0
|
||||||
mypy-extensions==1.0.0
|
mypy-extensions==1.0.0
|
||||||
|
packaging==23.2
|
||||||
parso==0.8.3
|
parso==0.8.3
|
||||||
|
pdm==2.11.2
|
||||||
pexpect==4.9.0; sys_platform != "win32"
|
pexpect==4.9.0; sys_platform != "win32"
|
||||||
|
platformdirs==4.1.0
|
||||||
|
pluggy==1.3.0
|
||||||
prompt-toolkit==3.0.43
|
prompt-toolkit==3.0.43
|
||||||
ptyprocess==0.7.0; sys_platform != "win32"
|
ptyprocess==0.7.0; sys_platform != "win32"
|
||||||
pure-eval==0.2.2
|
pure-eval==0.2.2
|
||||||
pyasn1==0.5.1
|
pyasn1==0.5.1
|
||||||
pyasn1-modules==0.3.0
|
pyasn1-modules==0.3.0
|
||||||
|
pycodestyle==2.11.1
|
||||||
pycparser==2.21; implementation_name != "cpython"
|
pycparser==2.21; implementation_name != "cpython"
|
||||||
pycryptodomex==3.19.1
|
pycryptodomex==3.19.1
|
||||||
|
pydantic==2.6.4
|
||||||
|
pydantic-core==2.16.3
|
||||||
|
pyflakes==3.2.0
|
||||||
pygments==2.17.2
|
pygments==2.17.2
|
||||||
|
pyproject-hooks==1.0.0
|
||||||
|
pytest==7.4.4
|
||||||
python-crontab==3.0.0
|
python-crontab==3.0.0
|
||||||
python-dateutil==2.8.2
|
python-dateutil==2.8.2
|
||||||
|
python-dotenv==1.0.0
|
||||||
python-ldap==3.4.4
|
python-ldap==3.4.4
|
||||||
pytz==2023.3.post1
|
pytz==2023.3.post1
|
||||||
|
recommonmark==0.7.1
|
||||||
regex==2023.12.25
|
regex==2023.12.25
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
|
requests-toolbelt==1.0.0
|
||||||
|
resolvelib==1.0.1
|
||||||
|
rich==13.7.0
|
||||||
|
setuptools==69.0.3
|
||||||
|
shellingham==1.5.4
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
|
snowballstemmer==2.2.0
|
||||||
sonic-client==1.0.0
|
sonic-client==1.0.0
|
||||||
|
sphinx==7.2.6
|
||||||
|
sphinx-rtd-theme==2.0.0
|
||||||
|
sphinxcontrib-applehelp==1.0.7
|
||||||
|
sphinxcontrib-devhelp==1.0.5
|
||||||
|
sphinxcontrib-htmlhelp==2.0.4
|
||||||
|
sphinxcontrib-jquery==4.1
|
||||||
|
sphinxcontrib-jsmath==1.0.1
|
||||||
|
sphinxcontrib-qthelp==1.0.6
|
||||||
|
sphinxcontrib-serializinghtml==1.1.9
|
||||||
sqlparse==0.4.4
|
sqlparse==0.4.4
|
||||||
stack-data==0.6.3
|
stack-data==0.6.3
|
||||||
|
tomli==2.0.1; python_version < "3.11"
|
||||||
|
tomlkit==0.12.3
|
||||||
traitlets==5.14.1
|
traitlets==5.14.1
|
||||||
typing-extensions==4.9.0; python_version < "3.11"
|
truststore==0.8.0; python_version >= "3.10"
|
||||||
|
types-pytz==2023.3.1.1
|
||||||
|
types-PyYAML==6.0.12.12
|
||||||
|
typing-extensions==4.9.0
|
||||||
tzdata==2023.4; platform_system == "Windows"
|
tzdata==2023.4; platform_system == "Windows"
|
||||||
tzlocal==5.2
|
tzlocal==5.2
|
||||||
|
unearth==0.12.1
|
||||||
urllib3==2.1.0
|
urllib3==2.1.0
|
||||||
|
virtualenv==20.25.0
|
||||||
w3lib==2.1.2
|
w3lib==2.1.2
|
||||||
wcwidth==0.2.12
|
wcwidth==0.2.12
|
||||||
websockets==12.0
|
websockets==12.0
|
||||||
|
wheel==0.42.0
|
||||||
yt-dlp==2023.12.30
|
yt-dlp==2023.12.30
|
||||||
|
zipp==3.17.0; python_version < "3.10"
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue