Skip to content

collection

collection

Collection model for ETM collection management.

Classes

CollectionError

Bases: Exception

Base collection error

Collection

Collection(**data)

Bases: Base

Pydantic model for a MyETM Collection (transition path).

A Collection groups multiple scenarios together, optionally with interpolation for multi-year projections.

Source code in src/pyetm/models/base.py
def __init__(self, **data: Any) -> None:
    """
    Initialize the model, converting validation errors to warnings.
    """
    super(BaseModel, self).__setattr__("__pydantic_private__", {})

    # Initialize all private attributes with their defaults
    private_dict: Dict[str, Any] = self.__pydantic_private__  # type: ignore[assignment]
    for attr_name, attr_info in self.__class__.__private_attributes__.items():
        if (
            hasattr(attr_info, "default_factory")
            and attr_info.default_factory is not None
        ):
            # Call factory - signature varies between pydantic versions
            private_dict[attr_name] = attr_info.default_factory()
        elif hasattr(attr_info, "default"):
            private_dict[attr_name] = attr_info.default
        else:
            private_dict[attr_name] = None

    try:
        super().__init__(**data)
    except ValidationError as e:
        # Check if data is None or empty - this indicates API error
        if not data or data is None:
            # Re-raise with clearer message
            raise ValueError(
                f"Cannot create {self.__class__.__name__} with empty data. "
                "This usually indicates an authentication or API error."
            ) from e

        # If validation fails, create model without validation and collect warnings
        # Use model_construct to bypass validation
        temp_instance = self.__class__.model_construct(**data)

        # Copy the constructed data to this instance
        for field_name, field_value in temp_instance.__dict__.items():
            if not field_name.startswith("_"):
                object.__setattr__(self, field_name, field_value)

        # Ensure required Pydantic slot attributes exist to prevent AttributeError
        for slot in ("__pydantic_fields_set__", "__pydantic_extra__"):
            try:
                value = object.__getattribute__(temp_instance, slot)
                object.__setattr__(self, slot, value)
            except AttributeError:
                # Initialize missing slot attributes with defaults
                if slot == "__pydantic_extra__":
                    object.__setattr__(self, slot, {})
                elif slot == "__pydantic_fields_set__":
                    object.__setattr__(self, slot, set())

        # Convert validation errors to warnings
        for error in e.errors():
            field_path = ".".join(str(part) for part in error.get("loc", []))
            message = error.get("msg", "Validation failed")
            self._warning_collector.add(field_path, message, "error")
Functions
create classmethod
create(title, scenario_ids=None, saved_scenario_ids=None, area_code=None, end_year=None, interpolation=False, client=None, **kwargs)

Create a new Collection in MyETM.

Parameters:

Name Type Description Default
title str

Title for the collection (required)

required
scenario_ids Optional[List[int]]

List of ETEngine scenario IDs to include

None
saved_scenario_ids Optional[List[int]]

List of MyETM saved scenario IDs to include

None
area_code Optional[str]

Area code for interpolated collections

None
end_year Optional[int]

End year for interpolated collections

None
interpolation bool

Whether this is an interpolated collection

False
client Optional[BaseClient]

Optional BaseClient instance

None
**kwargs Any

Additional collection parameters

{}

Returns:

Name Type Description
Collection 'Collection'

The created collection

Raises:

Type Description
CollectionError

If creation fails

Example

collection = Collection.create( title="My Collection", saved_scenario_ids=[1, 2, 3], interpolation=False )

Source code in src/pyetm/models/collection.py
@classmethod
def create(
    cls,
    title: str,
    scenario_ids: Optional[List[int]] = None,
    saved_scenario_ids: Optional[List[int]] = None,
    area_code: Optional[str] = None,
    end_year: Optional[int] = None,
    interpolation: bool = False,
    client: Optional[BaseClient] = None,
    **kwargs: Any,
) -> "Collection":
    """
    Create a new Collection in MyETM.

    Args:
        title: Title for the collection (required)
        scenario_ids: List of ETEngine scenario IDs to include
        saved_scenario_ids: List of MyETM saved scenario IDs to include
        area_code: Area code for interpolated collections
        end_year: End year for interpolated collections
        interpolation: Whether this is an interpolated collection
        client: Optional BaseClient instance
        **kwargs: Additional collection parameters

    Returns:
        Collection: The created collection

    Raises:
        CollectionError: If creation fails

    Example:
        collection = Collection.create(
            title="My Collection",
            saved_scenario_ids=[1, 2, 3],
            interpolation=False
        )
    """
    if client is None:
        client = get_client()

    collection_data = {
        "title": title,
        "interpolation": interpolation,
        **kwargs,
    }

    if scenario_ids:
        collection_data["scenario_ids"] = scenario_ids
    if saved_scenario_ids:
        collection_data["saved_scenario_ids"] = saved_scenario_ids
    if area_code:
        collection_data["area_code"] = area_code
    if end_year:
        collection_data["end_year"] = end_year

    result = CreateCollectionRunner.run(client, collection_data)

    if not result.success:
        raise CollectionError(f"Could not create collection: {result.errors}")

    collection = cls.model_validate(result.data)
    collection._client = client

    for warning in result.errors:
        collection.add_warning("base", warning)

    return collection
load classmethod
load(collection_id, client=None)

Load a Collection from MyETM by ID.

Parameters:

Name Type Description Default
collection_id int

ID of the collection to load

required
client Optional[BaseClient]

Optional BaseClient instance

None

Returns:

Name Type Description
Collection 'Collection'

The loaded collection

Raises:

