Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
35e0ea2
tests: added dirac:hints to cwl workflows
Stellatsuu Oct 16, 2025
f554a24
dev: added serverside transformation hints only
Stellatsuu Oct 16, 2025
4adbd1f
dev: modified function return type to Self
Stellatsuu Oct 20, 2025
6fef6de
dev: added namespaces to all transformation hints
Stellatsuu Oct 21, 2025
77ed28a
dev: updated production workflows examples
Stellatsuu Oct 21, 2025
af54bd0
dev: updated workflows examples
Stellatsuu Oct 21, 2025
f015637
dev: updated tests
Stellatsuu Oct 21, 2025
2e15981
dev: added serverside hints
Stellatsuu Oct 21, 2025
fee51d8
dev: added schemas back to its original folder
Stellatsuu Oct 21, 2025
6305416
dev: added schemas back to its original folder
Stellatsuu Oct 21, 2025
a099692
dev: removed legacy code
Stellatsuu Nov 14, 2025
f54aeee
fix: fixed merge conflicts
Stellatsuu Nov 14, 2025
836c8af
fix: fixed some tests
Stellatsuu Nov 14, 2025
d7b82d0
dev: removed dirac hints check
Stellatsuu Nov 17, 2025
fdf3008
dev: added dirac hints validation in SubmissionModel
Stellatsuu Nov 17, 2025
fc25877
dev: added schemas to test/ folder
Stellatsuu Nov 17, 2025
987658b
dev: updated hints validation in SubmissionModel
Stellatsuu Nov 17, 2025
fd8ffeb
dev: updated hints declaration in workflows
Stellatsuu Nov 18, 2025
ab22323
dev: updated workflow and metadata files
Stellatsuu Nov 18, 2025
18c05cf
dev: removed "global" hints from production, hints should be defined …
Stellatsuu Nov 18, 2025
3699053
dev: removed old tests files
Stellatsuu Nov 18, 2025
f62d9c1
dev: updated tests
Stellatsuu Nov 18, 2025
c5f0076
dev: removed override functionality
Stellatsuu Nov 19, 2025
91c0b45
dev: added TransformationHookHint validation in TransformationSubmiss…
Stellatsuu Nov 21, 2025
b63db85
dev: updated schemas ExecutionHooks and Scheduling
Stellatsuu Nov 21, 2025
5f78ba9
dev: updated ExecutionHooks and Scheduling references in tests
Stellatsuu Nov 21, 2025
f51a277
dev: removed transformation metadata for helloworld jobs
Stellatsuu Nov 21, 2025
63ae184
dev: removed _get_configuration from production wfs
Stellatsuu Nov 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ repos:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
exclude: ^test/workflows/test_meta/override_dirac_hints_twice\.yaml$
- id: check-added-large-files

- repo: https://github.com/astral-sh/ruff-pre-commit
Expand Down
2 changes: 1 addition & 1 deletion pixi.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ python scripts/generate_schemas.py \
--individual \
--unified && \
echo "Copying schemas to test locations..." && \
cp generated_schemas/dirac-metadata.json test/workflows/test_meta/schemas/dirac-metadata.json && \
cp generated_schemas/plugins-summary.json test/workflows/test_meta/schemas/plugins-summary.json
cp generated_schemas/dirac-metadata.json test/schemas/dirac-metadata.json && \
cp generated_schemas/plugins-summary.json test/schemas/plugins-summary.json
"""

schemas-yaml = """
Expand All @@ -128,8 +128,8 @@ schemas = { depends-on = ["schemas-json", "schemas-yaml"] }
clean-schemas = """
echo "Cleaning generated schemas..." && \
rm -rf generated_schemas/ && \
rm -f test/workflows/test_meta/schemas/dirac-metadata.json \
test/workflows/test_meta/schemas/plugins-summary.json
rm -f test/schemas/dirac-metadata.json \
test/schemas/plugins-summary.json
"""

# Schema validation
Expand Down
6 changes: 3 additions & 3 deletions scripts/generate_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ def collect_pydantic_models() -> Dict[str, Any]:
models.update(
{
"ExecutionHooksBasePlugin": ExecutionHooksBasePlugin,
"ExecutionHooksHint": ExecutionHooksHint,
"SchedulingHint": SchedulingHint,
"TransformationExecutionHooksHint": TransformationExecutionHooksHint,
"ExecutionHooks": ExecutionHooksHint,
"Scheduling": SchedulingHint,
"TransformationExecutionHooks": TransformationExecutionHooksHint,
}
)
logger.info("Collected core metadata models")
Expand Down
10 changes: 5 additions & 5 deletions src/dirac_cwl_proto/execution_hooks/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import logging
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any, ClassVar, Dict, List, Mapping, Optional, TypeVar, Union
from typing import Any, ClassVar, Dict, List, Mapping, Optional, Self, TypeVar, Union

from pydantic import BaseModel, ConfigDict, Field, PrivateAttr

Expand Down Expand Up @@ -366,7 +366,7 @@ def from_cwl(cls: type[T], cwl_object: Any) -> T:

