Skip to content

CMS Types

cmstypes

PathBase

Bases: TypedDict

Base class for Path object

Attributes:

Name Type Description
path str

The path string

siteId uuid_string

unique identifier string of the site associated with it

siteName str
Source code in cascade_cms/cmstypes.py
200
201
202
203
204
205
206
207
208
209
210
211
212
213
class PathBase(TypedDict):
    """
    Base class for Path object

    Attributes:
        path (str): The path string
        siteId (uuid_string): unique identifier string
                              of the site associated with it
        siteName (str):
    """

    path: str
    siteId: NotRequired[uuid.UUID]
    siteName: Annotated[str | None, Field(default=None)]

Path

Bases: PathBase

Represents a Path object that can be called directly. Substitution for TypeIdentifers with asset id Attributes: asset_type (AssetTypes): (Required) the type of the asset

Source code in cascade_cms/cmstypes.py
216
217
218
219
220
221
222
223
224
class Path(PathBase):
    """
    Represents a Path object that can be called directly.
    Substitution for TypeIdentifers with asset id
    Attributes:
        asset_type (AssetTypes): (Required) the type of the asset
    """

    asset_type: Literal[AssetTypes]

SimplePayload

Bases: BaseModel

Base class for Cascade CMS Payloads: Payloads are containers for specific Cascade operations that accept inputs

Source code in cascade_cms/cmstypes.py
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
class SimplePayload(BaseModel):
    """
    Base class for Cascade CMS Payloads:
    Payloads are containers for specific
    Cascade operations that accept inputs
    """

    model_config = ConfigDict(
        populate_by_name=True,
        from_attributes=True,
        validate_assignment=True,
        serialize_by_alias=True,
    )

    @model_serializer  # or @classmethod
    def format_builder(self) -> dict:
        """Wraps proper headers around payload data.

        Args:
            handler (SerializerFunctionWrapHandler): Pydantic validation function

        Returns:
            dict[str,object]: serialized wrapped inSerializerFunctionWrapHandler the inherit class name

            ```python
            {
                "searchInformation"{
                    ...
                }
            }
            ```

        """
        subclass_name = self.__class__.__name__
        try:
            fields_info = self.__pydantic_fields__  # Pydantic V3
        except AttributeError:
            fields_info = self.model_fields  # Pydantic V2

        def dump(value: Any) -> Any:
            # This dict comprehension only aliases top-level keys; a nested
            # BaseModel passed through as-is would serialize under its
            # Python field names instead of its aliases, so recurse.
            if isinstance(value, BaseModel):
                return value.model_dump(by_alias=True)
            if isinstance(value, list):
                return [dump(item) for item in value]
            return value

        aliased = {
            (fields_info[name].alias or name): dump(value)
            for name, value in self.__dict__.items()
            if name in fields_info
        }
        return {reformat_name(subclass_name): aliased}

format_builder()

Wraps proper headers around payload data.

Parameters:

Name Type Description Default
handler SerializerFunctionWrapHandler

Pydantic validation function

required

Returns:

Type Description
dict

dict[str,object]: serialized wrapped inSerializerFunctionWrapHandler the inherit class name

dict

```python

dict

{ "searchInformation"{ ... }

dict

}

dict

```

Source code in cascade_cms/cmstypes.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
@model_serializer  # or @classmethod
def format_builder(self) -> dict:
    """Wraps proper headers around payload data.

    Args:
        handler (SerializerFunctionWrapHandler): Pydantic validation function

    Returns:
        dict[str,object]: serialized wrapped inSerializerFunctionWrapHandler the inherit class name

        ```python
        {
            "searchInformation"{
                ...
            }
        }
        ```

    """
    subclass_name = self.__class__.__name__
    try:
        fields_info = self.__pydantic_fields__  # Pydantic V3
    except AttributeError:
        fields_info = self.model_fields  # Pydantic V2

    def dump(value: Any) -> Any:
        # This dict comprehension only aliases top-level keys; a nested
        # BaseModel passed through as-is would serialize under its
        # Python field names instead of its aliases, so recurse.
        if isinstance(value, BaseModel):
            return value.model_dump(by_alias=True)
        if isinstance(value, list):
            return [dump(item) for item in value]
        return value

    aliased = {
        (fields_info[name].alias or name): dump(value)
        for name, value in self.__dict__.items()
        if name in fields_info
    }
    return {reformat_name(subclass_name): aliased}

NewAsset

Bases: SimplePayload

Payload for the create operation.

Requires exactly one of site_name/site_id and exactly one of parent_folder_path/parent_folder_id (enforced by _check_required_alternatives). Extra fields are allowed and passed through, since asset-type-specific properties vary per asset_type.

