# Custom System
This tutorial describes how to create and use a custom system in a parent package.

1. Define the system. This example defines some custom attributes to illustrate serialization and
de-serialization behaviors.

```python
from typing import Any

from infrasys import System

class CustomSystem(System):
    """Custom System"""

    def __init__(self, my_attribute=5, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.data_format_version = "1.1.0"
        self.my_attribute = my_attribute

    def serialize_system_attributes(self) -> dict[str, Any]:
        return {"my_attribute": self.my_attribute}

    def deserialize_system_attributes(self, data: dict[str, Any]) -> None:
        self.my_attribute = data["my_attribute"]

    def handle_data_format_upgrade(self, data: dict[str, Any], from_version, to_version)) -> None:
        ...
```

**Notes**:

- The system's custom attribute `my_attribute` will be serialized and de-serialized automatically.
- `infrasys` will call handle_data_format_upgrade during de-serialization so that this package
can handle format changes that might occur in the future.

2. Define some component classes.

```python
from uuid import UUID
from infrasys.component import  Component
from infrasys.location import Location

class Bus(Component):
    """Represents a bus."""

    voltage: float
    coordinates: Location | None = None

    @classmethod
    def example(cls) -> "Bus":
        return Bus(
            name="my-bus",
            voltage=1.1,
            coordinates=Location(x=0.0, y=0.0),
        )

class Generator(Component):
    """Represents a generator."""

    available: bool
    bus: Bus
    active_power: float
    rating: float

    @classmethod
    def example(cls) -> "Generator":
        return Generator(
            name="simple-gen",
            available=True,
            bus=Bus.example(),
            active_power=1.0,
            rating=0.0,
        )
```

**Notes**:

- Each component defines the `example` method. This is highly recommended so that users can see
what a component might look like in the REPL.

- The `Bus` class implements a custom check when it is added to the system. It raises an exception
if its `Location` object is not already attached to the system. The same could be done for
generators and buses.

3. Build a system.

```python
import random
from datetime import datetime, timedelta

from infrasys.location import Location
from infrasys.time_series_models import SingleTimeSeries

system = CustomSystem(name="my_system", my_attribute=10)
location = Location(x=0.0, y=0.0)
bus = Bus(name="bus1", voltage=1.1, coordinates=location)
gen = Generator(name="gen1", available=True, bus=bus, active_power=1.2, rating=1.1)
system.add_components(location, bus, gen)
time_series = SingleTimeSeries.from_array(
    data=[random.random() for x in range(24)],
    variable_name="active_power",
    initial_time=datetime(year=2030, month=1, day=1),
    resolution=timedelta(hours=1),
)
system.add_time_series(time_series, gen)
```

4. Serialize and de-serialize the system.

```python
system.to_json("system.json")
system2 = CustomSystem.from_json("system.json")
assert system.get_component(Generator, "gen1").active_power == \
    system2.get_component(Generator, "gen1").active_power
```
