Platform specification with comprehensive validation
| 131 | |
| 132 | |
| 133 | class Platform(BaseModel): |
| 134 | """Platform specification with comprehensive validation""" |
| 135 | |
| 136 | name: str = Field(min_length=1, max_length=200, description="Platform display name") |
| 137 | architecture: str = Field( |
| 138 | pattern=r"^[a-zA-Z0-9_-]+$", description="Target architecture" |
| 139 | ) |
| 140 | version: str = Field(description="Platform version") |
| 141 | category: str = Field(min_length=1, description="Platform category") |
| 142 | url: HttpUrl = Field(description="Download URL for platform archive") |
| 143 | archive_filename: str = Field( |
| 144 | alias="archiveFileName", description="Archive file name" |
| 145 | ) |
| 146 | checksum: str = Field( |
| 147 | pattern=r"^SHA-256:[a-fA-F0-9]{64}$", description="SHA-256 checksum" |
| 148 | ) |
| 149 | size_mb: float = Field(gt=0, alias="size", description="Archive size in megabytes") |
| 150 | boards: list[Board] = Field( |
| 151 | default_factory=lambda: [], description="Supported boards" |
| 152 | ) |
| 153 | tool_dependencies: list[ToolDependency] = Field( |
| 154 | default_factory=lambda: [], |
| 155 | alias="toolsDependencies", |
| 156 | description="Required tool dependencies", |
| 157 | ) |
| 158 | help: Help = Field(description="Help and documentation links") |
| 159 | |
| 160 | @field_validator("version") |
| 161 | @classmethod |
| 162 | def validate_version_format(cls, v: str) -> str: |
| 163 | """Validate version format""" |
| 164 | if not v.strip(): |
| 165 | raise ValueError("Version cannot be empty") |
| 166 | return v.strip() |
| 167 | |
| 168 | @field_validator("size_mb", mode="before") |
| 169 | @classmethod |
| 170 | def convert_size_from_bytes(cls, v: str | int | float) -> float: |
| 171 | """Convert size from bytes to megabytes if needed""" |
| 172 | if isinstance(v, str): |
| 173 | try: |
| 174 | size_bytes = int(v) |
| 175 | return size_bytes / (1024 * 1024) |
| 176 | except ValueError: |
| 177 | raise ValueError(f"Invalid size format: {v}") |
| 178 | elif isinstance(v, (int, float)): |
| 179 | if v > 1024 * 1024: # Assume bytes if > 1MB |
| 180 | return v / (1024 * 1024) |
| 181 | return v # Already in MB |
| 182 | return v |
| 183 | |
| 184 | @field_validator("archive_filename") |
| 185 | @classmethod |
| 186 | def validate_archive_filename(cls, v: str) -> str: |
| 187 | """Validate archive filename format""" |
| 188 | valid_extensions = [".zip", ".tar.gz", ".tar.bz2", ".tar.xz", ".tar.zst"] |
| 189 | if not any(v.lower().endswith(ext) for ext in valid_extensions): |
| 190 | raise ValueError( |
no outgoing calls