Source code in cascade_cms/cmstypes.py
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
class NewAsset(SimplePayload):
    """Payload for the `create` operation.

    Requires exactly one of `site_name`/`site_id` and exactly one of
    `parent_folder_path`/`parent_folder_id` (enforced by
    `_check_required_alternatives`). Extra fields are allowed and passed
    through, since asset-type-specific properties vary per `asset_type`.
    """

    model_config = ConfigDict(
        extra="allow",
        validate_by_name=True,
        validate_by_alias=False,
    )

    name: str
    asset_type: AssetTypes
    site_name: str | None = Field(default=None, alias="siteName")
    site_id: uuid.UUID | None = Field(default=None, alias="siteId")
    parent_folder_path: str | None = Field(default=None, alias="parentFolderPath")
    parent_folder_id: uuid.UUID | None = Field(default=None, alias="parentFolderId")

    @field_serializer("site_id", "parent_folder_id")
    def serialize_uuid_as_hex(self, value: uuid.UUID | None) -> str | None:
        return value.hex if value is not None else None

    @model_validator(mode="after")
    def _check_required_alternatives(self) -> Self:
        if (self.site_name is None) == (self.site_id is None):
            raise ValueError("Provide exactly one of site_name or site_id")
        if (self.parent_folder_path is None) == (self.parent_folder_id is None):
            raise ValueError(
                "Provide exactly one of parent_folder_path or parent_folder_id"
            )
        return self

    @model_serializer(mode="wrap")
    def serialize_as_asset(self, handler: SerializerFunctionWrapHandler) -> dict:
        """Cascade expects {"asset": {"<type>": {...}}}"""
        payload_dict = handler(self)
        asset_type = payload_dict.pop("asset_type")
        cleaned = {k: v for k, v in payload_dict.items() if v is not None}
        return {"asset": {asset_type: cleaned}}

    def dump_json(self) -> bytes:
        return new_asset_adapter.dump_json(self, by_alias=True)

serialize_as_asset(handler)

Cascade expects {"asset": {"": {...}}}

Source code in cascade_cms/cmstypes.py
325
326
327
328
329
330
331
@model_serializer(mode="wrap")
def serialize_as_asset(self, handler: SerializerFunctionWrapHandler) -> dict:
    """Cascade expects {"asset": {"<type>": {...}}}"""
    payload_dict = handler(self)
    asset_type = payload_dict.pop("asset_type")
    cleaned = {k: v for k, v in payload_dict.items() if v is not None}
    return {"asset": {asset_type: cleaned}}

IdentifierType

Bases: BaseModel

Resolved reference to a Cascade asset: its UUID, type, and optional path info.

This is the "id-based" counterpart to Path (which references an asset by site + path string instead); see resolve_identifier.

Source code in cascade_cms/cmstypes.py
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
class IdentifierType(BaseModel):
    """Resolved reference to a Cascade asset: its UUID, type, and optional path info.

    This is the "id-based" counterpart to `Path` (which references an
    asset by site + path string instead); see `resolve_identifier`.
    """

    model_config = ConfigDict(
        frozen=True,
        extra="forbid",
        populate_by_name=True,
    )

    identifier: Annotated[uuid.UUID, Field(alias='id')]
    asset_type: Annotated[AssetTypes, Field(default=..., alias="type")]
    recycled: Annotated[bool | None, Field(default=None)] = None
    path: Annotated[PathBase | None, Field(default=None)] = None

    # Cascade rejects dashed UUIDs for identifiers - serialize as bare hex.
    @field_serializer("identifier")
    def serialize_identifier(self, value: uuid.UUID) -> str:
        return value.hex

    # getters
    @property
    def get_path(self):
        if self.path is not None:
            return self.path["path"]

    @property
    def get_sitename(self):
        if self.path is not None:
            return self.path["siteName"]

    @property
    def get_site_id(self):
        # siteId is NotRequired (unlike siteName, it has no Field default),
        # so pydantic may not populate the key at all — plain indexing would
        # KeyError on that legitimate case.
        if self.path is not None:
            return self.path.get("siteId")

    @property
    def get_id(self):
        return self.identifier.hex

    @property
    def get_type(self):
        return self.asset_type

    @model_validator(mode="before")
    @classmethod
    def reject_extra_fields(cls, values):
        if isinstance(values, dict):
            if "asset_type" in values and "type" not in values:
                values = {**values, "type": values["asset_type"]}
                values.pop("asset_type")
            if "identifier" in values and "id" not in values:
                values = {**values, "id": values["identifier"]}
                values.pop("identifier")

            allowed = {"id", "type", "recycled", "path"}
            extra = set(values) - allowed
            if extra:
                raise ValueError(
                    f"Identifier payload contains unexpected fields: {sorted(extra)}"
                )
        return values

workflowInformation

Bases: BaseModel

Response from the readWorkflowInformation operation, describing an asset's active workflow instance and its steps/actions.

