diff --git a/client/ayon_core/pipeline/traits/content.py b/client/ayon_core/pipeline/traits/content.py index 75b1263ea7..4ee63b2b08 100644 --- a/client/ayon_core/pipeline/traits/content.py +++ b/client/ayon_core/pipeline/traits/content.py @@ -163,15 +163,16 @@ class FileLocations(TraitBase): Args: representation (Representation): Representation to validate. - Returns: - bool: True if the trait is valid, False otherwise + Raises: + TraitValidationError: If the trait is invalid within the + representation. """ super().validate_trait(representation) if len(self.file_paths) == 0: - # If there are no file paths, we can't validate - msg = "No file locations defined (empty list)" - raise TraitValidationError(self.name, msg) + # If there are no file paths, we can't validate + msg = "No file locations defined (empty list)" + raise TraitValidationError(self.name, msg) if representation.contains_trait(FrameRanged): self._validate_frame_range(representation) if not representation.contains_trait(Sequence) \ @@ -234,7 +235,7 @@ class FileLocations(TraitBase): raise TraitValidationError(self.name, msg) if frames_from_spec: - if len(frames_from_spec) != len(self.file_paths) : + if len(frames_from_spec) != len(self.file_paths): # If the number of file paths does not match the frame range, # the trait is invalid msg = ( @@ -254,14 +255,14 @@ class FileLocations(TraitBase): ) if len(self.file_paths) != length_with_handles: - # If the number of file paths does not match the frame range, - # the trait is invalid - msg = ( - f"Number of file locations ({len(self.file_paths)}) " - "does not match frame range " - f"({length_with_handles})" - ) - raise TraitValidationError(self.name, msg) + # If the number of file paths does not match the frame range, + # the trait is invalid + msg = ( + f"Number of file locations ({len(self.file_paths)}) " + "does not match frame range " + f"({length_with_handles})" + ) + raise TraitValidationError(self.name, msg) frame_ranged: FrameRanged = representation.get_trait(FrameRanged) @@ -281,8 +282,8 @@ class FileLocations(TraitBase): ) raise TraitValidationError(self.name, msg) + @staticmethod def _get_frame_info_with_handles( - self, representation: Representation, frames_from_spec: list[int]) -> tuple[int, int]: """Get the frame range with handles from the representation. @@ -427,7 +428,12 @@ class Bundle(TraitBase): ..., title="Bundles of traits") def to_representations(self) -> Generator[Representation]: - """Convert bundle to representations.""" + """Convert bundle to representations. + + Yields: + Representation: Representation of the bundle. + + """ for idx, item in enumerate(self.items): yield Representation(name=f"{self.name} {idx}", traits=item) diff --git a/client/ayon_core/pipeline/traits/meta.py b/client/ayon_core/pipeline/traits/meta.py index e21f3eb7fd..0f8c175af5 100644 --- a/client/ayon_core/pipeline/traits/meta.py +++ b/client/ayon_core/pipeline/traits/meta.py @@ -90,6 +90,7 @@ class KeepOriginalLocation(TraitBase): id: ClassVar[str] = "ayon.meta.KeepOriginalLocation.v1" persistent: bool = Field(default=False, title="Persistent") + class KeepOriginalName(TraitBase): """Keep files in its original name. diff --git a/client/ayon_core/pipeline/traits/representation.py b/client/ayon_core/pipeline/traits/representation.py index d185c0466c..43b0397597 100644 --- a/client/ayon_core/pipeline/traits/representation.py +++ b/client/ayon_core/pipeline/traits/representation.py @@ -44,7 +44,7 @@ def _get_version_from_id(_id: str) -> Optional[int]: return int(match[1]) if match else None -class Representation(Generic[T]): +class Representation(Generic[T]): # noqa: PLR0904 """Representation of products. Representation defines collection of individual properties that describe @@ -54,6 +54,9 @@ class Representation(Generic[T]): It holds methods to add, remove, get, and check for the existence of a trait in the representation. It also provides a method to get all the + Note: + `PLR0904` is rule for checking number of public methods in a class. + Arguments: name (str): Representation name. Must be unique within instance. representation_id (str): Representation ID. @@ -79,9 +82,6 @@ class Representation(Generic[T]): Returns: TraitBase: Trait instance. - Raises: - MissingTraitError: If the trait is not found. - """ return self.get_trait_by_id(key) @@ -104,8 +104,6 @@ class Representation(Generic[T]): Args: key (str): Trait ID. - Raises: - ValueError: If the trait is not found. """ self.remove_trait_by_id(key) @@ -138,7 +136,7 @@ class Representation(Generic[T]): """Add a trait to the Representation. Args: - trait (TraiBase): Trait to add. + trait (TraitBase): Trait to add. exists_ok (bool, optional): If True, do not raise an error if the trait already exists. Defaults to False. @@ -228,7 +226,6 @@ class Representation(Generic[T]): for trait_id in trait_ids: self.remove_trait_by_id(trait_id) - def has_traits(self) -> bool: """Check if the Representation has any traits. @@ -260,7 +257,7 @@ class Representation(Generic[T]): bool: True if the trait exists, False otherwise. """ - return bool(self._data.get(trait_id)) + return bool(self._data.get(trait_id)) def contains_traits(self, traits: list[Type[T]]) -> bool: """Check if the traits exist. @@ -366,7 +363,7 @@ class Representation(Generic[T]): return result for trait in traits: - result[str(trait.id)] = self.get_trait(trait=trait) + result[str(trait.id)] = self.get_trait(trait=trait) return result def get_traits_by_ids(self, trait_ids: list[str]) -> dict[str, T]: @@ -532,12 +529,6 @@ class Representation(Generic[T]): Returns: Type[TraitBase]: Trait class. - Raises: - LooseMatchingTraitError: If the trait is found with a loose - matching criteria. This exception will include the trait - class that was found and the expected trait ID. Additional - downstream logic must decide how to handle this error. - """ version = cls._get_version_from_id(trait_id) @@ -588,9 +579,6 @@ class Representation(Generic[T]): Raises: IncompatibleTraitVersionError: If the trait version is incompatible with the current version of the trait. - UpgradableTraitError: If the trait can upgrade existing data - meant for older versions of the trait. - ValueError: If the trait model with the given ID is not found. """ try: @@ -662,6 +650,11 @@ class Representation(Generic[T]): Returns: Representation: Representation instance. + Raises: + ValueError: If the trait model with ID is not found. + TypeError: If the trait data is not a dictionary. + IncompatibleTraitVersionError: If the trait version is incompatible + """ if not trait_data: trait_data = {} @@ -697,7 +690,6 @@ class Representation(Generic[T]): return cls( name=name, representation_id=representation_id, traits=traits) - def validate(self) -> None: """Validate the representation. diff --git a/client/ayon_core/pipeline/traits/temporal.py b/client/ayon_core/pipeline/traits/temporal.py index c66172a6d2..286336ea55 100644 --- a/client/ayon_core/pipeline/traits/temporal.py +++ b/client/ayon_core/pipeline/traits/temporal.py @@ -143,11 +143,20 @@ class Sequence(TraitBase): def validate_frame_regex( cls, v: Optional[Pattern] ) -> Optional[Pattern]: - """Validate frame regex.""" + """Validate frame regex. + + Frame regex must have index and padding named groups. + + Returns: + Optional[Pattern]: Compiled regex pattern. + + Raises: + ValueError: If frame regex does not include 'index' and 'padding' + + """ if v is None: return v - _v = v.pattern - if v and any(s not in _v for s in ["?P", "?P"]): + if v and any(s not in v.pattern for s in ["?P", "?P"]): msg = "Frame regex must include 'index' and `padding named groups" raise ValueError(msg) return v @@ -172,10 +181,6 @@ class Sequence(TraitBase): Args: representation (Representation): Representation instance. - Raises: - TraitValidationError: If file locations do not match the - frame list specification - """ from .content import FileLocations file_locs: FileLocations = representation.get_trait( @@ -309,7 +314,15 @@ class Sequence(TraitBase): @staticmethod def list_spec_to_frames(list_spec: str) -> list[int]: - """Convert list specification to frames.""" + """Convert list specification to frames. + + Returns: + list[int]: List of frame numbers. + + Raises: + ValueError: If invalid frame number in the list. + + """ frames = [] segments = list_spec.split(",") for segment in segments: @@ -364,7 +377,12 @@ class Sequence(TraitBase): @staticmethod def get_frame_padding(file_locations: FileLocations) -> int: - """Get frame padding.""" + """Get frame padding. + + Returns: + int: Frame padding. + + """ src_collection = Sequence._get_collection(file_locations) return len(str(max(src_collection.indexes))) @@ -382,6 +400,7 @@ class Sequence(TraitBase): Default clique pattern is:: \.(?P(?P0*)\d+)\.\D+\d?$ + Returns: list[int]: List of frame numbers. @@ -394,6 +413,9 @@ class Sequence(TraitBase): If the regex is string, it will compile it to the pattern. + Returns: + Pattern: Compiled regex pattern. + """ if self.frame_regex: if isinstance(self.frame_regex, str): diff --git a/client/ayon_core/pipeline/traits/trait.py b/client/ayon_core/pipeline/traits/trait.py index 695ebb54c8..f15c525495 100644 --- a/client/ayon_core/pipeline/traits/trait.py +++ b/client/ayon_core/pipeline/traits/trait.py @@ -57,7 +57,7 @@ class TraitBase(ABC, BaseModel): """Abstract attribute for description.""" ... - def validate_trait(self, representation: Representation) -> None: + def validate_trait(self, representation: Representation) -> None: # noqa: PLR6301 """Validate the trait. This method should be implemented in the derived classes to validate @@ -67,10 +67,6 @@ class TraitBase(ABC, BaseModel): Args: representation (Representation): Representation instance. - Raises: - TraitValidationError: If the trait is invalid - within representation. - """ return @@ -82,6 +78,9 @@ class TraitBase(ABC, BaseModel): This assumes Trait ID ends with `.v{version}`. If not, it will return None. + Returns: + Optional[int]: Trait version + """ version_regex = r"v(\d+)$" match = re.search(version_regex, str(cls.id)) @@ -106,7 +105,7 @@ class IncompatibleTraitVersionError(Exception): """ -class UpgradableTraitError(Generic[T], Exception): +class UpgradableTraitError(Exception, Generic[T]): """Upgradable trait version exception. This exception is raised when the trait can upgrade existing data @@ -118,7 +117,7 @@ class UpgradableTraitError(Generic[T], Exception): old_data: dict -class LooseMatchingTraitError(Generic[T], Exception): +class LooseMatchingTraitError(Exception, Generic[T]): """Loose matching trait exception. This exception is raised when the trait is found with a loose matching diff --git a/client/ayon_core/pipeline/traits/two_dimensional.py b/client/ayon_core/pipeline/traits/two_dimensional.py index 62d6693336..77aa5767d6 100644 --- a/client/ayon_core/pipeline/traits/two_dimensional.py +++ b/client/ayon_core/pipeline/traits/two_dimensional.py @@ -136,7 +136,15 @@ class UDIM(TraitBase): @field_validator("udim_regex") @classmethod def validate_frame_regex(cls, v: Optional[str]) -> Optional[str]: - """Validate udim regex.""" + """Validate udim regex. + + Returns: + Optional[str]: UDIM regex. + + Raises: + ValueError: UDIM regex must include 'udim' named group. + + """ if v is not None and "?P" not in v: msg = "UDIM regex must include 'udim' named group" raise ValueError(msg) diff --git a/client/ayon_core/pipeline/traits/utils.py b/client/ayon_core/pipeline/traits/utils.py index 54cc99a261..ef22122124 100644 --- a/client/ayon_core/pipeline/traits/utils.py +++ b/client/ayon_core/pipeline/traits/utils.py @@ -24,6 +24,9 @@ def get_sequence_from_files(paths: list[Path]) -> FrameRanged: Returns: FrameRanged: FrameRanged trait. + Raises: + ValueError: If paths cannot be assembled into one collection + """ cols, rems = assemble([path.as_posix() for path in paths]) if rems: diff --git a/pyproject.toml b/pyproject.toml index 86b6c27ae4..2b9a1fc5ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,7 @@ indent-width = 4 target-version = "py39" [tool.ruff.lint] +preview = true pydocstyle.convention = "google" # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. select = ["ALL"] @@ -85,13 +86,19 @@ ignore = [ "S603", "ERA001", "TRY003", - "UP006", # support for older python version (type vs. Type) - "UP007", # ..^ - "UP035", # .. + "UP006", # support for older python version (type vs. Type) + "UP007", # ..^ + "UP035", # .. + "UP045", # Use `X | None` for type annotations "ARG002", - "INP001", # add `__init__.py` to namespaced package - "FIX002", # FIX all TODOs - "TD003", # missing issue link + "INP001", # add `__init__.py` to namespaced package + "FIX002", # FIX all TODOs + "TD003", # missing issue link + "S404", # subprocess module is possibly insecure + "PLC0415", # import must be on top of the file + "CPY001", # missing copyright header + "UP045" + ] # Allow fix for all enabled rules (when `--fix`) is provided.