Improve task control panel layout
Build custom container images / build (map[base_image:php:8-fpm-alpine build_args:PHP_VERSION=8
context:php8-pgsql fingerprint_command:{ apk info -v | LC_ALL=C sort; find /usr/local/lib/php/extensions /usr/local/etc/php/conf.d -type f -exec sha256sum {} + | LC_ALL=C sort; }
name:ph… (push) Successful in 47s
Build custom container images / build (map[base_image:postgres:18 build_args:PG_VERSION=18
POSTGIS_VERSION=3
VCHORD_VERSION=0.5.3
context:postgres fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; find /usr/lib/postgresql -type f -exec sha256su… (push) Successful in 1m40s
Build custom container images / build (map[base_image:python:3 build_args:PYTHON_VERSION=3
context:python-tools fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; pip freeze | LC_ALL=C sort; }
name:python-tools oci_labels:org.opencontainers.ima… (push) Successful in 1m18s
Build custom container images / build (map[base_image:php:8-fpm-alpine build_args:PHP_VERSION=8
context:php8-pgsql fingerprint_command:{ apk info -v | LC_ALL=C sort; find /usr/local/lib/php/extensions /usr/local/etc/php/conf.d -type f -exec sha256sum {} + | LC_ALL=C sort; }
name:ph… (push) Successful in 47s
Build custom container images / build (map[base_image:postgres:18 build_args:PG_VERSION=18
POSTGIS_VERSION=3
VCHORD_VERSION=0.5.3
context:postgres fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; find /usr/lib/postgresql -type f -exec sha256su… (push) Successful in 1m40s
Build custom container images / build (map[base_image:python:3 build_args:PYTHON_VERSION=3
context:python-tools fingerprint_command:{ dpkg-query -W -f='${binary:Package}=${Version}\n' | LC_ALL=C sort; pip freeze | LC_ALL=C sort; }
name:python-tools oci_labels:org.opencontainers.ima… (push) Successful in 1m18s
This commit is contained in:
@@ -12,10 +12,19 @@ from typing import Any
|
||||
|
||||
FIELD_NAME = re.compile(r"[a-z][a-z0-9_]*\Z")
|
||||
FIELD_TYPES = frozenset({"text", "integer", "date", "datetime", "choice", "multi_choice", "file"})
|
||||
DISPLAY_ACRONYMS = {
|
||||
"api": "API", "csv": "CSV", "iata": "IATA", "icao": "ICAO", "id": "ID",
|
||||
"ids": "IDs", "url": "URL", "urls": "URLs", "uuid": "UUID",
|
||||
}
|
||||
|
||||
|
||||
def display_name(value: str) -> str:
|
||||
return value.replace("_", " ")
|
||||
titled = value.replace("_", " ").title()
|
||||
return re.sub(
|
||||
r"\b[A-Za-z]+\b",
|
||||
lambda match: DISPLAY_ACRONYMS.get(match.group().casefold(), match.group()),
|
||||
titled,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -104,12 +113,19 @@ class TaskCatalog:
|
||||
or not group_path.is_dir()
|
||||
):
|
||||
continue
|
||||
tasks = tuple(self._tasks_in_group(group_path))
|
||||
manifest = load_input_manifest(
|
||||
group_path,
|
||||
{
|
||||
path.name for path in group_path.iterdir()
|
||||
if path.is_file() and not path.is_symlink() and path.suffix == ".py" and not path.name.startswith((".", "_"))
|
||||
},
|
||||
)
|
||||
tasks = tuple(self._tasks_in_group(group_path, manifest.declarations))
|
||||
groups.append(
|
||||
Group(
|
||||
id=group_path.name,
|
||||
path=group_path,
|
||||
display_name=display_name(group_path.name),
|
||||
display_name=manifest.label or display_name(group_path.name),
|
||||
tasks=tasks,
|
||||
)
|
||||
)
|
||||
@@ -140,7 +156,11 @@ class TaskCatalog:
|
||||
tasks.extend(load_background_tasks(group_path))
|
||||
return tuple(tasks)
|
||||
|
||||
def _tasks_in_group(self, group_path: Path) -> list[Task]:
|
||||
def _tasks_in_group(
|
||||
self,
|
||||
group_path: Path,
|
||||
declarations_by_filename: dict[str, "TaskDeclaration"] | None = None,
|
||||
) -> list[Task]:
|
||||
task_paths = [
|
||||
path
|
||||
for path in sorted(group_path.iterdir(), key=lambda candidate: candidate.name)
|
||||
@@ -151,10 +171,11 @@ class TaskCatalog:
|
||||
or path.suffix != ".py"
|
||||
)
|
||||
]
|
||||
declarations_by_filename = load_input_manifest(
|
||||
group_path,
|
||||
{path.name for path in task_paths},
|
||||
)
|
||||
if declarations_by_filename is None:
|
||||
declarations_by_filename = load_input_manifest(
|
||||
group_path,
|
||||
{path.name for path in task_paths},
|
||||
).declarations
|
||||
paths_by_filename = {path.name: path for path in task_paths}
|
||||
ordered_filenames = [
|
||||
*declarations_by_filename,
|
||||
@@ -264,37 +285,49 @@ def read_description(path: Path) -> str | None:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class GroupManifest:
|
||||
label: str | None
|
||||
declarations: dict[str, "TaskDeclaration"]
|
||||
|
||||
|
||||
def load_input_manifest(
|
||||
group_path: Path,
|
||||
task_filenames: set[str],
|
||||
) -> dict[str, "TaskDeclaration"]:
|
||||
) -> GroupManifest:
|
||||
"""Read the optional data-only input declaration for one task group."""
|
||||
|
||||
manifest_path = group_path / "task-inputs.json"
|
||||
if not manifest_path.exists():
|
||||
return {}
|
||||
return GroupManifest(label=None, declarations={})
|
||||
if manifest_path.is_symlink():
|
||||
raise ValueError(f"Input manifest cannot be a symlink: {manifest_path}")
|
||||
try:
|
||||
data = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error:
|
||||
raise ValueError(f"Could not read input manifest {manifest_path}: {error}") from error
|
||||
if not isinstance(data, dict) or set(data) != {"version", "tasks"}:
|
||||
if not isinstance(data, dict) or set(data) - {"version", "label", "tasks"}:
|
||||
raise ValueError(
|
||||
f"Input manifest {manifest_path} must contain only version and tasks."
|
||||
f"Input manifest {manifest_path} has an invalid structure."
|
||||
)
|
||||
if data["version"] != 1 or not isinstance(data["tasks"], dict):
|
||||
if data.get("version") != 1 or not isinstance(data.get("tasks"), dict):
|
||||
raise ValueError(f"Input manifest {manifest_path} has an unsupported structure.")
|
||||
label = data.get("label")
|
||||
if label is not None and (not isinstance(label, str) or not label.strip()):
|
||||
raise ValueError(f"Input manifest {manifest_path} label must be non-empty text.")
|
||||
|
||||
unknown_tasks = set(data["tasks"]) - task_filenames
|
||||
if unknown_tasks:
|
||||
names = ", ".join(sorted(unknown_tasks))
|
||||
raise ValueError(f"Input manifest {manifest_path} names unknown tasks: {names}.")
|
||||
|
||||
return {
|
||||
filename: parse_task_declaration(manifest_path, filename, declaration)
|
||||
for filename, declaration in data["tasks"].items()
|
||||
}
|
||||
return GroupManifest(
|
||||
label=label,
|
||||
declarations={
|
||||
filename: parse_task_declaration(manifest_path, filename, declaration)
|
||||
for filename, declaration in data["tasks"].items()
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
Reference in New Issue
Block a user