Source code in cascade_cms/cmstypes.py
534
535
536
537
538
539
540
541
542
543
544
545
546
547
class workflowInformation(BaseModel):
    """Response from the `readWorkflowInformation` operation, describing an
    asset's active workflow instance and its steps/actions."""

    model_config = ConfigDict(frozen=True)

    related_entity: Annotated[IdentifierType, Field(alias="relatedEntity")]
    current_step: Annotated[str, Field(alias="currentStep")]
    ordered_steps: list[WorkflowSteps]
    unordered_steps: list[WorkflowSteps]
    start_date: datetime
    end_date: datetime
    name: str
    workflow_info_id: Annotated[uuid.UUID, Field(alias="workflowInfoId")]

Asset

Dynamic wrapper around a raw Cascade asset JSON payload.

Cascade asset payloads have the shape {"asset": {"<type>": {...}}} with a structure that varies per asset type, so unlike the Pydantic models above this is a thin dict-backed wrapper rather than a fixed schema: _data holds the inner {...} dict by reference, and __setattr__ only enforces that an existing field keeps its Python type when reassigned (it does not validate against a schema). pageConfigurations, if present, is parsed into PageConfiguration models up front for convenient access via get_page_configuration.

Source code in cascade_cms/cmstypes.py
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
class Asset:
    """Dynamic wrapper around a raw Cascade asset JSON payload.

    Cascade asset payloads have the shape `{"asset": {"<type>": {...}}}`
    with a structure that varies per asset type, so unlike the Pydantic
    models above this is a thin dict-backed wrapper rather than a fixed
    schema: `_data` holds the inner `{...}` dict by reference, and
    `__setattr__` only enforces that an existing field keeps its Python
    type when reassigned (it does not validate against a schema).
    `pageConfigurations`, if present, is parsed into `PageConfiguration`
    models up front for convenient access via `get_page_configuration`.
    """

    _asset_type: str
    _data: dict[str, Any]
    _page_configs: list[PageConfiguration]

    def __init__(self, data: dict):
        object.__setattr__(self, "_asset_type", next(iter(data["asset"].keys())))
        inner: dict[str, Any] = data["asset"][self._asset_type]
        object.__setattr__(self, "_data", inner)

        # Parse pageConfigurations into Pydantic models
        if "pageConfigurations" in self._data:
            object.__setattr__(
                self,
                "_page_configs",
                [
                    PageConfiguration(**config)
                    for config in self._data["pageConfigurations"]
                ],
            )
        else:
            object.__setattr__(self, "_page_configs", [])

    def __setattr__(self, key: str, value: object) -> None:
        if key.startswith("_"):
            object.__setattr__(self, key, value)
            return
        if key in self._data:
            current = self._data[key]
            if type(value) is not type(current):
                raise TypeError(
                    f"Field {key!r} has changed type: "
                    f"expected {type(current).__name__!r}, "
                    f"got {type(value).__name__!r}"
                )
        self._data[key] = value

    # Cascade wraps a response under a key that's usually the requested
    # asset_type re-cased (e.g. "dataDefinition" for a "datadefinition"
    # request), but not always: this handles the exceptions where the
    # wrapper key isn't a mechanical re-casing of the request-side type.
    _ASSET_TYPE_KEY_ALIASES: ClassVar[dict[str, str]] = {
        "scriptformat": "format",
    }

    @property
    def asset_type(self) -> str:
        """The request-side asset type (matching `AssetTypes`/`Path.asset_type`),
        normalized from the raw response wrapper key in `_asset_type`."""
        lowered = self._asset_type.lower()
        return self._ASSET_TYPE_KEY_ALIASES.get(lowered, lowered)

    def get(self, key: str, default=None):
        """Access _data fields conveniently."""
        return self._data.get(key, default)

    def get_data_structure(self: 'Asset', group: str, identifier: str) -> list[dict[str, Any]] | None:
        """
        Find nodes matching identifier within all instances of a group.
        Returns first match per group instance as a list of node objects by reference.
        """

        def find_group(obj):
            if isinstance(obj, dict):
                if obj.get("type") == "group" and obj.get("identifier") == group:
                    yield obj
                for value in obj.values():
                    yield from find_group(value)
            elif isinstance(obj, list):
                for item in obj:
                    yield from find_group(item)

        def find_in_nodes(nodes):
            for node in nodes:
                if (
                    node.get("identifier") == identifier
                    and "structuredDataNodes" not in node
                ):
                    return node
                if "structuredDataNodes" in node:
                    result = find_in_nodes(node["structuredDataNodes"])
                    if result:
                        return result
            return None

        matches = []
        for group_node in find_group(self._data):
            nodes = group_node.get("structuredDataNodes", [])
            match = find_in_nodes(nodes)
            if match:
                matches.append(match)

        return matches if matches else None

    def get_page_configuration(
        self, configuration_name: str, page_region: str | None = None
    ) -> PageConfiguration | PageRegion | None:
        """
        Find a page configuration and optionally a specific region within it.
        Returns Pydantic model objects by reference.

        Args:
            configuration_name: The 'name' of the configuration e.g. 'ASPX', 'XML'
            page_region:        The 'name' of the page region e.g. 'DEFAULT', 'FOOTER' (optional)

        Returns:
            - PageConfiguration object if only configuration_name is provided
            - PageRegion object if page_region is also provided
            - None if either is not found
        """
        config = next(
            (c for c in self._page_configs if c.name == configuration_name), None
        )

        if config is None:
            return None

        if page_region is None:
            return config

        region = next((r for r in config.pageRegions if r.name == page_region), None)

        return region

    # Field names Cascade exposes on a site asset for the root container of
    # each asset type. Only asset types confirmed against a real site payload
    # are listed here; unmapped types return None rather than guess.
    _ROOT_CONTAINER_FIELDS: ClassVar[dict[str, str]] = {
        "datadefinition": "rootDataDefinitionContainerId",
        "sharedfield": "rootSharedFieldContainerId",
        "folder": "rootFolderId",
    }

    def root_container_id(self, asset_type: "AssetTypes") -> uuid.UUID | None:
        """Return the root container id for `asset_type` on this site asset.

        `self` must be a `site` asset. Returns None if there's no known root
        field for `asset_type` (see `_ROOT_CONTAINER_FIELDS`).
        """
        field = self._ROOT_CONTAINER_FIELDS.get(asset_type)
        if field is None:
            return None
        value = self._data.get(field)
        if value is None:
            return None
        return uuid.UUID(value)

