setup daphne and django channels to replace runserver

This commit is contained in:
Nick Sweeting 2024-09-05 21:45:43 -07:00
parent a190745f36
commit 00aa7dc19f
No known key found for this signature in database
4 changed files with 438 additions and 102 deletions

View file

@ -78,6 +78,8 @@ DEBUG = CONFIG.DEBUG or ('--debug' in sys.argv)
INSTALLED_APPS = [
'daphne',
# Django default apps
'django.contrib.auth',
'django.contrib.contenttypes',
@ -351,36 +353,45 @@ en_formats.SHORT_DATETIME_FORMAT = SHORT_DATETIME_FORMAT
### Logging Settings
################################################################################
IGNORABLE_404_URLS = [
re.compile(r'apple-touch-icon.*\.png$'),
re.compile(r'favicon\.ico$'),
re.compile(r'robots\.txt$'),
re.compile(r'.*\.(css|js)\.map$'),
]
IGNORABLE_200_URLS = [
re.compile(r'.*"GET /static/.* HTTP/.*" 2|3.+', re.I | re.M),
re.compile(r'.*"GET /admin/jsi18n/ HTTP/1.1" 200 .+', re.I | re.M),
IGNORABLE_URL_PATTERNS = [
re.compile(r"/.*/?apple-touch-icon.*\.png"),
re.compile(r"/.*/?favicon\.ico"),
re.compile(r"/.*/?robots\.txt"),
re.compile(r"/.*/?.*\.(css|js)\.map"),
re.compile(r"/.*/?.*\.(css|js)\.map"),
re.compile(r"/static/.*"),
re.compile(r"/admin/jsi18n/"),
]
class NoisyRequestsFilter(logging.Filter):
def filter(self, record) -> bool:
logline = record.getMessage()
# '"GET /api/v1/docs HTTP/1.1" 200 1023'
# '"GET /static/admin/js/SelectFilter2.js HTTP/1.1" 200 15502'
# '"GET /static/admin/js/SelectBox.js HTTP/1.1" 304 0'
# '"GET /admin/jsi18n/ HTTP/1.1" 200 3352'
# '"GET /admin/api/apitoken/0191bbf8-fd5e-0b8c-83a8-0f32f048a0af/change/ HTTP/1.1" 200 28778'
# ignore harmless 404s for the patterns in IGNORABLE_404_URLS
for ignorable_url_pattern in IGNORABLE_404_URLS:
ignorable_log_pattern = re.compile(f'"GET /.*/?{ignorable_url_pattern.pattern[:-1]} HTTP/.*" (200|30.|404) .+$', re.I | re.M)
if ignorable_log_pattern.match(logline):
# ignore harmless 404s for the patterns in IGNORABLE_URL_PATTERNS
for pattern in IGNORABLE_URL_PATTERNS:
ignorable_GET_request = re.compile(f'"GET {pattern.pattern} HTTP/.*" (2..|30.|404) .+$', re.I | re.M)
if ignorable_GET_request.match(logline):
return False
ignorable_log_pattern = re.compile(f'Not Found: /.*/?{ignorable_url_pattern.pattern}', re.I | re.M)
if ignorable_log_pattern.match(logline):
ignorable_404_pattern = re.compile(f'Not Found: {pattern.pattern}', re.I | re.M)
if ignorable_404_pattern.match(logline):
return False
# ignore staticfile requests that 200 or 30*
for ignorable_url_pattern in IGNORABLE_200_URLS:
if ignorable_log_pattern.match(logline):
return False
return True
def add_extra_logging_attrs(record):
record.username = ''
try:
record.username = record.request.user.username
except AttributeError:
record.username = "Anonymous"
if hasattr(record, 'request'):
import ipdb; ipdb.set_trace()
return True
@ -393,35 +404,38 @@ else:
# if there's an issue on startup, we trash the log and let user figure it out via stdout/stderr
print(f'[!] WARNING: data/logs dir does not exist. Logging to temp file: {ERROR_LOG}')
LOG_LEVEL_DATABASE = 'DEBUG' if DEBUG else 'WARNING'
LOG_LEVEL_REQUEST = 'DEBUG' if DEBUG else 'WARNING'
import pydantic
import django.template
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
"console": {
"level": "DEBUG",
"filters": [],
'formatter': 'simple',
"class": "logging.StreamHandler",
'filters': ['noisyrequestsfilter'],
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"rich": {
"datefmt": "[%X]",
# "format": "{asctime} {levelname} {module} {name} {message} {username}",
# "format": "%(message)s (user=%(username)s",
},
'logfile': {
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler',
'filename': ERROR_LOG,
'maxBytes': 1024 * 1024 * 25, # 25 MB
'backupCount': 10,
'formatter': 'verbose',
'filters': ['noisyrequestsfilter'],
"verbose": {
"style": "{",
},
# "mail_admins": {
# "level": "ERROR",
# "filters": ["require_debug_false"],
# "class": "django.utils.log.AdminEmailHandler",
# },
"simple": {
"format": "{name} {message}",
"style": "{",
},
'filters': {
'noisyrequestsfilter': {
'()': NoisyRequestsFilter,
"django.server": {
"()": "django.utils.log.ServerFormatter",
# "format": "{message} (user={username})",
"style": "{",
},
},
"filters": {
"noisyrequestsfilter": {
"()": NoisyRequestsFilter,
},
"require_debug_false": {
"()": "django.utils.log.RequireDebugFalse",
@ -429,58 +443,106 @@ LOGGING = {
"require_debug_true": {
"()": "django.utils.log.RequireDebugTrue",
},
# "add_extra_logging_attrs": {
# "()": "django.utils.log.CallbackFilter",
# "callback": add_extra_logging_attrs,
# },
},
'formatters': {
'verbose': {
'format': '{name} {levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
"handlers": {
# "console": {
# "level": "DEBUG",
# 'formatter': 'simple',
# "class": "logging.StreamHandler",
# 'filters': ['noisyrequestsfilter', 'add_extra_logging_attrs'],
# },
"console": {
"class": "rich.logging.RichHandler",
"formatter": "rich",
"level": "DEBUG",
"markup": False,
"rich_tracebacks": True,
"filters": ["noisyrequestsfilter"],
"tracebacks_suppress": [
pydantic,
django.template,
],
},
'simple': {
'format': '{name} {message}',
'style': '{',
"logfile": {
"level": "ERROR",
"class": "logging.handlers.RotatingFileHandler",
"filename": ERROR_LOG,
"maxBytes": 1024 * 1024 * 25, # 25 MB
"backupCount": 10,
"formatter": "verbose",
"filters": ["noisyrequestsfilter"],
},
"django.server": {
"()": "django.utils.log.ServerFormatter",
"format": "[{server_time}] {message}",
"style": "{",
# "mail_admins": {
# "level": "ERROR",
# "filters": ["require_debug_false"],
# "class": "django.utils.log.AdminEmailHandler",
# },
"null": {
"class": "logging.NullHandler",
},
},
'loggers': {
'api': {
'handlers': ['console', 'logfile'],
'level': 'DEBUG',
"root": {
"handlers": ["console", "logfile"],
"level": "INFO",
"formatter": "verbose",
},
'checks': {
'handlers': ['console', 'logfile'],
'level': 'DEBUG',
"loggers": {
"api": {
"handlers": ["console", "logfile"],
"level": "DEBUG",
},
'core': {
'handlers': ['console', 'logfile'],
'level': 'DEBUG',
"checks": {
"handlers": ["console", "logfile"],
"level": "DEBUG",
},
'builtin_plugins': {
'handlers': ['console', 'logfile'],
'level': 'DEBUG',
"core": {
"handlers": ["console", "logfile"],
"level": "DEBUG",
},
'django': {
'handlers': ['console', 'logfile'],
'level': 'INFO',
'filters': ['noisyrequestsfilter'],
"builtin_plugins": {
"handlers": ["console", "logfile"],
"level": "DEBUG",
},
'django.server': {
'handlers': ['console', 'logfile'],
'level': 'INFO',
'filters': ['noisyrequestsfilter'],
'propagate': False,
"django": {
"handlers": ["console", "logfile"],
"level": "INFO",
"filters": ["noisyrequestsfilter"],
},
"django.utils.autoreload": {
"propagate": False,
"handlers": [],
"level": "ERROR",
},
"django.channels.server": {
"propagate": False,
"handlers": ["console", "logfile"],
"level": "INFO",
"filters": ["noisyrequestsfilter"],
"formatter": "django.server",
},
'django.request': {
'handlers': ['console', 'logfile'],
'level': 'INFO',
'filters': ['noisyrequestsfilter'],
'propagate': False,
"django.server": { # logs all requests (2xx, 3xx, 4xx)
"propagate": False,
"handlers": ["console", "logfile"],
"level": "INFO",
"filters": ["noisyrequestsfilter"],
"formatter": "django.server",
},
"django.request": { # only logs 4xx and 5xx errors
"propagate": False,
"handlers": ["console", "logfile"],
"level": "INFO",
"filters": ["noisyrequestsfilter"],
"formatter": "django.server",
},
"django.db.backends": {
"propagate": False,
"handlers": ["console"],
"level": LOG_LEVEL_DATABASE,
},
},
}

View file

@ -1335,9 +1335,9 @@ def server(runserver_args: Optional[List[str]]=None,
print(' archivebox manage createsuperuser')
print()
# fallback to serving staticfiles insecurely with django when DEBUG=False
if not config.DEBUG:
runserver_args.append('--insecure') # TODO: serve statics w/ nginx instead
# fallback to serving staticfiles insecurely with django when DEBUG=False (not compatible with daphne)
# if not config.DEBUG:
# runserver_args.append('--insecure') # TODO: serve statics w/ nginx instead
# toggle autoreloading when archivebox code changes (it's on by default)
if not reload:

305
pdm.lock
View file

@ -5,7 +5,7 @@
groups = ["default", "ldap", "sonic"]
strategy = ["inherit_metadata"]
lock_version = "4.5.0"
content_hash = "sha256:c6aa1f436032d18d079a4c2e9d9b95a5110579eb96a449751bfaf4d472eba401"
content_hash = "sha256:f940c4c0a330b7b0bcff68a006b29ea3b1292ad6aadd3cfc909de0622f2963ac"
[[metadata.targets]]
requires_python = "==3.10.*"
@ -90,6 +90,54 @@ files = [
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
]
[[package]]
name = "attrs"
version = "24.2.0"
requires_python = ">=3.7"
summary = "Classes Without Boilerplate"
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"importlib-metadata; python_version < \"3.8\"",
]
files = [
{file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"},
{file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"},
]
[[package]]
name = "autobahn"
version = "24.4.2"
requires_python = ">=3.9"
summary = "WebSocket client & server library, WAMP real-time framework"
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"cryptography>=3.4.6",
"hyperlink>=21.0.0",
"setuptools",
"txaio>=21.2.1",
]
files = [
{file = "autobahn-24.4.2-py2.py3-none-any.whl", hash = "sha256:c56a2abe7ac78abbfb778c02892d673a4de58fd004d088cd7ab297db25918e81"},
{file = "autobahn-24.4.2.tar.gz", hash = "sha256:a2d71ef1b0cf780b6d11f8b205fd2c7749765e65795f2ea7d823796642ee92c9"},
]
[[package]]
name = "automat"
version = "24.8.1"
requires_python = ">=3.8"
summary = "Self-service finite-state machines for the programmer on the go."
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"typing-extensions; python_version < \"3.10\"",
]
files = [
{file = "Automat-24.8.1-py3-none-any.whl", hash = "sha256:bf029a7bc3da1e2c24da2343e7598affaa9f10bf0ab63ff808566ce90551e02a"},
{file = "automat-24.8.1.tar.gz", hash = "sha256:b34227cf63f6325b8ad2399ede780675083e439b20c323d376373d8ee6306d88"},
]
[[package]]
name = "base32-crockford"
version = "0.3.0"
@ -157,6 +205,39 @@ files = [
{file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"},
]
[[package]]
name = "channels"
version = "4.1.0"
requires_python = ">=3.8"
summary = "Brings async, event-driven capabilities to Django 3.2 and up."
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"Django>=4.2",
"asgiref<4,>=3.6.0",
]
files = [
{file = "channels-4.1.0-py3-none-any.whl", hash = "sha256:a3c4419307f582c3f71d67bfb6eff748ae819c2f360b9b141694d84f242baa48"},
{file = "channels-4.1.0.tar.gz", hash = "sha256:e0ed375719f5c1851861f05ed4ce78b0166f9245ca0ecd836cb77d4bb531489d"},
]
[[package]]
name = "channels"
version = "4.1.0"
extras = ["daphne"]
requires_python = ">=3.8"
summary = "Brings async, event-driven capabilities to Django 3.2 and up."
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"channels==4.1.0",
"daphne>=4.0.0",
]
files = [
{file = "channels-4.1.0-py3-none-any.whl", hash = "sha256:a3c4419307f582c3f71d67bfb6eff748ae819c2f360b9b141694d84f242baa48"},
{file = "channels-4.1.0.tar.gz", hash = "sha256:e0ed375719f5c1851861f05ed4ce78b0166f9245ca0ecd836cb77d4bb531489d"},
]
[[package]]
name = "charset-normalizer"
version = "3.3.2"
@ -172,6 +253,18 @@ files = [
{file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
]
[[package]]
name = "constantly"
version = "23.10.4"
requires_python = ">=3.8"
summary = "Symbolic constants in Python"
groups = ["default"]
marker = "python_version == \"3.10\""
files = [
{file = "constantly-23.10.4-py3-none-any.whl", hash = "sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9"},
{file = "constantly-23.10.4.tar.gz", hash = "sha256:aa92b70a33e2ac0bb33cd745eb61776594dc48764b06c35e0efd050b7f1c7cbd"},
]
[[package]]
name = "croniter"
version = "3.0.3"
@ -206,6 +299,23 @@ files = [
{file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"},
]
[[package]]
name = "daphne"
version = "4.1.2"
requires_python = ">=3.8"
summary = "Django ASGI (HTTP/WebSocket) server"
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"asgiref<4,>=3.5.2",
"autobahn>=22.4.2",
"twisted[tls]>=22.4",
]
files = [
{file = "daphne-4.1.2-py3-none-any.whl", hash = "sha256:618d1322bb4d875342b99dd2a10da2d9aae7ee3645f765965fdc1e658ea5290a"},
{file = "daphne-4.1.2.tar.gz", hash = "sha256:fcbcace38eb86624ae247c7ffdc8ac12f155d7d19eafac4247381896d6f33761"},
]
[[package]]
name = "dateparser"
version = "1.2.0"
@ -534,6 +644,22 @@ files = [
{file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"},
]
[[package]]
name = "hyperlink"
version = "21.0.0"
requires_python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
summary = "A featureful, immutable, and correct URL for Python."
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"idna>=2.5",
"typing; python_version < \"3.5\"",
]
files = [
{file = "hyperlink-21.0.0-py2.py3-none-any.whl", hash = "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4"},
{file = "hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b"},
]
[[package]]
name = "idna"
version = "3.8"
@ -546,6 +672,22 @@ files = [
{file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"},
]
[[package]]
name = "incremental"
version = "24.7.2"
requires_python = ">=3.8"
summary = "A small library that versions your Python projects."
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"setuptools>=61.0",
"tomli; python_version < \"3.11\"",
]
files = [
{file = "incremental-24.7.2-py3-none-any.whl", hash = "sha256:8cb2c3431530bec48ad70513931a760f446ad6c25e8333ca5d95e24b0ed7b8fe"},
{file = "incremental-24.7.2.tar.gz", hash = "sha256:fb4f1d47ee60efe87d4f6f0ebb5f70b9760db2b2574c59c8e8912be4ebd464c9"},
]
[[package]]
name = "ipython"
version = "8.26.0"
@ -586,6 +728,21 @@ files = [
{file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"},
]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
requires_python = ">=3.8"
summary = "Python port of markdown-it. Markdown parsing, done right!"
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"mdurl~=0.1",
]
files = [
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
]
[[package]]
name = "matplotlib-inline"
version = "0.1.7"
@ -601,6 +758,18 @@ files = [
{file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"},
]
[[package]]
name = "mdurl"
version = "0.1.2"
requires_python = ">=3.7"
summary = "Markdown URL utilities"
groups = ["default"]
marker = "python_version == \"3.10\""
files = [
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
[[package]]
name = "mutagen"
version = "1.47.0"
@ -664,19 +833,6 @@ dependencies = [
"requests",
]
[[package]]
name = "pocket"
version = "0.3.7"
git = "https://github.com/tapanpandita/pocket.git"
ref = "v0.3.7"
revision = "5a144438cc89bfc0ec94db960718ccf1f76468c1"
summary = "api wrapper for getpocket.com"
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"requests",
]
[[package]]
name = "prompt-toolkit"
version = "3.0.47"
@ -719,7 +875,7 @@ name = "pyasn1"
version = "0.6.0"
requires_python = ">=3.8"
summary = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
groups = ["ldap"]
groups = ["default", "ldap"]
marker = "python_version == \"3.10\""
files = [
{file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"},
@ -731,7 +887,7 @@ name = "pyasn1-modules"
version = "0.4.0"
requires_python = ">=3.8"
summary = "A collection of ASN.1-based protocols modules"
groups = ["ldap"]
groups = ["default", "ldap"]
marker = "python_version == \"3.10\""
dependencies = [
"pyasn1<0.7.0,>=0.4.6",
@ -832,6 +988,21 @@ files = [
{file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
]
[[package]]
name = "pyopenssl"
version = "24.2.1"
requires_python = ">=3.7"
summary = "Python wrapper module around the OpenSSL library"
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"cryptography<44,>=41.0.5",
]
files = [
{file = "pyOpenSSL-24.2.1-py3-none-any.whl", hash = "sha256:967d5719b12b243588573f39b0c677637145c7a1ffedcd495a487e58177fbb8d"},
{file = "pyopenssl-24.2.1.tar.gz", hash = "sha256:4247f0dbe3748d560dcbb2ff3ea01af0f9a1a001ef5f7c4c647956ed8cbf0e95"},
]
[[package]]
name = "python-crontab"
version = "3.2.0"
@ -920,6 +1091,41 @@ files = [
{file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
]
[[package]]
name = "rich"
version = "13.8.0"
requires_python = ">=3.7.0"
summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"markdown-it-py>=2.2.0",
"pygments<3.0.0,>=2.13.0",
"typing-extensions<5.0,>=4.0.0; python_version < \"3.9\"",
]
files = [
{file = "rich-13.8.0-py3-none-any.whl", hash = "sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc"},
{file = "rich-13.8.0.tar.gz", hash = "sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4"},
]
[[package]]
name = "service-identity"
version = "24.1.0"
requires_python = ">=3.8"
summary = "Service identity verification for pyOpenSSL & cryptography."
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"attrs>=19.1.0",
"cryptography",
"pyasn1",
"pyasn1-modules",
]
files = [
{file = "service_identity-24.1.0-py3-none-any.whl", hash = "sha256:a28caf8130c8a5c1c7a6f5293faaf239bbfb7751e4862436920ee6f2616f568a"},
{file = "service_identity-24.1.0.tar.gz", hash = "sha256:6829c9d62fb832c2e1c435629b0a8c476e1929881f28bee4d20bc24161009221"},
]
[[package]]
name = "setuptools"
version = "74.0.0"
@ -1029,6 +1235,58 @@ files = [
{file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"},
]
[[package]]
name = "twisted"
version = "24.7.0"
requires_python = ">=3.8.0"
summary = "An asynchronous networking framework written in Python"
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"attrs>=21.3.0",
"automat>=0.8.0",
"constantly>=15.1",
"hyperlink>=17.1.1",
"incremental>=24.7.0",
"typing-extensions>=4.2.0",
"zope-interface>=5",
]
files = [
{file = "twisted-24.7.0-py3-none-any.whl", hash = "sha256:734832ef98108136e222b5230075b1079dad8a3fc5637319615619a7725b0c81"},
{file = "twisted-24.7.0.tar.gz", hash = "sha256:5a60147f044187a127ec7da96d170d49bcce50c6fd36f594e60f4587eff4d394"},
]
[[package]]
name = "twisted"
version = "24.7.0"
extras = ["tls"]
requires_python = ">=3.8.0"
summary = "An asynchronous networking framework written in Python"
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"idna>=2.4",
"pyopenssl>=21.0.0",
"service-identity>=18.1.0",
"twisted==24.7.0",
]
files = [
{file = "twisted-24.7.0-py3-none-any.whl", hash = "sha256:734832ef98108136e222b5230075b1079dad8a3fc5637319615619a7725b0c81"},
{file = "twisted-24.7.0.tar.gz", hash = "sha256:5a60147f044187a127ec7da96d170d49bcce50c6fd36f594e60f4587eff4d394"},
]
[[package]]
name = "txaio"
version = "23.1.1"
requires_python = ">=3.7"
summary = "Compatibility API between asyncio/Twisted/Trollius"
groups = ["default"]
marker = "python_version == \"3.10\""
files = [
{file = "txaio-23.1.1-py2.py3-none-any.whl", hash = "sha256:aaea42f8aad50e0ecfb976130ada140797e9dcb85fad2cf72b0f37f8cefcb490"},
{file = "txaio-23.1.1.tar.gz", hash = "sha256:f9a9216e976e5e3246dfd112ad7ad55ca915606b60b84a757ac769bd404ff704"},
]
[[package]]
name = "typeid-python"
version = "0.3.1"
@ -1183,3 +1441,18 @@ files = [
{file = "yt_dlp-2024.8.6-py3-none-any.whl", hash = "sha256:ab507ff600bd9269ad4d654e309646976778f0e243eaa2f6c3c3214278bb2922"},
{file = "yt_dlp-2024.8.6.tar.gz", hash = "sha256:e8551f26bc8bf67b99c12373cc87ed2073436c3437e53290878d0f4b4bb1f663"},
]
[[package]]
name = "zope-interface"
version = "7.0.3"
requires_python = ">=3.8"
summary = "Interfaces for Python"
groups = ["default"]
marker = "python_version == \"3.10\""
dependencies = [
"setuptools",
]
files = [
{file = "zope.interface-7.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6195c3c03fef9f87c0dbee0b3b6451df6e056322463cf35bca9a088e564a3c58"},
{file = "zope.interface-7.0.3.tar.gz", hash = "sha256:cd2690d4b08ec9eaf47a85914fe513062b20da78d10d6d789a792c0b20307fb1"},
]

View file

@ -49,6 +49,7 @@ dependencies = [
"django-taggit==1.3.0",
"base32-crockford==0.3.0",
"rich>=13.8.0",
"channels[daphne]>=4.1.0",
]
homepage = "https://github.com/ArchiveBox/ArchiveBox"