hints = getattr(cwl_object, "hints", []) or []
for hint in hints:
if hint.get("class") == "dirac:scheduling":
if hint.get("class") == "dirac:Scheduling":
hint_data = {k: v for k, v in hint.items() if k != "class"}
descriptor = descriptor.model_copy(update=hint_data)

Expand Down Expand Up @@ -407,7 +407,7 @@ def model_copy(
update: Optional[Mapping[str, Any]] = None,
*,
deep: bool = False,
) -> "ExecutionHooksHint":
) -> Self:
"""Enhanced model copy with intelligent merging of dict fields (including configuration)."""
if update is None:
update = {}
Expand Down Expand Up @@ -489,12 +489,12 @@ def _dash_to_snake(s: str) -> str:
return get_registry().instantiate_plugin(descriptor)

@classmethod
def from_cwl(cls, cwl_object: Any) -> "ExecutionHooksHint":
def from_cwl(cls, cwl_object: Any) -> Self:
"""Extract metadata descriptor from CWL object using Hint interface."""
descriptor = cls()
hints = getattr(cwl_object, "hints", []) or []
for hint in hints:
if hint.get("class") == "dirac:execution-hooks":
if hint.get("class") == "dirac:ExecutionHooks":
hint_data = {k: v for k, v in hint.items() if k != "class"}
descriptor = descriptor.model_copy(update=hint_data)
return descriptor
Expand Down
31 changes: 1 addition & 30 deletions src/dirac_cwl_proto/job/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
from dirac_cwl_proto.submission_models import (
JobInputModel,
JobSubmissionModel,
extract_dirac_hints,
)