asset_type property

The request-side asset type (matching AssetTypes/Path.asset_type), normalized from the raw response wrapper key in _asset_type.

get(key, default=None)

Access _data fields conveniently.

Source code in cascade_cms/cmstypes.py
624
625
626
def get(self, key: str, default=None):
    """Access _data fields conveniently."""
    return self._data.get(key, default)

get_data_structure(group, identifier)

Find nodes matching identifier within all instances of a group. Returns first match per group instance as a list of node objects by reference.

Source code in cascade_cms/cmstypes.py
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
def get_data_structure(self: 'Asset', group: str, identifier: str) -> list[dict[str, Any]] | None:
    """
    Find nodes matching identifier within all instances of a group.
    Returns first match per group instance as a list of node objects by reference.
    """

    def find_group(obj):
        if isinstance(obj, dict):
            if obj.get("type") == "group" and obj.get("identifier") == group:
                yield obj
            for value in obj.values():
                yield from find_group(value)
        elif isinstance(obj, list):
            for item in obj:
                yield from find_group(item)

    def find_in_nodes(nodes):
        for node in nodes:
            if (
                node.get("identifier") == identifier
                and "structuredDataNodes" not in node
            ):
                return node
            if "structuredDataNodes" in node:
                result = find_in_nodes(node["structuredDataNodes"])
                if result:
                    return result
        return None

    matches = []
    for group_node in find_group(self._data):
        nodes = group_node.get("structuredDataNodes", [])
        match = find_in_nodes(nodes)
        if match:
            matches.append(match)

    return matches if matches else None

get_page_configuration(configuration_name, page_region=None)

Find a page configuration and optionally a specific region within it. Returns Pydantic model objects by reference.

Parameters:

Name Type Description Default
configuration_name str

The 'name' of the configuration e.g. 'ASPX', 'XML'

required
page_region str | None

The 'name' of the page region e.g. 'DEFAULT', 'FOOTER' (optional)

None

Returns:

Type Description
PageConfiguration | PageRegion | None
  • PageConfiguration object if only configuration_name is provided
PageConfiguration | PageRegion | None
  • PageRegion object if page_region is also provided
PageConfiguration | PageRegion | None
  • None if either is not found
Source code in cascade_cms/cmstypes.py
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
def get_page_configuration(
    self, configuration_name: str, page_region: str | None = None
) -> PageConfiguration | PageRegion | None:
    """
    Find a page configuration and optionally a specific region within it.
    Returns Pydantic model objects by reference.

    Args:
        configuration_name: The 'name' of the configuration e.g. 'ASPX', 'XML'
        page_region:        The 'name' of the page region e.g. 'DEFAULT', 'FOOTER' (optional)

    Returns:
        - PageConfiguration object if only configuration_name is provided
        - PageRegion object if page_region is also provided
        - None if either is not found
    """
    config = next(
        (c for c in self._page_configs if c.name == configuration_name), None
    )

    if config is None:
        return None

    if page_region is None:
        return config

    region = next((r for r in config.pageRegions if r.name == page_region), None)

    return region

root_container_id(asset_type)

Return the root container id for asset_type on this site asset.

self must be a site asset. Returns None if there's no known root field for asset_type (see _ROOT_CONTAINER_FIELDS).

