♻️ change pydantic models to pure dataclasses

This commit is contained in:
Ondřej Samohel 2025-03-03 16:31:26 +01:00
parent 50e390e612
commit 534be2c64e
No known key found for this signature in database
GPG key ID: 02376E18990A97C6
12 changed files with 174 additions and 126 deletions

View file

@ -184,10 +184,13 @@ to different packages based on their use:
| | Overscan | holds overscan/underscan information (added pixels to bottom/sides)
| | UDIM | Representation is UDIM tile set
Traits are [Pydantic models](https://docs.pydantic.dev/latest/) with optional
Traits are Python data classes with optional
validation and helper methods. If they implement `TraitBase.validate(Representation)` method, they can validate against all other traits
in the representation if needed. They can also implement pydantic form of
data validators.
in the representation if needed.
> [!NOTE]
> They could be easily converted to [Pydantic models](https://docs.pydantic.dev/latest/) but since this must run in diverse Python environments inside DCC, we cannot
> easily resolve pydantic-core dependency (as it is binary written in Rust).
> [!NOTE]
> Every trait has id, name and some human readable description. Every trait

View file

@ -1,13 +1,13 @@
"""Color management related traits."""
from __future__ import annotations
from dataclasses import dataclass
from typing import ClassVar, Optional
from pydantic import Field
from .trait import TraitBase
@dataclass
class ColorManaged(TraitBase):
"""Color managed trait.
@ -24,9 +24,7 @@ class ColorManaged(TraitBase):
id: ClassVar[str] = "ayon.color.ColorManaged.v1"
name: ClassVar[str] = "ColorManaged"
color_space: str
description: ClassVar[str] = "Color Managed trait."
color_space: str = Field(
...,
description="Color space."
)
config: Optional[str] = Field(default=None, description="Color config.")
persistent: ClassVar[bool] = True
config: Optional[str] = None

View file

@ -3,13 +3,12 @@ from __future__ import annotations
import contextlib
import re
from dataclasses import dataclass
# TC003 is there because Path in TYPECHECKING will fail in tests
from pathlib import Path # noqa: TC003
from typing import ClassVar, Generator, Optional
from pydantic import Field
from .representation import Representation
from .temporal import FrameRanged, Handles, Sequence
from .trait import (
@ -21,6 +20,7 @@ from .two_dimensional import UDIM
from .utils import get_sequence_from_files
@dataclass
class MimeType(TraitBase):
"""MimeType trait model.
@ -40,9 +40,11 @@ class MimeType(TraitBase):
name: ClassVar[str] = "MimeType"
description: ClassVar[str] = "MimeType Trait Model"
id: ClassVar[str] = "ayon.content.MimeType.v1"
mime_type: str = Field(..., title="Mime Type")
persistent: ClassVar[bool] = True
mime_type: str
@dataclass
class LocatableContent(TraitBase):
"""LocatableContent trait model.
@ -57,15 +59,18 @@ class LocatableContent(TraitBase):
description (str): Trait description.
id (str): id should be namespaced trait name with version
location (str): Location.
is_templated (Optional[bool]): Is the location templated? Default is None.
"""
name: ClassVar[str] = "LocatableContent"
description: ClassVar[str] = "LocatableContent Trait Model"
id: ClassVar[str] = "ayon.content.LocatableContent.v1"
location: str = Field(..., title="Location")
is_templated: Optional[bool] = Field(default=None, title="Is Templated")
persistent: ClassVar[bool] = True
location: str
is_templated: Optional[bool] = None
@dataclass
class FileLocation(TraitBase):
"""FileLocation trait model.
@ -78,18 +83,20 @@ class FileLocation(TraitBase):
description (str): Trait description.
id (str): id should be namespaced trait name with version
file_path (str): File path.
file_size (int): File size in bytes.
file_hash (str): File hash.
file_size (Optional[int]): File size in bytes.
file_hash (Optional[str]): File hash.
"""
name: ClassVar[str] = "FileLocation"
description: ClassVar[str] = "FileLocation Trait Model"
id: ClassVar[str] = "ayon.content.FileLocation.v1"
file_path: Path = Field(..., title="File Path")
file_size: Optional[int] = Field(default=None, title="File Size")
file_hash: Optional[str] = Field(default=None, title="File Hash")
persistent: ClassVar[bool] = True
file_path: Path
file_size: Optional[int] = None
file_hash: Optional[str] = None
@dataclass
class FileLocations(TraitBase):
"""FileLocation trait model.
@ -108,7 +115,8 @@ class FileLocations(TraitBase):
name: ClassVar[str] = "FileLocations"
description: ClassVar[str] = "FileLocations Trait Model"
id: ClassVar[str] = "ayon.content.FileLocations.v1"
file_paths: list[FileLocation] = Field(..., title="File Path")
persistent: ClassVar[bool] = True
file_paths: list[FileLocation]
def get_files(self) -> Generator[Path, None, None]:
"""Get all file paths from the trait.
@ -340,6 +348,7 @@ class FileLocations(TraitBase):
return frame_start_with_handles, frame_end_with_handles
@dataclass
class RootlessLocation(TraitBase):
"""RootlessLocation trait model.
@ -363,9 +372,11 @@ class RootlessLocation(TraitBase):
name: ClassVar[str] = "RootlessLocation"
description: ClassVar[str] = "RootlessLocation Trait Model"
id: ClassVar[str] = "ayon.content.RootlessLocation.v1"
rootless_path: str = Field(..., title="File Path")
persistent: ClassVar[bool] = True
rootless_path: str
@dataclass
class Compressed(TraitBase):
"""Compressed trait model.
@ -386,9 +397,11 @@ class Compressed(TraitBase):
name: ClassVar[str] = "Compressed"
description: ClassVar[str] = "Compressed Trait"
id: ClassVar[str] = "ayon.content.Compressed.v1"
compression_type: str = Field(..., title="Compression Type")
persistent: ClassVar[bool] = True
compression_type: str
@dataclass
class Bundle(TraitBase):
"""Bundle trait model.
@ -424,8 +437,8 @@ class Bundle(TraitBase):
name: ClassVar[str] = "Bundle"
description: ClassVar[str] = "Bundle Trait"
id: ClassVar[str] = "ayon.content.Bundle.v1"
items: list[list[TraitBase]] = Field(
..., title="Bundles of traits")
persistent: ClassVar[bool] = True
items: list[list[TraitBase]]
def to_representations(self) -> Generator[Representation]:
"""Convert bundle to representations.
@ -438,6 +451,7 @@ class Bundle(TraitBase):
yield Representation(name=f"{self.name} {idx}", traits=item)
@dataclass
class Fragment(TraitBase):
"""Fragment trait model.
@ -466,4 +480,5 @@ class Fragment(TraitBase):
name: ClassVar[str] = "Fragment"
description: ClassVar[str] = "Fragment Trait"
id: ClassVar[str] = "ayon.content.Fragment.v1"
parent: str = Field(..., title="Parent Representation Id")
persistent: ClassVar[bool] = True
parent: str

View file

@ -1,13 +1,13 @@
"""Cryptography traits."""
from __future__ import annotations
from dataclasses import dataclass
from typing import ClassVar, Optional
from pydantic import Field
from .trait import TraitBase
@dataclass
class DigitallySigned(TraitBase):
"""Digitally signed trait.
@ -20,25 +20,22 @@ class DigitallySigned(TraitBase):
id: ClassVar[str] = "ayon.cryptography.DigitallySigned.v1"
name: ClassVar[str] = "DigitallySigned"
description: ClassVar[str] = "Digitally signed trait."
persistent: ClassVar[bool] = True
@dataclass
class PGPSigned(DigitallySigned):
"""PGP signed trait.
This trait holds PGP (RFC-4880) signed data.
Attributes:
signature (str): PGP signature.
signed_data (str): Signed data.
clear_text (str): Clear text.
"""
id: ClassVar[str] = "ayon.cryptography.PGPSigned.v1"
name: ClassVar[str] = "PGPSigned"
description: ClassVar[str] = "PGP signed trait."
signed_data: str = Field(
...,
description="Signed data."
)
clear_text: Optional[str] = Field(
None,
description="Clear text."
)
persistent: ClassVar[bool] = True
signed_data: str
clear_text: Optional[str] = None

View file

@ -1,9 +1,11 @@
"""Lifecycle traits."""
from dataclasses import dataclass
from typing import ClassVar
from .trait import TraitBase, TraitValidationError
@dataclass
class Transient(TraitBase):
"""Transient trait model.
@ -19,6 +21,7 @@ class Transient(TraitBase):
name: ClassVar[str] = "Transient"
description: ClassVar[str] = "Transient Trait Model"
id: ClassVar[str] = "ayon.lifecycle.Transient.v1"
persistent: ClassVar[bool] = True # see note in Persistent
def validate_trait(self, representation) -> None: # noqa: ANN001
"""Validate representation is not Persistent.
@ -26,14 +29,16 @@ class Transient(TraitBase):
Args:
representation (Representation): Representation model.
Returns:
bool: True if representation is valid, False otherwise.
Raises:
TraitValidationError: If representation is marked as both
"""
if representation.contains_trait(Persistent):
msg = "Representation is marked as both Persistent and Transient."
raise TraitValidationError(self.name, msg)
@dataclass
class Persistent(TraitBase):
"""Persistent trait model.
@ -50,6 +55,10 @@ class Persistent(TraitBase):
name: ClassVar[str] = "Persistent"
description: ClassVar[str] = "Persistent Trait Model"
id: ClassVar[str] = "ayon.lifecycle.Persistent.v1"
# note that this affects persistence of the trait itself, not
# the representation. This is a class variable, so it is shared
# among all instances of the class.
persistent: bool = True
def validate_trait(self, representation) -> None: # noqa: ANN001
"""Validate representation is not Transient.
@ -57,6 +66,9 @@ class Persistent(TraitBase):
Args:
representation (Representation): Representation model.
Raises:
TraitValidationError: If representation is marked as both
"""
if representation.contains_trait(Transient):
msg = "Representation is marked as both Persistent and Transient."

View file

@ -1,13 +1,13 @@
"""Metadata traits."""
from __future__ import annotations
from dataclasses import dataclass
from typing import ClassVar, List, Optional
from pydantic import Field
from .trait import TraitBase
@dataclass
class Tagged(TraitBase):
"""Tagged trait model.
@ -27,9 +27,11 @@ class Tagged(TraitBase):
name: ClassVar[str] = "Tagged"
description: ClassVar[str] = "Tagged Trait Model"
id: ClassVar[str] = "ayon.meta.Tagged.v1"
tags: List[str] = Field(..., title="Tags")
persistent: ClassVar[bool] = True
tags: List[str]
@dataclass
class TemplatePath(TraitBase):
"""TemplatePath trait model.
@ -51,10 +53,12 @@ class TemplatePath(TraitBase):
name: ClassVar[str] = "TemplatePath"
description: ClassVar[str] = "Template Path Trait Model"
id: ClassVar[str] = "ayon.meta.TemplatePath.v1"
template: str = Field(..., title="Template Path")
data: dict = Field(..., title="Formatting Data")
persistent: ClassVar[bool] = True
template: str
data: dict
@dataclass
class Variant(TraitBase):
"""Variant trait model.
@ -75,9 +79,11 @@ class Variant(TraitBase):
name: ClassVar[str] = "Variant"
description: ClassVar[str] = "Variant Trait Model"
id: ClassVar[str] = "ayon.meta.Variant.v1"
variant: str = Field(..., title="Variant")
persistent: ClassVar[bool] = True
variant: str
@dataclass
class KeepOriginalLocation(TraitBase):
"""Keep files in its original location.
@ -88,9 +94,10 @@ class KeepOriginalLocation(TraitBase):
name: ClassVar[str] = "KeepOriginalLocation"
description: ClassVar[str] = "Keep Original Location Trait Model"
id: ClassVar[str] = "ayon.meta.KeepOriginalLocation.v1"
persistent: bool = Field(default=False, title="Persistent")
persistent: ClassVar[bool] = False
@dataclass
class KeepOriginalName(TraitBase):
"""Keep files in its original name.
@ -101,9 +108,10 @@ class KeepOriginalName(TraitBase):
name: ClassVar[str] = "KeepOriginalName"
description: ClassVar[str] = "Keep Original Name Trait Model"
id: ClassVar[str] = "ayon.meta.KeepOriginalName.v1"
persistent: bool = Field(default=False, title="Persistent")
persistent: ClassVar[bool] = False
@dataclass
class SourceApplication(TraitBase):
"""Metadata about the source (producing) application.
@ -115,22 +123,26 @@ class SourceApplication(TraitBase):
Note that this is not really connected to any logic in
ayon-applications addon.
Attributes:
application (str): Application name.
variant (str): Application variant.
version (str): Application version.
platform (str): Platform name (Windows, darwin, etc.).
host_name (str): AYON host name if applicable.
"""
name: ClassVar[str] = "SourceApplication"
description: ClassVar[str] = "Source Application Trait Model"
id: ClassVar[str] = "ayon.meta.SourceApplication.v1"
application: str = Field(..., title="Application Name")
variant: Optional[str] = Field(
None, title="Application Variant (e.g. Pro)")
version: Optional[str] = Field(
None, title="Application Version")
platform: Optional[str] = Field(
None, title="Platform Name (e.g. Windows)")
host_name: Optional[str] = Field(
None, title="AYON host Name if applicable")
persistent: ClassVar[bool] = True
application: str
variant: Optional[str] = None
version: Optional[str] = None
platform: Optional[str] = None
host_name: Optional[str] = None
@dataclass
class IntendedUse(TraitBase):
"""Intended use of the representation.
@ -138,9 +150,13 @@ class IntendedUse(TraitBase):
can be used in cases, where the other traits are not enough to
describe the intended use. For example txt file with tracking
points can be used as corner pin in After Effect but not in Nuke.
"""
Attributes:
use (str): Intended use description.
"""
name: ClassVar[str] = "IntendedUse"
description: ClassVar[str] = "Intended Use Trait Model"
id: ClassVar[str] = "ayon.meta.IntendedUse.v1"
use: str = Field(..., title="Intended Use")
persistent: ClassVar[bool] = True
use: str

View file

@ -3,12 +3,12 @@ from __future__ import annotations
import contextlib
import re
from dataclasses import dataclass
from enum import Enum, auto
from re import Pattern
from typing import TYPE_CHECKING, ClassVar, Optional
import clique
from pydantic import Field, field_validator
from .trait import MissingTraitError, TraitBase, TraitValidationError
@ -36,6 +36,7 @@ class GapPolicy(Enum):
black = auto()
@dataclass
class FrameRanged(TraitBase):
"""Frame ranged trait model.
@ -70,16 +71,16 @@ class FrameRanged(TraitBase):
name: ClassVar[str] = "FrameRanged"
description: ClassVar[str] = "Frame Ranged Trait"
id: ClassVar[str] = "ayon.time.FrameRanged.v1"
frame_start: int = Field(
..., title="Start Frame")
frame_end: int = Field(
..., title="Frame Start")
frame_in: Optional[int] = Field(default=None, title="In Frame")
frame_out: Optional[int] = Field(default=None, title="Out Frame")
frames_per_second: str = Field(..., title="Frames Per Second")
step: Optional[int] = Field(default=1, title="Step")
persistent: ClassVar[bool] = True
frame_start: int
frame_end: int
frame_in: Optional[int] = None
frame_out: Optional[int] = None
frames_per_second: str = None
step: Optional[int] = None
@dataclass
class Handles(TraitBase):
"""Handles trait model.
@ -98,14 +99,13 @@ class Handles(TraitBase):
name: ClassVar[str] = "Handles"
description: ClassVar[str] = "Handles Trait"
id: ClassVar[str] = "ayon.time.Handles.v1"
inclusive: Optional[bool] = Field(
False, title="Handles are inclusive") # noqa: FBT003
frame_start_handle: Optional[int] = Field(
0, title="Frame Start Handle")
frame_end_handle: Optional[int] = Field(
0, title="Frame End Handle")
persistent: ClassVar[bool] = True
inclusive: Optional[bool] = False
frame_start_handle: Optional[int] = None
frame_end_handle: Optional[int] = None
@dataclass
class Sequence(TraitBase):
"""Sequence trait model.
@ -130,15 +130,12 @@ class Sequence(TraitBase):
name: ClassVar[str] = "Sequence"
description: ClassVar[str] = "Sequence Trait Model"
id: ClassVar[str] = "ayon.time.Sequence.v1"
gaps_policy: Optional[GapPolicy] = Field(
default=GapPolicy.forbidden, title="Gaps Policy")
frame_padding: int = Field(..., title="Frame Padding")
frame_regex: Optional[Pattern] = Field(
default=None, title="Frame Regex")
frame_spec: Optional[str] = Field(default=None,
title="Frame Specification")
persistent: ClassVar[bool] = True
frame_padding: int
gaps_policy: Optional[GapPolicy] = GapPolicy.forbidden
frame_regex: Optional[Pattern] = None
frame_spec: Optional[str] = None
@field_validator("frame_regex")
@classmethod
def validate_frame_regex(
cls, v: Optional[Pattern]
@ -426,15 +423,22 @@ class Sequence(TraitBase):
# Do we need one for drop and non-drop frame?
@dataclass
class SMPTETimecode(TraitBase):
"""SMPTE Timecode trait model."""
"""SMPTE Timecode trait model.
Attributes:
timecode (str): SMPTE Timecode HH:MM:SS:FF
"""
name: ClassVar[str] = "Timecode"
description: ClassVar[str] = "SMPTE Timecode Trait"
id: ClassVar[str] = "ayon.time.SMPTETimecode.v1"
timecode: str = Field(..., title="SMPTE Timecode HH:MM:SS:FF")
persistent: ClassVar[bool] = True
timecode: str
@dataclass
class Static(TraitBase):
"""Static time trait.
@ -444,3 +448,4 @@ class Static(TraitBase):
name: ClassVar[str] = "Static"
description: ClassVar[str] = "Static Time Trait"
id: ClassVar[str] = "ayon.time.Static.v1"
persistent: ClassVar[bool] = True

View file

@ -1,11 +1,11 @@
"""3D traits."""
from dataclasses import dataclass
from typing import ClassVar
from pydantic import Field
from .trait import TraitBase
@dataclass
class Spatial(TraitBase):
"""Spatial trait model.
@ -29,11 +29,13 @@ class Spatial(TraitBase):
id: ClassVar[str] = "ayon.3d.Spatial.v1"
name: ClassVar[str] = "Spatial"
description: ClassVar[str] = "Spatial trait model."
up_axis: str = Field(..., title="Up axis")
handedness: str = Field(..., title="Handedness")
meters_per_unit: float = Field(..., title="Meters per unit")
persistent: ClassVar[bool] = True
up_axis: str
handedness: str
meters_per_unit: float
@dataclass
class Geometry(TraitBase):
"""Geometry type trait model.
@ -45,8 +47,10 @@ class Geometry(TraitBase):
id: ClassVar[str] = "ayon.3d.Geometry.v1"
name: ClassVar[str] = "Geometry"
description: ClassVar[str] = "Geometry trait model."
persistent: ClassVar[bool] = True
@dataclass
class Shader(TraitBase):
"""Shader trait model.
@ -58,8 +62,10 @@ class Shader(TraitBase):
id: ClassVar[str] = "ayon.3d.Shader.v1"
name: ClassVar[str] = "Shader"
description: ClassVar[str] = "Shader trait model."
persistent: ClassVar[bool] = True
@dataclass
class Lighting(TraitBase):
"""Lighting trait model.
@ -71,8 +77,10 @@ class Lighting(TraitBase):
id: ClassVar[str] = "ayon.3d.Lighting.v1"
name: ClassVar[str] = "Lighting"
description: ClassVar[str] = "Lighting trait model."
persistent: ClassVar[bool] = True
@dataclass
class IESProfile(TraitBase):
"""IES profile (IES-LM-64) type trait model.
@ -82,3 +90,4 @@ class IESProfile(TraitBase):
id: ClassVar[str] = "ayon.3d.IESProfile.v1"
name: ClassVar[str] = "IESProfile"
description: ClassVar[str] = "IES profile trait model."
persistent: ClassVar[bool] = True

View file

@ -3,16 +3,9 @@ from __future__ import annotations
import re
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import TYPE_CHECKING, Generic, Optional, TypeVar
import pydantic.alias_generators
from pydantic import (
AliasGenerator,
BaseModel,
ConfigDict,
Field,
)
if TYPE_CHECKING:
from .representation import Representation
@ -20,25 +13,15 @@ if TYPE_CHECKING:
T = TypeVar("T", bound="TraitBase")
class TraitBase(ABC, BaseModel):
@dataclass
class TraitBase(ABC):
"""Base trait model.
This model must be used as a base for all trait models.
It is using Pydantic BaseModel for serialization and validation.
``id``, ``name``, and ``description`` are abstract attributes that must be
implemented in the derived classes.
"""
model_config = ConfigDict(
alias_generator=AliasGenerator(
serialization_alias=pydantic.alias_generators.to_camel,
)
)
persistent: bool = Field(
default=True, title="Persistent",
description="Whether the trait is persistent (integrated) or not.")
@property
@abstractmethod
def id(self) -> str:

View file

@ -2,16 +2,16 @@
from __future__ import annotations
import re
from dataclasses import dataclass
from typing import TYPE_CHECKING, ClassVar, Optional
from pydantic import Field, field_validator
from .trait import TraitBase
if TYPE_CHECKING:
from .content import FileLocation, FileLocations
@dataclass
class Image(TraitBase):
"""Image trait model.
@ -26,8 +26,10 @@ class Image(TraitBase):
name: ClassVar[str] = "Image"
description: ClassVar[str] = "Image Trait"
id: ClassVar[str] = "ayon.2d.Image.v1"
persistent: ClassVar[bool] = True
@dataclass
class PixelBased(TraitBase):
"""PixelBased trait model.
@ -45,11 +47,13 @@ class PixelBased(TraitBase):
name: ClassVar[str] = "PixelBased"
description: ClassVar[str] = "PixelBased Trait Model"
id: ClassVar[str] = "ayon.2d.PixelBased.v1"
display_window_width: int = Field(..., title="Display Window Width")
display_window_height: int = Field(..., title="Display Window Height")
pixel_aspect_ratio: float = Field(..., title="Pixel Aspect Ratio")
persistent: ClassVar[bool] = True
display_window_width: int
display_window_height: int
pixel_aspect_ratio: float
@dataclass
class Planar(TraitBase):
"""Planar trait model.
@ -57,7 +61,7 @@ class Planar(TraitBase):
Todo:
* (antirotor): Is this really a planar configuration? As with
bitplanes and everything? If it serves as differentiator for
bit planes and everything? If it serves as differentiator for
Deep images, should it be named differently? Like Raster?
Attributes:
@ -70,9 +74,11 @@ class Planar(TraitBase):
name: ClassVar[str] = "Planar"
description: ClassVar[str] = "Planar Trait Model"
id: ClassVar[str] = "ayon.2d.Planar.v1"
planar_configuration: str = Field(..., title="Planar-based Image")
persistent: ClassVar[bool] = True
planar_configuration: str
@dataclass
class Deep(TraitBase):
"""Deep trait model.
@ -87,8 +93,10 @@ class Deep(TraitBase):
name: ClassVar[str] = "Deep"
description: ClassVar[str] = "Deep Trait Model"
id: ClassVar[str] = "ayon.2d.Deep.v1"
persistent: ClassVar[bool] = True
@dataclass
class Overscan(TraitBase):
"""Overscan trait model.
@ -108,12 +116,14 @@ class Overscan(TraitBase):
name: ClassVar[str] = "Overscan"
description: ClassVar[str] = "Overscan Trait"
id: ClassVar[str] = "ayon.2d.Overscan.v1"
left: int = Field(..., title="Left Overscan")
right: int = Field(..., title="Right Overscan")
top: int = Field(..., title="Top Overscan")
bottom: int = Field(..., title="Bottom Overscan")
persistent: ClassVar[bool] = True
left: int
right: int
top: int
bottom: int
@dataclass
class UDIM(TraitBase):
"""UDIM trait model.
@ -124,16 +134,18 @@ class UDIM(TraitBase):
description (str): Trait description.
id (str): id should be namespaced trait name with version
udim (int): UDIM value.
udim_regex (str): UDIM regex.
"""
name: ClassVar[str] = "UDIM"
description: ClassVar[str] = "UDIM Trait"
id: ClassVar[str] = "ayon.2d.UDIM.v1"
udim: list[int] = Field(..., title="UDIM")
udim_regex: Optional[str] = Field(
default=r"(?:\.|_)(?P<udim>\d+)\.\D+\d?$", title="UDIM Regex")
persistent: ClassVar[bool] = True
udim: list[int]
udim_regex: Optional[str] = r"(?:\.|_)(?P<udim>\d+)\.\D+\d?$"
@field_validator("udim_regex")
# field validator for udim_regex - this works in pydantic model v2 but not
# with the pure data classes
@classmethod
def validate_frame_regex(cls, v: Optional[str]) -> Optional[str]:
"""Validate udim regex.

View file

@ -10,7 +10,6 @@ pyblish-base = "^1.8.11"
speedcopy = "^2.1"
six = "^1.15"
qtawesome = "0.7.3"
pydantic = "^2.9.2"
[ayon.runtimeDependencies]
aiohttp-middlewares = "^2.0.0"

View file

@ -12,7 +12,6 @@ readme = "README.md"
[tool.poetry.dependencies]
python = ">=3.9.1,<3.10"
pydantic = "^2.9.2"
pre-commit = "^4.0.0"
clique = "^2"
pyblish-base = "^1.8"