Type Description
CollectionError

If collection not found or loading fails

Example

collection = Collection.load(123) print(f"Collection: {collection.title}")

Source code in src/pyetm/models/collection.py
@classmethod
def load(cls, collection_id: int, client: Optional[BaseClient] = None) -> "Collection":
    """
    Load a Collection from MyETM by ID.

    Args:
        collection_id: ID of the collection to load
        client: Optional BaseClient instance

    Returns:
        Collection: The loaded collection

    Raises:
        CollectionError: If collection not found or loading fails

    Example:
        collection = Collection.load(123)
        print(f"Collection: {collection.title}")
    """
    if client is None:
        client = get_client()

    result = FetchCollectionRunner.run(client, collection_id)

    if not result.success:
        raise CollectionError(f"Could not load collection: {result.errors}")

    collection = cls.model_validate(result.data)
    collection._client = client

    for warning in result.errors:
        collection.add_warning("base", warning)

    return collection
load_all classmethod
load_all(client=None)

Load all collections for the authenticated user.

Parameters:

Name Type Description Default
client Optional[BaseClient]

Optional BaseClient instance

None

Returns:

Type Description
List['Collection']

List[Collection]: List of all user's collections

Raises:

Type Description
CollectionError

If loading fails

Example

collections = Collection.load_all() for collection in collections: print(f"{collection.id}: {collection.title}")

Source code in src/pyetm/models/collection.py
@classmethod
def load_all(cls, client: Optional[BaseClient] = None) -> List["Collection"]:
    """
    Load all collections for the authenticated user.

    Args:
        client: Optional BaseClient instance

    Returns:
        List[Collection]: List of all user's collections

    Raises:
        CollectionError: If loading fails

    Example:
        collections = Collection.load_all()
        for collection in collections:
            print(f"{collection.id}: {collection.title}")
    """
    if client is None:
        client = get_client()

    result = FetchUserCollectionsRunner.run(client)

    if not result.success:
        raise CollectionError(f"Could not load collections: {result.errors}")

    if result.data is None:
        return []

    collections = [cls.model_validate(data) for data in result.data]

    # Store client for future operations
    for collection in collections:
        collection._client = client

    return collections
update
update(client=None, **kwargs)

Update this Collection.

Parameters:

Name Type Description Default
client Optional[BaseClient]

Optional BaseClient instance

None
**kwargs Any

Fields to update (title, area_code, end_year, interpolation, discarded, scenario_ids, saved_scenario_ids)

{}

Raises:

Type Description
CollectionError

If update fails

Example

collection = Collection.load(123) collection.update(title="Updated Title", saved_scenario_ids=[1, 2, 3, 4])

Source code in src/pyetm/models/collection.py
def update(self, client: Optional[BaseClient] = None, **kwargs: Any) -> None:
    """
    Update this Collection.

    Args:
        client: Optional BaseClient instance
        **kwargs: Fields to update (title, area_code, end_year, interpolation,
                 discarded, scenario_ids, saved_scenario_ids)

    Raises:
        CollectionError: If update fails

    Example:
        collection = Collection.load(123)
        collection.update(title="Updated Title", saved_scenario_ids=[1, 2, 3, 4])
    """
    if client is None:
        client = get_client()

    result = UpdateCollectionRunner.run(client, self.id, kwargs)

    if not result.success:
        raise CollectionError(f"Could not update collection: {result.errors}")

    for warning in result.errors:
        self.add_warning("update", warning)

    if result.data:
        for field, value in result.data.items():
            if hasattr(self, field):
                setattr(self, field, value)

    for field, value in kwargs.items():
        if hasattr(self, field) and (not result.data or field not in result.data):
            setattr(self, field, value)
delete
delete(client=None)

Permanently delete this Collection from MyETM (hard delete).

WARNING: This is a PERMANENT deletion and CANNOT be undone. The collection will be permanently removed from MyETM.

NOTE: SavedScenarios within the collection are NOT deleted. Only the collection itself is removed. The scenarios will remain in MyETM and can still be accessed individually.

Parameters:

Name Type Description Default
client Optional[BaseClient]

Optional BaseClient instance

None

Raises:

Type Description
CollectionError

If deletion fails

Example

collection = Collection.load(123) collection.delete() # PERMANENT deletion - cannot be recovered

Source code in src/pyetm/models/collection.py
def delete(self, client: Optional[BaseClient] = None) -> None:
    """
    Permanently delete this Collection from MyETM (hard delete).

    WARNING: This is a PERMANENT deletion and CANNOT be undone. The collection
    will be permanently removed from MyETM.

    NOTE: SavedScenarios within the collection are NOT deleted. Only the
    collection itself is removed. The scenarios will remain in MyETM and
    can still be accessed individually.

    Args:
        client: Optional BaseClient instance

    Raises:
        CollectionError: If deletion fails

    Example:
        collection = Collection.load(123)
        collection.delete()  # PERMANENT deletion - cannot be recovered
    """
    if client is None:
        client = get_client()

    result = DeleteCollectionRunner.run(client, self.id)

    if not result.success:
        raise CollectionError(f"Could not delete collection: {result.errors}")
to_dict
to_dict()

Convert collection to dictionary.

Returns:

Type Description
Dict[str, Any]

Dict[str, Any]: Dictionary representation of the collection

Source code in src/pyetm/models/collection.py
def to_dict(self) -> Dict[str, Any]:
    """
    Convert collection to dictionary.

    Returns:
        Dict[str, Any]: Dictionary representation of the collection
    """
    return self.model_dump(mode="python")

Functions