Source code in cascade_cms/cmstypes.py
705
706
707
708
709
710
711
712
713
714
715
716
717
def root_container_id(self, asset_type: "AssetTypes") -> uuid.UUID | None:
    """Return the root container id for `asset_type` on this site asset.

    `self` must be a `site` asset. Returns None if there's no known root
    field for `asset_type` (see `_ROOT_CONTAINER_FIELDS`).
    """
    field = self._ROOT_CONTAINER_FIELDS.get(asset_type)
    if field is None:
        return None
    value = self._data.get(field)
    if value is None:
        return None
    return uuid.UUID(value)

Message

Bases: SimplePayload

A Cascade inbox message, also used as the payload for mark/delete-message operations.

Source code in cascade_cms/cmstypes.py
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
class Message(SimplePayload):
    """A Cascade inbox message, also used as the payload for mark/delete-message operations."""

    model_config = ConfigDict(populate_by_name=True)

    m_from: Annotated[str, Field(alias="from", exclude=True)]
    m_to: Annotated[str, Field(alias="to", exclude=True)]
    m_subject: Annotated[str, Field(alias="subject", exclude=True)]
    m_date: Annotated[datetime, Field(alias="date", exclude=True)]
    m_id: Annotated[uuid.UUID, Field(alias="id", exclude=True)]
    marked: str = Field("unread", alias="markType")

    @field_validator("m_date", mode="after")
    @classmethod
    def remove_timezone(cls, dt: datetime) -> datetime:
        return dt.replace(tzinfo=None)

CheckedOutAsset

Bases: BaseModel

Response from the checkOut operation, referencing the new working copy.

Source code in cascade_cms/cmstypes.py
741
742
743
744
745
746
class CheckedOutAsset(BaseModel):
    """Response from the `checkOut` operation, referencing the new working copy."""

    model_config = ConfigDict(frozen=True)

    workingCopyIdentifier: IdentifierType

ListElements

Bases: BaseModel

Response container for list-shaped endpoints (search, listSites, listMessages, readAudits, listSubscribers), whose JSON key varies by endpoint but is always aliased into elements via AliasChoices.

Source code in cascade_cms/cmstypes.py
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
class ListElements(BaseModel):
    """Response container for list-shaped endpoints (search, listSites, listMessages,
    readAudits, listSubscribers), whose JSON key varies by endpoint but is always
    aliased into `elements` via `AliasChoices`."""

    model_config = ConfigDict(frozen=True)
    elements: list[IdentifierType | Message | Audit] = Field(
        validation_alias=AliasChoices(
            "preferences",
            "matches",
            "messages",
            "relationships",
            "sites",
        )
    )

    @property
    def flat(self) -> list[IdentifierType | Message | Audit]:
        return self.elements

CascadeError

Bases: BaseModel

Represents a Cascade API-level failure response ({"success": false, "message": ...}).

Source code in cascade_cms/cmstypes.py
770
771
772
773
774
775
class CascadeError(BaseModel):
    """Represents a Cascade API-level failure response (`{"success": false, "message": ...}`)."""

    model_config = ConfigDict(frozen=True, extra='forbid')
    success: bool = False
    message: str = ""

CascadeSuccess

Bases: BaseModel

Represents a Cascade API-level success response with no further data ({"success": true}).

Source code in cascade_cms/cmstypes.py
778
779
780
781
782
class CascadeSuccess(BaseModel):
    """Represents a Cascade API-level success response with no further data (`{"success": true}`)."""

    model_config = ConfigDict(frozen=True)
    success: bool = True

SearchInformation

Bases: SimplePayload

Payload for the search operation.

Source code in cascade_cms/cmstypes.py
788
789
790
791
792
793
794
795
796
797
798
class SearchInformation(SimplePayload):
    """Payload for the `search` operation."""

    siteName: str
    searchTerms: str
    searchFields: list[FieldsSearchTypes] | list[Literal[""]] = Field(
        default_factory=lambda: [cast(Literal[""], "")]
    )
    searchTypes: list[AssetTypes] | list[Literal[""]] = Field(
        default_factory=lambda: [cast(Literal[""], "")]
    )

preference

Bases: SimplePayload

Payload for the editPreference operation (a single user preference name/value).

Source code in cascade_cms/cmstypes.py
801
802
803
804
805
class preference(SimplePayload):
    """Payload for the `editPreference` operation (a single user preference name/value)."""

    name: str
    value: str | None

deleteParameters

Bases: SimplePayload

Payload for the delete operation.

Source code in cascade_cms/cmstypes.py
808
809
810
811
812
813
class deleteParameters(SimplePayload):
    """Payload for the `delete` operation."""

    do_workflow: bool = Field(alias="doWorkflow")
    destinations_identifiers: list[IdentifierType] = Field(alias="destinations")
    unpublish: bool = True

copyParameters

Bases: SimplePayload

Payload for the copy operation.

Source code in cascade_cms/cmstypes.py
816
817
818
819
820
821
822
823
class copyParameters(SimplePayload):
    """Payload for the `copy` operation."""

    do_workflow: Annotated[bool, Field(alias="doWorkflow")]
    new_name: Annotated[str, Field(default=..., alias="newName")]
    destination_container_identifier: Annotated[
        IdentifierType, Field(alias="destinationContainerIdentifier")  # required
    ]