app = AsyncTyper()
Expand Down Expand Up @@ -80,16 +79,6 @@ async def submit_job_client(
return typer.Exit(code=1)

console.print(f"\t[green]:heavy_check_mark:[/green] Task {task_path}")

# Extract and validate dirac hints; unknown hints are logged as warnings.
try:
job_metadata, job_scheduling = extract_dirac_hints(task)
except Exception as exc:
console.print(
f"[red]:heavy_multiplication_x:[/red] [bold]CLI:[/bold] Invalid DIRAC hints:\n{exc}"
)
return typer.Exit(code=1)

console.print("\t[green]:heavy_check_mark:[/green] Hints")

# Extract parameters if any
Expand All @@ -104,17 +93,6 @@ async def submit_job_client(
)
return typer.Exit(code=1)

overrides = parameter.pop("cwltool:overrides", {})
if overrides:
override_hints = overrides[next(iter(overrides))].get("hints", {})
if override_hints:
job_scheduling = job_scheduling.model_copy(
update=override_hints.pop("dirac:scheduling", {})
)
job_metadata = job_metadata.model_copy(
update=override_hints.pop("dirac:execution-hooks", {})
)

# Prepare files for the ISB
isb_file_paths = prepare_input_sandbox(parameter)

Expand All @@ -131,12 +109,7 @@ async def submit_job_client(
f"\t[green]:heavy_check_mark:[/green] Parameter {parameter_p}"
)

job = JobSubmissionModel(
task=task,
parameters=parameters,
scheduling=job_scheduling,
execution_hooks=job_metadata,
)
job = JobSubmissionModel(task=task, parameters=parameters)
console.print(
"[green]:heavy_check_mark:[/green] [bold]CLI:[/bold] Job(s) validated."
)
Expand Down Expand Up @@ -175,8 +148,6 @@ def validate_jobs(job: JobSubmissionModel) -> list[JobSubmissionModel]:
JobSubmissionModel(
task=job.task,
parameters=[parameter],
scheduling=job.scheduling,
execution_hooks=job.execution_hooks,
)
)
console.print(
Expand Down
4 changes: 3 additions & 1 deletion src/dirac_cwl_proto/job/job_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from rich.text import Text
from ruamel.yaml import YAML

from dirac_cwl_proto.execution_hooks import ExecutionHooksHint
from dirac_cwl_proto.execution_hooks.core import ExecutionHooksBasePlugin
from dirac_cwl_proto.submission_models import (
JobInputModel,
Expand Down Expand Up @@ -161,8 +162,9 @@ def run_job(self, job: JobSubmissionModel) -> bool:
logger = logging.getLogger("JobWrapper")
# Instantiate runtime metadata from the serializable descriptor and
# the job context so implementations can access task inputs/overrides.
job_execution_hooks = ExecutionHooksHint.from_cwl(job.task)
self.runtime_metadata = (
job.execution_hooks.to_runtime(job) if job.execution_hooks else None
job_execution_hooks.to_runtime(job) if job_execution_hooks else None
)

# Isolate the job in a specific directory
Expand Down
10 changes: 6 additions & 4 deletions src/dirac_cwl_proto/job/submission_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from diracx.client.aio import AsyncDiracClient
from rich.console import Console

from dirac_cwl_proto.execution_hooks import SchedulingHint
from dirac_cwl_proto.submission_models import JobSubmissionModel

console = Console()
Expand Down Expand Up @@ -169,11 +170,12 @@ def convert_to_jdl(self, job: JobSubmissionModel, sandbox_ids: list[str]) -> str
jdl_lines.append("JobName = test;")
jdl_lines.append("OutputSandbox = {std.out, std.err};")

if job.scheduling.priority:
jdl_lines.append(f"Priority = {job.scheduling.priority};")
job_scheduling = SchedulingHint.from_cwl(job.task)
if job_scheduling.priority:
jdl_lines.append(f"Priority = {job_scheduling.priority};")

if job.scheduling.sites:
jdl_lines.append(f"Site = {job.scheduling.sites};")
if job_scheduling.sites:
jdl_lines.append(f"Site = {job_scheduling.sites};")

jdl_lines.append(f"InputSandbox = {sandbox_ids};")

Expand Down
71 changes: 5 additions & 66 deletions src/dirac_cwl_proto/production/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import logging
from concurrent.futures import ThreadPoolExecutor
from typing import Any, List, Optional
from typing import List, Optional

import typer
from cwl_utils.pack import pack
Expand All @@ -18,13 +18,8 @@
)
from rich import print_json
from rich.console import Console
from ruamel.yaml import YAML
from schema_salad.exceptions import ValidationException

from dirac_cwl_proto.execution_hooks import (
SchedulingHint,
TransformationExecutionHooksHint,
)
from dirac_cwl_proto.submission_models import (
ProductionSubmissionModel,
TransformationSubmissionModel,
Expand All @@ -45,10 +40,6 @@
@app.command("submit")
def submit_production_client(
task_path: str = typer.Argument(..., help="Path to the CWL file"),
steps_metadata_path: str = typer.Option(
None,
help="Path to metadata file used to generate the transformations (one entry per step)",
),
# Specific parameter for the purpose of the prototype
local: Optional[bool] = typer.Option(
True, help="Run the job locally instead of submitting it to the router"
Expand Down Expand Up @@ -78,33 +69,10 @@ def submit_production_client(
)
return typer.Exit(code=1)
console.print(f"\t[green]:heavy_check_mark:[/green] Task {task_path}")

# Load the metadata: at this stage, only the structure is validated, not the content
steps_metadata = {}
if steps_metadata_path:
with open(steps_metadata_path, "r") as file:
steps_metadata = YAML(typ="safe").load(file)

production_step_execution_hooks = {}
production_step_scheduling = {}
for step_name, step_data in steps_metadata.items():
# Extract metadata and scheduling from step_data
metadata_config = step_data.get("execution-hooks", {})
scheduling_config = step_data.get("scheduling", {})

# Create TransformationExecutionHooksHint with the metadata
production_step_execution_hooks[step_name] = TransformationExecutionHooksHint(
**metadata_config
)
production_step_scheduling[step_name] = SchedulingHint(**scheduling_config)
console.print("\t[green]:heavy_check_mark:[/green] Metadata")

# Create the production
transformation = ProductionSubmissionModel(
task=task,
steps_execution_hooks=production_step_execution_hooks,
steps_scheduling=production_step_scheduling,
)
production = ProductionSubmissionModel(task=task)
console.print(
"[green]:heavy_check_mark:[/green] [bold]CLI:[/bold] Production validated."
)
Expand All @@ -113,8 +81,8 @@ def submit_production_client(
console.print(
"[blue]:information_source:[/blue] [bold]CLI:[/bold] Submitting the production..."
)
print_json(transformation.model_dump_json(indent=4))
if not submit_production_router(transformation):
print_json(production.model_dump_json(indent=4))
if not submit_production_router(production):
console.print(
"[red]:heavy_multiplication_x:[/red] [bold]CLI:[/bold] Failed to run production."
)
Expand Down Expand Up @@ -170,31 +138,15 @@ def _get_transformations(
"""
# Create a subworkflow and a transformation for each step
transformations = []

for step in production.task.steps:
step_task = _create_subworkflow(
step, str(production.task.cwlVersion), production.task.inputs
)
configuration = _get_configuration(production.task)

# Get the execution_hooks & description for the step
step_id = step.id.split("#")[-1]
step_data: TransformationExecutionHooksHint = (
production.steps_execution_hooks.get(
step_id,
TransformationExecutionHooksHint(),
)
)
step_scheduling: SchedulingHint = production.steps_scheduling.get(
step_id,
SchedulingHint(),
)
step_data.configuration.update(configuration)

transformations.append(
TransformationSubmissionModel(
task=step_task,
execution_hooks=step_data,
scheduling=step_scheduling,
)
)
return transformations
Expand Down Expand Up @@ -263,16 +215,3 @@ def _create_subworkflow(
break

return new_workflow


def _get_configuration(task: Workflow) -> dict[str, Any]:
"""Get the external inputs of a step.

:param task: The task to get the query params for

:return: A dictionary of query params
"""
configuration = {}
for input in task.inputs:
configuration[input.id.split("#")[-1]] = input.default
return configuration
Loading
Loading