Build and Push Docker Container / build-and-push (push) Successful in 2m25s
- Require Python 3.15 for the server and Python 3.14 for the CLI. - Match the server requirement to quart_common's Python 3.15 features. - Replace future annotations with native deferred evaluation. - Defer CLI command dependencies and QR imports on Python 3.15. - Enable argparse suggestions for mistyped CLI commands. - Remove the obsolete tomllib import fallback. - Update my_helpers and quart_common submodule references. - Lock test dependencies and exclude them from Docker installations. - Add regression coverage for lazy imports and deferred annotations. - Document runtime requirements and standalone CLI validation. - Validate 276 tests on Python 3.15 and 18 CLI tests on Python 3.14. BREAKING CHANGE: The server now requires Python 3.15+ and the standalone CLI requires Python 3.14+.
68 lines
1.9 KiB
Python
68 lines
1.9 KiB
Python
from collections.abc import Awaitable, Callable
|
|
|
|
from .linkify import tokenize
|
|
from .own_links import parse_share_link
|
|
|
|
# Each resolved link costs one backend lookup, so a single document stays bounded.
|
|
MAX_LOOKUPS = 20
|
|
|
|
def _media_kind(content_type: str) -> str:
|
|
value = (content_type or '').split(';')[0].strip().lower()
|
|
for prefix in ('image', 'video', 'audio'):
|
|
if value.startswith(prefix + '/'):
|
|
return prefix
|
|
if 'pdf' in value:
|
|
return 'pdf'
|
|
return 'other'
|
|
|
|
async def build_segments(
|
|
text: str,
|
|
host: str,
|
|
root_path: str,
|
|
lookup: Callable[[str], Awaitable[dict | None]] | None = None,
|
|
max_lookups: int = MAX_LOOKUPS,
|
|
) -> list[dict]:
|
|
"""Turn raw text into template segments: plain text, links and resolved own files."""
|
|
segments: list[dict] = []
|
|
resolved: dict[str, dict | None] = {}
|
|
|
|
for kind, value in tokenize(text):
|
|
if kind == 'text':
|
|
segments.append({'kind': 'text', 'text': value})
|
|
continue
|
|
|
|
file_id = parse_share_link(value, host=host, root_path=root_path) if lookup else None
|
|
if file_id is None:
|
|
segments.append({'kind': 'link', 'url': value})
|
|
continue
|
|
|
|
if file_id not in resolved:
|
|
if len(resolved) >= max_lookups:
|
|
segments.append({'kind': 'link', 'url': value})
|
|
continue
|
|
resolved[file_id] = await lookup(file_id)
|
|
|
|
file_data = resolved[file_id]
|
|
if not file_data:
|
|
segments.append({'kind': 'link', 'url': value, 'state': 'missing'})
|
|
continue
|
|
if file_data.get('expired'):
|
|
segments.append({
|
|
'kind': 'link',
|
|
'url': value,
|
|
'state': 'expired',
|
|
'label': file_data.get('file_name') or file_id,
|
|
})
|
|
continue
|
|
|
|
segments.append({
|
|
'kind': 'file',
|
|
'url': value,
|
|
'state': 'ok',
|
|
'file_id': file_id,
|
|
'label': file_data.get('file_name') or file_id,
|
|
'media': _media_kind(file_data.get('content_type') or ''),
|
|
})
|
|
|
|
return segments
|