moveParameters

Bases: SimplePayload

Payload for the move operation.

Source code in cascade_cms/cmstypes.py
826
827
828
829
830
831
832
833
834
835
class moveParameters(SimplePayload):
    """Payload for the `move` operation."""

    destinations: list[IdentifierType]
    do_workflow: bool = Field(alias="doWorkflow")
    destination_container_identifier: IdentifierType = Field(
        alias="destinationContainerIdentifier"
    )
    new_name: str = Field(default="", alias="newName")  # empty new name means no rename
    unpublish: bool = True

publishInformation

Bases: SimplePayload

Payload for the publish operation.

Source code in cascade_cms/cmstypes.py
838
839
840
841
class publishInformation(SimplePayload):
    """Payload for the `publish` operation."""

    unpublish: bool = True

Comment

Bases: SimplePayload

Payload for the checkIn operation (a check-in comment).

Source code in cascade_cms/cmstypes.py
844
845
846
847
class Comment(SimplePayload):
    """Payload for the `checkIn` operation (a check-in comment)."""

    comment: str

SiteCopyParameter

Bases: SimplePayload

Payload for the siteCopy operation.

Source code in cascade_cms/cmstypes.py
850
851
852
853
854
class SiteCopyParameter(SimplePayload):
    """Payload for the `siteCopy` operation."""

    original_sitename: str | IdentifierType = Field(alias="originalSiteName")
    new_sitename: str = Field(alias="newSiteName")

workflowTransitionInformation

Bases: SimplePayload

Payload for the performWorkflowTransition operation.

Source code in cascade_cms/cmstypes.py
857
858
859
860
861
862
class workflowTransitionInformation(SimplePayload):
    """Payload for the `performWorkflowTransition` operation."""

    workflow_identifier: Annotated[uuid.UUID, Field(alias="workflowId")]
    action_identifier: Annotated[str, Field(alias="actionIdentifier")]
    transition_comment: str | None = Field(alias="transitionComment")

auditParameters

Bases: SimplePayload

Payload for the readAudits operation.

Source code in cascade_cms/cmstypes.py
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
class auditParameters(SimplePayload):
    """Payload for the `readAudits` operation."""

    auditType: AuditTypes
    by_identifier: IdentifierType = Field(alias="identifier")
    by_username: str | None = Field(default=None, alias="username")
    by_group: str | None = Field(default=None, alias="groupname")
    by_role: str | None = Field(default=None, alias="rolename")
    startDate: datetime | None = Field(default=None)
    endDate: datetime | None = Field(default=None)

    # make sure its only user, group, role
    @field_validator("by_identifier", mode="after")
    @classmethod
    def is_admin_entity(cls, identifier: IdentifierType) -> IdentifierType:
        if identifier.get_type not in {"user", "role", "group"}:
            raise ValueError("Identifier needs to be either user, role, or group.")
        return identifier

    """
    def toJson(self) -> str:
        return self.model_dump_json(
            by_alias=True,
            exclude_none=True,
        )
    """

AssetAdapter

Drop-in counterpart to TypeAdapter for Asset objects.

Mirrors TypeAdapter's validate_json / dump_json interface so callers never need isinstance checks to decide how to serialize/deserialize.

Source code in cascade_cms/cmstypes.py
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
class AssetAdapter:
    """Drop-in counterpart to TypeAdapter for Asset objects.

    Mirrors TypeAdapter's validate_json / dump_json interface so callers
    never need isinstance checks to decide how to serialize/deserialize.
    """

    def validate_json(self, json_str: bytes | str) -> Asset:
        return Asset(json.loads(json_str))

    def dump_json(self, asset: Asset) -> bytes:
        # `_page_configs` only models `name`/`pageRegions[].content` for
        # convenient access via `get_page_configuration`; it is not
        # authoritative on write. `_data["pageConfigurations"]` (copied
        # below via `asset._data`) still holds the original raw dicts,
        # including fields the model doesn't parse (templateId, blockId,
        # formatId, ...), so round-tripping preserves them.
        data = {**asset._data}
        reconstructed = {"asset": {asset._asset_type: data}}
        return json.dumps(reconstructed).encode()

ResponseParser

Bases: BaseModel

Parses a raw response body, trying CascadeError first and falling back to serializer on the expected success shape.

_content holds the parsed result (either a CascadeError or a T), and _cacheable is set to True only when the success-path parse succeeds, so error responses are never cached.

