File indexing completed on 2026-08-12 08:24:56
0001 """Pydantic configuration model for the PanDAiDDS scheduler.
0002
0003 This module defines a Pydantic model that mirrors the expected PanDAiDDS
0004 attributes used when submitting jobs to the PanDA system. The fields are
0005 inferred from the example `panda_attrs` dictionary used by the EPIC/PanDA
0006 integration.
0007
0008 The model is registered with the scheduler configuration registry so the
0009 framework can discover and validate PanDA runner configurations.
0010 """
0011
0012 import os
0013 import getpass
0014 from typing import List, Optional, Any
0015 from pydantic import BaseModel, Field, field_validator, model_validator
0016
0017 from aid2e.utilities.configurations.scheduler_registry import register as register_runner_config
0018
0019
0020 class PanDAiDDSRunnerConfig(BaseModel):
0021 """Configuration for the PanDAiDDS scheduler.
0022
0023 Auto-generation behavior:
0024 - name: Auto-generates as 'user.<username>.aid2e_job' if not provided
0025 Override username via PANDA_USERNAME environment variable
0026 - source_dir: Auto-sets to project root directory if not provided
0027 Override via PANDA_SOURCE_DIR environment variable
0028 - init_env: Auto-sets to 'source setup_aid2e.sh' if not provided
0029 If provided as string, appends ' && source setup_aid2e.sh'
0030
0031 Fields:
0032 - name: str (auto-generated)
0033 - init_env: any (auto-set to source setup script, or prepended if string)
0034 - cloud: str
0035 - queue: str
0036 - working_group: str
0037 - task_type: str
0038 - source_dir: str (auto-set to project root)
0039 - source_dir_parent_level: int
0040 - exclude_source_files: List[str] (includes .venv, venv, .git)
0041 - max_walltime: int (seconds)
0042 - core_count: int
0043 - total_memory: int (MB)
0044 - enable_separate_log: bool
0045 - job_dir: Optional[str]
0046 """
0047
0048 name: Optional[str] = Field(
0049 default=None,
0050 description=(
0051 "PanDA job name, must start with 'user.<username>'. "
0052 "If not provided, will be auto-generated from username. "
0053 "Set PANDA_USERNAME environment variable to override system username."
0054 ),
0055 )
0056 init_env: Optional[Any] = Field(
0057 default=None,
0058 description=(
0059 "Initialization environment (callable, dict, or other) to prepare remote jobs. "
0060 "If not provided, defaults to 'source setup_aid2e.sh'. "
0061 "If a string is provided, 'source setup_aid2e.sh' will be appended to it."
0062 ),
0063 )
0064 post_script: Optional[Any] = Field(
0065 default="rm -fr .local .venv src examples tests docs",
0066 description=(
0067 "Post-execution script (callable, dict, or other) to clean up after remote jobs. "
0068 "If not provided, defaults to 'rm -fr .local .venv src examples tests docs'. "
0069 "If a string is provided, it will be executed after the job completes."
0070 ),
0071 )
0072
0073 @model_validator(mode='after')
0074 def validate_and_set_defaults(self) -> 'PanDAiDDSRunnerConfig':
0075 """Validate or generate PanDA job name and set source_dir defaults.
0076
0077 The name must start with 'user.<username>'. If not provided, it will be
0078 auto-generated using the system username (or PANDA_USERNAME env var).
0079
0080 The source_dir defaults to the project root directory. Can be overridden
0081 via PANDA_SOURCE_DIR environment variable.
0082
0083 The init_env defaults to sourcing setup_aid2e.sh. If a string value is
0084 already provided, the setup script will be appended to it.
0085
0086 Returns:
0087 Self with validated/generated name, source_dir, and init_env.
0088
0089 Raises:
0090 ValueError: If the provided name doesn't start with 'user.'.
0091 """
0092
0093 if self.name is not None and self.name != "":
0094 if not self.name.startswith("user."):
0095 raise ValueError(
0096 f"PanDA job name must start with 'user.<username>', got: {self.name}"
0097 )
0098 else:
0099
0100
0101 username = os.environ.get("PANDA_USERNAME") or getpass.getuser()
0102 self.name = f"user.{username}.aid2e_job"
0103
0104
0105 if self.source_dir is None:
0106
0107 env_source = os.environ.get("PANDA_SOURCE_DIR")
0108 if env_source:
0109 self.source_dir = env_source
0110 else:
0111
0112
0113
0114 config_file_dir = os.path.dirname(os.path.abspath(__file__))
0115 project_root = os.path.abspath(os.path.join(config_file_dir, "..", "..", "..", ".."))
0116 self.source_dir = project_root
0117
0118
0119 if self.init_env is None:
0120 self.init_env = "source setup_aid2e.sh; bash install_aid2e_dependencies.sh; "
0121 else:
0122
0123 if isinstance(self.init_env, str):
0124 self.init_env = f"source setup_aid2e.sh && bash install_aid2e_dependencies.sh && {self.init_env}"
0125
0126
0127 return self
0128
0129 cloud: Optional[str] = Field(
0130 default=None,
0131 description="Cloud/region for the PanDA submission (e.g. 'US')",
0132 )
0133 queue: Optional[str] = Field(
0134 default=None,
0135 description="PanDA queue name to submit jobs to",
0136 )
0137 working_group: Optional[str] = Field(
0138 default="AID2E",
0139 description="Working group for the PanDA submission (e.g. 'wg_epic', 'AID2E')",
0140 )
0141 task_type: Optional[str] = Field(
0142 default="AID2E",
0143 description="Task type for PanDA job classification (e.g. 'test', 'prod', 'analysis', 'AID2E')",
0144 )
0145 source_dir: str = Field(
0146 default=None,
0147 description=(
0148 "Directory whose contents should be uploaded to PanDA for remote jobs. "
0149 "If not provided, defaults to project root directory. "
0150 "Set PANDA_SOURCE_DIR environment variable to override."
0151 ),
0152 )
0153 source_dir_parent_level: int = Field(
0154 default=1,
0155 description="How many parent levels above the source_dir should be included when uploading",
0156 )
0157 exclude_source_files: List[str] = Field(
0158 default_factory=lambda: [
0159 r"(^|/)\.[^/]+",
0160 r"^doc",
0161 r"^DTLZ2",
0162 r".*json$",
0163 r".*log$",
0164 "work",
0165 "log",
0166 "OUTDIR",
0167 "calibrations",
0168 "fieldmaps",
0169 "gdml",
0170 "EICrecon-drich-mobo",
0171 "eic-software",
0172 "epic-geom-drich-mobo",
0173 "irt",
0174 "share",
0175 "back*",
0176 "__pycache__",
0177 ".venv",
0178 "venv",
0179 ".git",
0180 ".local",
0181 ],
0182 description="Filename patterns to exclude when packaging source_dir",
0183 )
0184 max_walltime: Optional[int] = Field(
0185 default=None,
0186 description="Maximum walltime in seconds for a PanDA job",
0187 )
0188 core_count: int = Field(
0189 default=1,
0190 description="Number of CPU cores requested per job",
0191 )
0192 total_memory: int = Field(
0193 default=4000,
0194 description="Total memory in MB requested per job",
0195 )
0196 enable_separate_log: bool = Field(
0197 default=True,
0198 description="Whether to enable separate log files for each remote job",
0199 )
0200 job_dir: Optional[str] = Field(
0201 default=None,
0202 description="Remote job working directory (if applicable)",
0203 )
0204
0205
0206
0207 register_runner_config("PanDAiDDSRunner", PanDAiDDSRunnerConfig)