Source code in cascade_cms/cmstypes.py
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
class ResponseParser[T](BaseModel):
    """Parses a raw response body, trying `CascadeError` first and falling
    back to `serializer` on the expected success shape.

    `_content` holds the parsed result (either a `CascadeError` or a `T`),
    and `_cacheable` is set to True only when the success-path parse
    succeeds, so error responses are never cached.
    """

    model_config = ConfigDict(arbitrary_types_allowed=True)

    serializer: TypeAdapter[Any] | AssetAdapter | None = None
    _content: T | CascadeError | None = PrivateAttr(default=None)
    _cacheable: bool = PrivateAttr(default=False)

    def __init__(
        self,
        raw: bytes,
        serializer: TypeAdapter[Any] | AssetAdapter | None = None,
        **kwargs,
    ):
        super().__init__(serializer=serializer, **kwargs)
        try:
            self._content = CascadeError.model_validate_json(raw)
        except ValidationError:
            if self.serializer is None:
                raise RuntimeWarning("No serializer included...")
            self._content = self.serializer.validate_json(raw)  # type: ignore[assignment]
            self._cacheable = True

reformat_name(class_name)

Lowercase the first letter of a class name (e.g. "NewAsset" -> "newAsset").

Source code in cascade_cms/cmstypes.py
177
178
179
180
181
def reformat_name(class_name: str):
    """Lowercase the first letter of a class name (e.g. "NewAsset" -> "newAsset")."""
    if class_name[0].isupper():
        return class_name[0].lower() + class_name[1:]
    return class_name

set_checkedout(key)

Toggle a checkout-segment key in the local checkout ledger.

Called once per checkIn/checkOut operation queued, so calling it twice for the same key (once on checkOut, once on the matching checkIn) flips it back out again rather than accumulating duplicates.

Source code in cascade_cms/cmstypes.py
184
185
186
187
188
189
190
191
192
193
194
def set_checkedout(key: str):
    """Toggle a checkout-segment key in the local checkout ledger.

    Called once per checkIn/checkOut operation queued, so calling it twice
    for the same key (once on checkOut, once on the matching checkIn) flips
    it back out again rather than accumulating duplicates.
    """
    if key in ALL_CHECKOUT_ASSETS:
        ALL_CHECKOUT_ASSETS.discard(key)
    else:
        ALL_CHECKOUT_ASSETS.add(key)

resolve_identifier(identifier)

Returns the URL path segments (after the operation name) identifying this asset.

IdentifierType resolves to (asset_type, id). Path resolves to (asset_type, siteName, path), matching the REST endpoint shape .../{operation_name}/{asset_type}/{siteName}/{path}.

Source code in cascade_cms/cmstypes.py
412
413
414
415
416
417
418
419
420
421
422
423
def resolve_identifier(identifier: "IdentifierType | Path") -> tuple[str, ...]:
    """Returns the URL path segments (after the operation name) identifying this asset.

    IdentifierType resolves to (asset_type, id). Path resolves to
    (asset_type, siteName, path), matching the REST endpoint shape
    `.../{operation_name}/{asset_type}/{siteName}/{path}`.
    """
    if isinstance(identifier, IdentifierType):
        return (str(identifier.get_type), str(identifier.get_id))
    if identifier.get("siteName") is None:
        raise ValueError("Path identifiers require siteName to build the request URL")
    return (str(identifier["asset_type"]), str(identifier["siteName"]), str(identifier["path"]))

identifier_from_asset(asset)

Build an IdentifierType from an Asset's own id/path/site fields.

A Cascade asset payload carries its own identity as flat _data keys (id, path, siteId, siteName) rather than the nested path shape IdentifierType.path (PathBase) expects, so this reshapes one into the other. Used by edit() to derive a request's identifier from the asset being saved, rather than from a separately-supplied identifier argument.

Does not itself guard against a missing idUUID(None) raises on that naturally — since whether a missing id is tolerable is a decision for the caller (e.g. OperationChain._edit_identifier raises a specific, actionable error for its call path instead of leaving this bare failure).

Source code in cascade_cms/cmstypes.py
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
def identifier_from_asset(asset: "Asset") -> IdentifierType:
    """Build an `IdentifierType` from an `Asset`'s own id/path/site fields.

    A Cascade asset payload carries its own identity as flat `_data` keys
    (`id`, `path`, `siteId`, `siteName`) rather than the nested `path` shape
    `IdentifierType.path` (`PathBase`) expects, so this reshapes one into the
    other. Used by `edit()` to derive a request's identifier from the asset
    being saved, rather than from a separately-supplied identifier argument.

    Does not itself guard against a missing `id` — `UUID(None)` raises on
    that naturally — since whether a missing id is tolerable is a decision
    for the caller (e.g. `OperationChain._edit_identifier` raises a specific,
    actionable error for its call path instead of leaving this bare failure).
    """
    path_value: PathBase = {
        "path": asset.get("path"),
        "siteName": asset.get("siteName"),
    }
    site_id = asset.get("siteId")
    if site_id:
        path_value["siteId"] = uuid.UUID(site_id)

    return IdentifierType(
        identifier=uuid.UUID(asset.get("id")),
        asset_type=cast(AssetTypes, asset.asset_type),
        path=path_value,
    )

serialize_payload(payload)

Serialize a request payload to JSON bytes, dispatching by payload type.

Source code in cascade_cms/cmstypes.py
966
967
968
969
970
971
972
def serialize_payload(payload: Payloads) -> bytes:
    """Serialize a request payload to JSON bytes, dispatching by payload type."""
    if isinstance(payload, Asset):
        return asset_adapter.dump_json(payload)
    elif isinstance(payload, NewAsset):
        return new_asset_adapter.dump_json(payload, by_alias=True)
    return simple_payload_adapter.dump_json(payload)

parse_assets(raw)

Parse a read response body into an Asset.

Source code in cascade_cms/cmstypes.py
1012
1013
1014
1015
def parse_assets(raw: bytes) -> ResponseParser[Asset]:
    """Parse a `read` response body into an `Asset`."""
    a: ResponseParser[Asset] = ResponseParser(raw=raw, serializer=asset_adapter)
    return a

parse_list_elements(raw)

Parse a list-shaped response body (search, listSites, etc.) into ListElements.

Source code in cascade_cms/cmstypes.py
1018
1019
1020
1021
def parse_list_elements(raw: bytes) -> ResponseParser[ListElements]:
    """Parse a list-shaped response body (search, listSites, etc.) into `ListElements`."""
    a: ResponseParser[ListElements] = ResponseParser(raw, serializer=list_element_adapter)
    return a

parse_payloads(raw)

Parse a generic response body into the appropriate SimplePayload subclass.

Source code in cascade_cms/cmstypes.py
1024
1025
1026
def parse_payloads(raw: bytes) -> ResponseParser[SimplePayload]:
    """Parse a generic response body into the appropriate `SimplePayload` subclass."""
    return ResponseParser(raw=raw, serializer=simple_payload_adapter)

parse_create_asset(raw, pass_type)

Parse a create response, rebuilding an IdentifierType from createdAssetId.

Cascade's create response only returns the new asset's id, not its type, so pass_type (the asset_type from the original create payload, bound via functools.partial in Operations.create) is injected to reconstruct a full IdentifierType.

Source code in cascade_cms/cmstypes.py
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
def parse_create_asset(raw: bytes, pass_type: str) -> ResponseParser[IdentifierType]:
    """Parse a `create` response, rebuilding an `IdentifierType` from `createdAssetId`.

    Cascade's create response only returns the new asset's id, not its
    type, so `pass_type` (the `asset_type` from the original create
    payload, bound via `functools.partial` in `Operations.create`) is
    injected to reconstruct a full `IdentifierType`.
    """
    data = json.loads(raw)
    if data.get("createdAssetId") is not None: # we know that the creation succeeded
        identifier_payload = {"id": data["createdAssetId"], "type": pass_type}

        return ResponseParser(
            json.dumps(identifier_payload).encode(),
            serializer=identifier_type_adapter,
        )
    return ResponseParser( # `createdAssetId` does NOT exist we know that its most likely a error
        raw,
        serializer=identifier_type_adapter,
    )

parse_access_rights(raw)

Parse a readAccessRights response body.

Source code in cascade_cms/cmstypes.py
1051
1052
1053
def parse_access_rights(raw: bytes) -> ResponseParser[accessRightsInformationPayload]:
    """Parse a `readAccessRights` response body."""
    return ResponseParser(raw=raw, serializer=access_rights_adapter)

parse_workflow_settings(raw)

Parse a readWorkflowSettings response body.

Source code in cascade_cms/cmstypes.py
1056
1057
1058
def parse_workflow_settings(raw: bytes) -> ResponseParser[workflowSettingsPayload]:
    """Parse a `readWorkflowSettings` response body."""
    return ResponseParser(raw=raw, serializer=workflow_settings_adapter)

parse_checked_out_asset(raw)

Parse a checkOut response body.

Source code in cascade_cms/cmstypes.py
1061
1062
1063
def parse_checked_out_asset(raw: bytes) -> ResponseParser[CheckedOutAsset]:
    """Parse a `checkOut` response body."""
    return ResponseParser(raw=raw, serializer=checked_out_adapter)

parse_workflow_information(raw)

Parse a readWorkflowInformation response body.

Source code in cascade_cms/cmstypes.py
1066
1067
1068
def parse_workflow_information(raw: bytes) -> ResponseParser[workflowInformation]:
    """Parse a `readWorkflowInformation` response body."""
    return ResponseParser(raw=raw, serializer=workflow_info_adapter)

parse_success(raw)

Parse a bare {"success": true} response body from a write operation.

Source code in cascade_cms/cmstypes.py
1071
1072
1073
def parse_success(raw: bytes) -> ResponseParser[CascadeSuccess]:
    """Parse a bare `{"success": true}` response body from a write operation."""
    return ResponseParser(raw=raw, serializer=cascade_success_adapter)