Skip to content

base_classes

based_base

BasedBase

Bases: SerializerComponent

Source code in src/easyscience/base_classes/based_base.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
class BasedBase(SerializerComponent):
    __slots__ = ['_name', '_global_object', 'user_data', '_kwargs']

    _REDIRECT = {}

    def __init__(
        self,
        name: str,
        interface: Optional[InterfaceFactoryTemplate] = None,
        unique_name: Optional[str] = None,
    ):
        self._global_object = global_object
        if unique_name is None:
            unique_name = self._global_object.generate_unique_name(self.__class__.__name__)
        self._unique_name = unique_name
        self._name = name
        self._global_object.map.add_vertex(self, obj_type='created')
        self.interface = interface
        self.user_data: dict = {}

    @property
    def _arg_spec(self) -> Set[str]:
        base_cls = getattr(self, '__old_class__', self.__class__)
        sign = signature(base_cls.__init__)
        names = [
            param.name
            for param in sign.parameters.values()
            if param.kind == param.POSITIONAL_OR_KEYWORD
        ]
        return set(names[1:])

    def __reduce__(self):
        """Make the class picklable. Due to the nature of the dynamic
        class definitions special measures need to be taken.

        :return: Tuple consisting of how to make the object
        :rtype: tuple
        """
        state = self.encode()
        cls = getattr(self, '__old_class__', self.__class__)
        return cls.from_dict, (state,)

    @property
    def unique_name(self) -> str:
        """Get the unique name of the object."""
        return self._unique_name

    @unique_name.setter
    def unique_name(self, new_unique_name: str):
        """Set a new unique name for the object. The old name is still
        kept in the map.

        :param new_unique_name: New unique name for the object
        """
        if not isinstance(new_unique_name, str):
            raise TypeError('Unique name has to be a string.')
        self._unique_name = new_unique_name
        self._global_object.map.add_vertex(self)

    @property
    def name(self) -> str:
        """Get the common name of the object.

        :return: Common name of the object
        """
        return self._name

    @name.setter
    def name(self, new_name: str):
        """Set a new common name for the object.

        :param new_name: New name for the object
        :return: None
        """
        self._name = new_name

    @property
    def interface(self) -> InterfaceFactoryTemplate:
        """Get the current interface of the object."""
        return self._interface

    @interface.setter
    def interface(self, new_interface: InterfaceFactoryTemplate):
        """Set the current interface to the object and generate bindings
        if possible.

        iF.e. ``` def __init__(self, bar, interface=None, **kwargs):
        super().__init__(self, **kwargs)     self.foo = bar
        self.interface = interface  # As final step after initialization
        to set correct bindings. ```
        """
        self._interface = new_interface
        if new_interface is not None:
            self.generate_bindings()

    def generate_bindings(self):
        """Generate or re-generate bindings to an interface (if exists)

        :raises: AttributeError
        """
        if self.interface is None:
            raise AttributeError(
                'Interface error for generating bindings. `interface` has to be set.'
            )
        interfaceable_children = [
            key
            for key in self._global_object.map.get_edges(self)
            if issubclass(type(self._global_object.map.get_item_by_key(key)), BasedBase)
        ]
        for child_key in interfaceable_children:
            child = self._global_object.map.get_item_by_key(child_key)
            child.interface = self.interface
        self.interface.generate_bindings(self)

    def switch_interface(self, new_interface_name: str):
        """Switch or create a new interface."""
        if self.interface is None:
            raise AttributeError(
                'Interface error for generating bindings. `interface` has to be set.'
            )
        self.interface.switch(new_interface_name)
        self.generate_bindings()

    def get_parameters(self) -> List[Parameter]:
        """Get all parameter objects as a list.

        :return: List of `Parameter` objects.
        """
        par_list = []
        for key, item in self._kwargs.items():
            if hasattr(item, 'get_parameters'):
                par_list = [*par_list, *item.get_parameters()]
            elif isinstance(item, Parameter):
                par_list.append(item)
        return par_list

    def _get_linkable_attributes(self) -> List[DescriptorBase]:
        """Get all objects which can be linked against as a list.

        :return: List of `Descriptor`/`Parameter` objects.
        """
        item_list = []
        for key, item in self._kwargs.items():
            if hasattr(item, '_get_linkable_attributes'):
                item_list = [*item_list, *item._get_linkable_attributes()]
            elif issubclass(type(item), (DescriptorBase)):
                item_list.append(item)
        return item_list

    def get_fit_parameters(self) -> List[Parameter]:
        """Get all objects which can be fitted (and are not fixed) as a
        list.

        :return: List of `Parameter` objects which can be used in fitting.
        """
        fit_list = []
        for key, item in self._kwargs.items():
            if hasattr(item, 'get_fit_parameters'):
                fit_list = [*fit_list, *item.get_fit_parameters()]
            elif isinstance(item, Parameter):
                if item.independent and not item.fixed:
                    fit_list.append(item)
        return fit_list

    def __dir__(self) -> Iterable[str]:
        """This creates auto-completion and helps out in iPython
        notebooks.

        :return: list of function and parameter names for auto-
            completion
        """
        new_class_objs = list(k for k in dir(self.__class__) if not k.startswith('_'))
        return sorted(new_class_objs)

    def __copy__(self) -> BasedBase:
        """Return a copy of the object."""
        temp = self.as_dict(skip=['unique_name'])
        new_obj = self.__class__.from_dict(temp)
        return new_obj

    def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]:
        """Convert an object into a full dictionary using
        `SerializerDict`. This is a shortcut for
        ```obj.encode(encoder=SerializerDict)```

        :param skip: List of field names as strings to skip when forming
            the dictionary
        :return: encoded object containing all information to reform an
            EasyScience object.
        """
        # extend skip to include unique_name by default
        if skip is None:
            skip = []
        if 'unique_name' not in skip:
            skip.append('unique_name')
        return super().as_dict(skip=skip)

__copy__()

Return a copy of the object.

Source code in src/easyscience/base_classes/based_base.py
199
200
201
202
203
def __copy__(self) -> BasedBase:
    """Return a copy of the object."""
    temp = self.as_dict(skip=['unique_name'])
    new_obj = self.__class__.from_dict(temp)
    return new_obj

__dir__()

This creates auto-completion and helps out in iPython notebooks.

:return: list of function and parameter names for auto- completion

Source code in src/easyscience/base_classes/based_base.py
189
190
191
192
193
194
195
196
197
def __dir__(self) -> Iterable[str]:
    """This creates auto-completion and helps out in iPython
    notebooks.

    :return: list of function and parameter names for auto-
        completion
    """
    new_class_objs = list(k for k in dir(self.__class__) if not k.startswith('_'))
    return sorted(new_class_objs)

__reduce__()

Make the class picklable. Due to the nature of the dynamic class definitions special measures need to be taken.

:return: Tuple consisting of how to make the object :rtype: tuple

Source code in src/easyscience/base_classes/based_base.py
56
57
58
59
60
61
62
63
64
65
def __reduce__(self):
    """Make the class picklable. Due to the nature of the dynamic
    class definitions special measures need to be taken.

    :return: Tuple consisting of how to make the object
    :rtype: tuple
    """
    state = self.encode()
    cls = getattr(self, '__old_class__', self.__class__)
    return cls.from_dict, (state,)

as_dict(skip=None)

Convert an object into a full dictionary using SerializerDict. This is a shortcut for obj.encode(encoder=SerializerDict)

:param skip: List of field names as strings to skip when forming the dictionary :return: encoded object containing all information to reform an EasyScience object.

Source code in src/easyscience/base_classes/based_base.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
def as_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]:
    """Convert an object into a full dictionary using
    `SerializerDict`. This is a shortcut for
    ```obj.encode(encoder=SerializerDict)```

    :param skip: List of field names as strings to skip when forming
        the dictionary
    :return: encoded object containing all information to reform an
        EasyScience object.
    """
    # extend skip to include unique_name by default
    if skip is None:
        skip = []
    if 'unique_name' not in skip:
        skip.append('unique_name')
    return super().as_dict(skip=skip)

generate_bindings()

Generate or re-generate bindings to an interface (if exists)

:raises: AttributeError

Source code in src/easyscience/base_classes/based_base.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def generate_bindings(self):
    """Generate or re-generate bindings to an interface (if exists)

    :raises: AttributeError
    """
    if self.interface is None:
        raise AttributeError(
            'Interface error for generating bindings. `interface` has to be set.'
        )
    interfaceable_children = [
        key
        for key in self._global_object.map.get_edges(self)
        if issubclass(type(self._global_object.map.get_item_by_key(key)), BasedBase)
    ]
    for child_key in interfaceable_children:
        child = self._global_object.map.get_item_by_key(child_key)
        child.interface = self.interface
    self.interface.generate_bindings(self)

get_fit_parameters()

Get all objects which can be fitted (and are not fixed) as a list.

:return: List of Parameter objects which can be used in fitting.

Source code in src/easyscience/base_classes/based_base.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
def get_fit_parameters(self) -> List[Parameter]:
    """Get all objects which can be fitted (and are not fixed) as a
    list.

    :return: List of `Parameter` objects which can be used in fitting.
    """
    fit_list = []
    for key, item in self._kwargs.items():
        if hasattr(item, 'get_fit_parameters'):
            fit_list = [*fit_list, *item.get_fit_parameters()]
        elif isinstance(item, Parameter):
            if item.independent and not item.fixed:
                fit_list.append(item)
    return fit_list

get_parameters()

Get all parameter objects as a list.

:return: List of Parameter objects.

Source code in src/easyscience/base_classes/based_base.py
148
149
150
151
152
153
154
155
156
157
158
159
def get_parameters(self) -> List[Parameter]:
    """Get all parameter objects as a list.

    :return: List of `Parameter` objects.
    """
    par_list = []
    for key, item in self._kwargs.items():
        if hasattr(item, 'get_parameters'):
            par_list = [*par_list, *item.get_parameters()]
        elif isinstance(item, Parameter):
            par_list.append(item)
    return par_list

interface property writable

Get the current interface of the object.

name property writable

Get the common name of the object.

:return: Common name of the object

switch_interface(new_interface_name)

Switch or create a new interface.

Source code in src/easyscience/base_classes/based_base.py
139
140
141
142
143
144
145
146
def switch_interface(self, new_interface_name: str):
    """Switch or create a new interface."""
    if self.interface is None:
        raise AttributeError(
            'Interface error for generating bindings. `interface` has to be set.'
        )
    self.interface.switch(new_interface_name)
    self.generate_bindings()

unique_name property writable

Get the unique name of the object.

collection_base

CollectionBase

Bases: BasedBase, MutableSequence

This is the base class for which all higher level classes are built off of.

NOTE: This object is serializable only if parameters are supplied as: ObjBase(a=value, b=value). For Parameter or Descriptor objects we can cheat with ObjBase(*[Descriptor(...), Parameter(...), ...]).

Source code in src/easyscience/base_classes/collection_base.py
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
class CollectionBase(BasedBase, MutableSequence):
    """This is the base class for which all higher level classes are
    built off of.

    NOTE: This object is serializable only if parameters are supplied as:
    `ObjBase(a=value, b=value)`. For `Parameter` or `Descriptor` objects we can
    cheat with `ObjBase(*[Descriptor(...), Parameter(...), ...])`.
    """

    def __init__(
        self,
        name: str,
        *args: Union[BasedBase, DescriptorBase, NewBase],
        interface: Optional[InterfaceFactoryTemplate] = None,
        unique_name: Optional[str] = None,
        **kwargs,
    ):
        """Set up the base collection class.

        :param name: Name of this object
        :type name: str
        :param args: selection of
        :param _kwargs: Fields which this class should contain
        :type _kwargs: dict
        """
        BasedBase.__init__(self, name, unique_name=unique_name)
        kwargs = {key: kwargs[key] for key in kwargs.keys() if kwargs[key] is not None}
        _args = []
        for item in args:
            if not isinstance(item, list):
                _args.append(item)
            else:
                _args += item
        _kwargs = {}
        for key, item in kwargs.items():
            if isinstance(item, list) and len(item) > 0:
                _args += item
            else:
                _kwargs[key] = item
        kwargs = _kwargs
        for item in list(kwargs.values()) + _args:
            if not issubclass(type(item), (DescriptorBase, BasedBase, NewBase)):
                raise AttributeError('A collection can only be formed from easyscience objects.')
        args = _args
        _kwargs = {}
        for key, item in kwargs.items():
            _kwargs[key] = item
        for arg in args:
            kwargs[arg.unique_name] = arg
            _kwargs[arg.unique_name] = arg

        # Set kwargs, also useful for serialization
        self._kwargs = NotarizedDict(**_kwargs)

        for key in kwargs.keys():
            if key in self.__dict__.keys() or key in self.__slots__:
                raise AttributeError(
                    f'Given kwarg: `{key}`, is an internal attribute. Please rename.'
                )
            if kwargs[key]:  # Might be None (empty tuple or list)
                self._global_object.map.add_edge(self, kwargs[key])
                self._global_object.map.reset_type(kwargs[key], 'created_internal')
                if interface is not None:
                    kwargs[key].interface = interface
            # TODO wrap getter and setter in Logger
        if interface is not None:
            self.interface = interface
        self._kwargs._stack_enabled = True

    def insert(self, index: int, value: Union[DescriptorBase, BasedBase, NewBase]) -> None:
        """Insert an object into the collection at an index.

        :param index: Index for EasyScience object to be inserted.
        :type index: int
        :param value: Object to be inserted.
        :type value: Union[BasedBase, DescriptorBase, NewBase]
        :return: None
        :rtype: None
        """
        t_ = type(value)
        if issubclass(t_, (BasedBase, DescriptorBase, NewBase)):
            update_key = list(self._kwargs.keys())
            values = list(self._kwargs.values())
            # Update the internal dict
            new_key = value.unique_name
            update_key.insert(index, new_key)
            values.insert(index, value)
            self._kwargs.reorder(**{k: v for k, v in zip(update_key, values)})
            # ADD EDGE
            self._global_object.map.add_edge(self, value)
            self._global_object.map.reset_type(value, 'created_internal')
            value.interface = self.interface
        else:
            raise AttributeError('Only EasyScience objects can be put into an EasyScience group')

    def __getitem__(self, idx: Union[int, slice]) -> Union[DescriptorBase, BasedBase, NewBase]:
        """Get an item in the collection based on its index.

        :param idx: index or slice of the collection.
        :type idx: Union[int, slice]
        :return: Object at index `idx`
        :rtype: Union[Parameter, Descriptor, ObjBase, 'CollectionBase']
        """
        if isinstance(idx, slice):
            start, stop, step = idx.indices(len(self))
            return self.__class__(
                getattr(self, 'name'), *[self[i] for i in range(start, stop, step)]
            )
        if str(idx) in self._kwargs.keys():
            return self._kwargs[str(idx)]
        if isinstance(idx, str):
            idx = [index for index, item in enumerate(self) if item.name == idx]
            noi = len(idx)
            if noi == 0:
                raise IndexError('Given index does not exist')
            elif noi == 1:
                idx = idx[0]
            else:
                return self.__class__(getattr(self, 'name'), *[self[i] for i in idx])
        elif not isinstance(idx, int) or isinstance(idx, bool):
            if isinstance(idx, bool):
                raise TypeError('Boolean indexing is not supported at the moment')
            try:
                if idx > len(self):
                    raise IndexError(f'Given index {idx} is out of bounds')
            except TypeError:
                raise IndexError('Index must be of type `int`/`slice` or an item name (`str`)')
        keys = list(self._kwargs.keys())
        return self._kwargs[keys[idx]]

    def __setitem__(self, key: int, value: Union[BasedBase, DescriptorBase, NewBase]) -> None:
        """Set an item via it's index.

        :param key: Index in self.
        :type key: int
        :param value: Value which index key should be set to.
        :type value: Any
        """
        if isinstance(value, Number):  # noqa: S3827
            item = self.__getitem__(key)
            item.value = value
        elif issubclass(type(value), (BasedBase, DescriptorBase, NewBase)):
            update_key = list(self._kwargs.keys())
            values = list(self._kwargs.values())
            old_item = values[key]
            # Update the internal dict
            update_dict = {update_key[key]: value}
            self._kwargs.update(update_dict)
            # ADD EDGE
            self._global_object.map.add_edge(self, value)
            self._global_object.map.reset_type(value, 'created_internal')
            value.interface = self.interface
            # REMOVE EDGE
            self._global_object.map.prune_vertex_from_edge(self, old_item)
        else:
            raise NotImplementedError(
                'At the moment only numerical values or EasyScience objects can be set.'
            )

    def __delitem__(self, key: int) -> None:
        """Try to delete  an idem by key.

        :param key:
        :type key:
        :return:
        :rtype:
        """
        keys = list(self._kwargs.keys())
        item = self._kwargs[keys[key]]
        self._global_object.map.prune_vertex_from_edge(self, item)
        del self._kwargs[keys[key]]

    def __len__(self) -> int:
        """Get the number of items in this collection.

        :return: Number of items in this collection.
        :rtype: int
        """
        return len(self._kwargs.keys())

    def _convert_to_dict(self, in_dict, encoder, skip: List[str] = [], **kwargs) -> dict:
        """Convert ones self into a serialized form.

        :return: dictionary of ones self
        :rtype: dict
        """
        d = {}
        if hasattr(self, '_modify_dict'):
            # any extra keys defined on the inheriting class
            d = self._modify_dict(skip=skip, **kwargs)
        in_dict['data'] = [encoder._convert_to_dict(item, skip=skip, **kwargs) for item in self]
        out_dict = {**in_dict, **d}
        return out_dict

    @property
    def data(self) -> Tuple:
        """The data function returns a tuple of the keyword arguments
        passed to the constructor. This is useful for when you need to
        pass in a dictionary of data to other functions, such as with
        matplotlib's plot function.

        :param self: Access attributes of the class within the method
        :return: The values of the attributes in a tuple :doc-author:
            Trelent
        """
        return tuple(self._kwargs.values())

    def __repr__(self) -> str:
        return f'{self.__class__.__name__} `{getattr(self, "name")}` of length {len(self)}'

    def sort(
        self,
        mapping: Callable[[Union[BasedBase, DescriptorBase, NewBase]], Any],
        reverse: bool = False,
    ) -> None:
        """Sort the collection according to the given mapping.

        :param mapping: mapping function to sort the collection. i.e.
            lambda parameter: parameter.value
        :type mapping: Callable
        :param reverse: Reverse the sorting.
        :type reverse: bool
        """
        i = list(self._kwargs.items())
        i.sort(key=lambda x: mapping(x[1]), reverse=reverse)
        self._kwargs.reorder(**{k[0]: k[1] for k in i})

__delitem__(key)

Try to delete an idem by key.

:param key: :type key: :return: :rtype:

Source code in src/easyscience/base_classes/collection_base.py
185
186
187
188
189
190
191
192
193
194
195
196
def __delitem__(self, key: int) -> None:
    """Try to delete  an idem by key.

    :param key:
    :type key:
    :return:
    :rtype:
    """
    keys = list(self._kwargs.keys())
    item = self._kwargs[keys[key]]
    self._global_object.map.prune_vertex_from_edge(self, item)
    del self._kwargs[keys[key]]

__getitem__(idx)

Get an item in the collection based on its index.

:param idx: index or slice of the collection. :type idx: Union[int, slice] :return: Object at index idx :rtype: Union[Parameter, Descriptor, ObjBase, 'CollectionBase']

Source code in src/easyscience/base_classes/collection_base.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def __getitem__(self, idx: Union[int, slice]) -> Union[DescriptorBase, BasedBase, NewBase]:
    """Get an item in the collection based on its index.

    :param idx: index or slice of the collection.
    :type idx: Union[int, slice]
    :return: Object at index `idx`
    :rtype: Union[Parameter, Descriptor, ObjBase, 'CollectionBase']
    """
    if isinstance(idx, slice):
        start, stop, step = idx.indices(len(self))
        return self.__class__(
            getattr(self, 'name'), *[self[i] for i in range(start, stop, step)]
        )
    if str(idx) in self._kwargs.keys():
        return self._kwargs[str(idx)]
    if isinstance(idx, str):
        idx = [index for index, item in enumerate(self) if item.name == idx]
        noi = len(idx)
        if noi == 0:
            raise IndexError('Given index does not exist')
        elif noi == 1:
            idx = idx[0]
        else:
            return self.__class__(getattr(self, 'name'), *[self[i] for i in idx])
    elif not isinstance(idx, int) or isinstance(idx, bool):
        if isinstance(idx, bool):
            raise TypeError('Boolean indexing is not supported at the moment')
        try:
            if idx > len(self):
                raise IndexError(f'Given index {idx} is out of bounds')
        except TypeError:
            raise IndexError('Index must be of type `int`/`slice` or an item name (`str`)')
    keys = list(self._kwargs.keys())
    return self._kwargs[keys[idx]]

__init__(name, *args, interface=None, unique_name=None, **kwargs)

Set up the base collection class.

:param name: Name of this object :type name: str :param args: selection of :param _kwargs: Fields which this class should contain :type _kwargs: dict

Source code in src/easyscience/base_classes/collection_base.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def __init__(
    self,
    name: str,
    *args: Union[BasedBase, DescriptorBase, NewBase],
    interface: Optional[InterfaceFactoryTemplate] = None,
    unique_name: Optional[str] = None,
    **kwargs,
):
    """Set up the base collection class.

    :param name: Name of this object
    :type name: str
    :param args: selection of
    :param _kwargs: Fields which this class should contain
    :type _kwargs: dict
    """
    BasedBase.__init__(self, name, unique_name=unique_name)
    kwargs = {key: kwargs[key] for key in kwargs.keys() if kwargs[key] is not None}
    _args = []
    for item in args:
        if not isinstance(item, list):
            _args.append(item)
        else:
            _args += item
    _kwargs = {}
    for key, item in kwargs.items():
        if isinstance(item, list) and len(item) > 0:
            _args += item
        else:
            _kwargs[key] = item
    kwargs = _kwargs
    for item in list(kwargs.values()) + _args:
        if not issubclass(type(item), (DescriptorBase, BasedBase, NewBase)):
            raise AttributeError('A collection can only be formed from easyscience objects.')
    args = _args
    _kwargs = {}
    for key, item in kwargs.items():
        _kwargs[key] = item
    for arg in args:
        kwargs[arg.unique_name] = arg
        _kwargs[arg.unique_name] = arg

    # Set kwargs, also useful for serialization
    self._kwargs = NotarizedDict(**_kwargs)

    for key in kwargs.keys():
        if key in self.__dict__.keys() or key in self.__slots__:
            raise AttributeError(
                f'Given kwarg: `{key}`, is an internal attribute. Please rename.'
            )
        if kwargs[key]:  # Might be None (empty tuple or list)
            self._global_object.map.add_edge(self, kwargs[key])
            self._global_object.map.reset_type(kwargs[key], 'created_internal')
            if interface is not None:
                kwargs[key].interface = interface
        # TODO wrap getter and setter in Logger
    if interface is not None:
        self.interface = interface
    self._kwargs._stack_enabled = True

__len__()

Get the number of items in this collection.

:return: Number of items in this collection. :rtype: int

Source code in src/easyscience/base_classes/collection_base.py
198
199
200
201
202
203
204
def __len__(self) -> int:
    """Get the number of items in this collection.

    :return: Number of items in this collection.
    :rtype: int
    """
    return len(self._kwargs.keys())

__setitem__(key, value)

Set an item via it's index.

:param key: Index in self. :type key: int :param value: Value which index key should be set to. :type value: Any

Source code in src/easyscience/base_classes/collection_base.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def __setitem__(self, key: int, value: Union[BasedBase, DescriptorBase, NewBase]) -> None:
    """Set an item via it's index.

    :param key: Index in self.
    :type key: int
    :param value: Value which index key should be set to.
    :type value: Any
    """
    if isinstance(value, Number):  # noqa: S3827
        item = self.__getitem__(key)
        item.value = value
    elif issubclass(type(value), (BasedBase, DescriptorBase, NewBase)):
        update_key = list(self._kwargs.keys())
        values = list(self._kwargs.values())
        old_item = values[key]
        # Update the internal dict
        update_dict = {update_key[key]: value}
        self._kwargs.update(update_dict)
        # ADD EDGE
        self._global_object.map.add_edge(self, value)
        self._global_object.map.reset_type(value, 'created_internal')
        value.interface = self.interface
        # REMOVE EDGE
        self._global_object.map.prune_vertex_from_edge(self, old_item)
    else:
        raise NotImplementedError(
            'At the moment only numerical values or EasyScience objects can be set.'
        )

data property

The data function returns a tuple of the keyword arguments passed to the constructor. This is useful for when you need to pass in a dictionary of data to other functions, such as with matplotlib's plot function.

:param self: Access attributes of the class within the method :return: The values of the attributes in a tuple :doc-author: Trelent

insert(index, value)

Insert an object into the collection at an index.

:param index: Index for EasyScience object to be inserted. :type index: int :param value: Object to be inserted. :type value: Union[BasedBase, DescriptorBase, NewBase] :return: None :rtype: None

Source code in src/easyscience/base_classes/collection_base.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def insert(self, index: int, value: Union[DescriptorBase, BasedBase, NewBase]) -> None:
    """Insert an object into the collection at an index.

    :param index: Index for EasyScience object to be inserted.
    :type index: int
    :param value: Object to be inserted.
    :type value: Union[BasedBase, DescriptorBase, NewBase]
    :return: None
    :rtype: None
    """
    t_ = type(value)
    if issubclass(t_, (BasedBase, DescriptorBase, NewBase)):
        update_key = list(self._kwargs.keys())
        values = list(self._kwargs.values())
        # Update the internal dict
        new_key = value.unique_name
        update_key.insert(index, new_key)
        values.insert(index, value)
        self._kwargs.reorder(**{k: v for k, v in zip(update_key, values)})
        # ADD EDGE
        self._global_object.map.add_edge(self, value)
        self._global_object.map.reset_type(value, 'created_internal')
        value.interface = self.interface
    else:
        raise AttributeError('Only EasyScience objects can be put into an EasyScience group')

sort(mapping, reverse=False)

Sort the collection according to the given mapping.

:param mapping: mapping function to sort the collection. i.e. lambda parameter: parameter.value :type mapping: Callable :param reverse: Reverse the sorting. :type reverse: bool

Source code in src/easyscience/base_classes/collection_base.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
def sort(
    self,
    mapping: Callable[[Union[BasedBase, DescriptorBase, NewBase]], Any],
    reverse: bool = False,
) -> None:
    """Sort the collection according to the given mapping.

    :param mapping: mapping function to sort the collection. i.e.
        lambda parameter: parameter.value
    :type mapping: Callable
    :param reverse: Reverse the sorting.
    :type reverse: bool
    """
    i = list(self._kwargs.items())
    i.sort(key=lambda x: mapping(x[1]), reverse=reverse)
    self._kwargs.reorder(**{k[0]: k[1] for k in i})

easy_list

EasyList

Bases: NewBase, MutableSequence[ProtectedType_]

Source code in src/easyscience/base_classes/easy_list.py
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
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
287
288
289
290
291
292
293
294
295
296
297
298
299
class EasyList(NewBase, MutableSequence[ProtectedType_]):
    # If we were to inherit from List instead of MutableSequence,
    # we would have to overwrite "extend", "remove", "__iadd__", "count", "append", "__iter__" and "clear"
    def __init__(
        self,
        *args: ProtectedType_ | list[ProtectedType_],
        protected_types: list[Type[NewBase]] | Type[NewBase] | None = None,
        unique_name: Optional[str] = None,
        display_name: Optional[str] = None,
        **kwargs: Any,
    ):
        """Initialize the EasyList.

        :param args: Initial items to add to the list
        :param protected_types: Types that are allowed in the list. Can
            be a single NewBase subclass or a list of them. If None,
            defaults to [NewBase].
        :param unique_name: Optional unique name for the list
        :param display_name: Optional display name for the list
        """
        super().__init__(unique_name=unique_name, display_name=display_name)
        if protected_types is None:
            self._protected_types = [NewBase]
        elif isinstance(protected_types, type) and issubclass(protected_types, NewBase):
            self._protected_types = [protected_types]
        elif isinstance(protected_types, Iterable) and all(
            issubclass(t, NewBase) for t in protected_types
        ):
            self._protected_types = list(protected_types)
        else:
            raise TypeError(
                'protected_types must be a NewBase subclass or an iterable of NewBase subclasses'
            )
        self._data: List[ProtectedType_] = []

        # Add initial items
        for item in args:
            if isinstance(item, list):
                for sub_item in item:
                    self.append(sub_item)
            else:
                self.append(item)

        # For deserialization, the dict can't contain an *args, so we check for 'data' in kwargs
        if 'data' in kwargs:
            data = kwargs.pop('data')
            for item in data:
                self.append(item)

    # MutableSequence abstract methods

    # Use @overload to provide precise type hints for different __getitem__ argument types
    @overload
    def __getitem__(self, idx: int) -> ProtectedType_: ...
    @overload
    def __getitem__(self, idx: slice) -> 'EasyList[ProtectedType_]': ...
    @overload
    def __getitem__(self, idx: str) -> ProtectedType_: ...
    def __getitem__(self, idx: int | slice | str) -> ProtectedType_ | 'EasyList[ProtectedType_]':
        """Get an item by index, slice, or unique_name.

        :param idx: Index, slice, or unique_name of the item
        :return: The item or a new EasyList for slices
        """
        if isinstance(idx, int):
            return self._data[idx]
        elif isinstance(idx, slice):
            return self.__class__(self._data[idx], protected_types=self._protected_types)
        elif isinstance(idx, str):
            element = next((r for r in self._data if self._get_key(r) == idx), None)
            if element is not None:
                return element
            raise KeyError(f'No item with unique name "{idx}" found')
        else:
            raise TypeError('Index must be an int, slice, or str')

    @overload
    def __setitem__(self, idx: int, value: ProtectedType_) -> None: ...
    @overload
    def __setitem__(self, idx: slice, value: Iterable[ProtectedType_]) -> None: ...

    def __setitem__(
        self, idx: int | slice, value: ProtectedType_ | Iterable[ProtectedType_]
    ) -> None:
        """Set an item at an index.

        :param idx: Index to set
        :param value: New value
        """
        if isinstance(idx, int):
            if not isinstance(value, tuple(self._protected_types)):
                raise TypeError(f'Items must be one of {self._protected_types}, got {type(value)}')
            if value is not self._data[idx] and value in self:
                warnings.warn(
                    f'Item with unique name "{self._get_key(value)}" already in EasyList, it will be ignored'
                )
                return
            self._data[idx] = value
        elif isinstance(idx, slice):
            if not isinstance(value, Iterable):
                raise TypeError('Value must be an iterable for slice assignment')
            replaced = self._data[idx]
            new_values = list(value)
            if len(new_values) != len(replaced):
                raise ValueError(
                    'Length of new values must match the length of the slice being replaced'
                )
            for i, v in enumerate(new_values):
                if not isinstance(v, tuple(self._protected_types)):
                    raise TypeError(f'Items must be one of {self._protected_types}, got {type(v)}')
                if v in self and replaced[i] is not v:
                    warnings.warn(
                        f'Item with unique name "{v.unique_name}" already in EasyList, it will be ignored'
                    )
                    new_values[i] = replaced[
                        i
                    ]  # Keep the original value if the new one is a duplicate
            self._data[idx] = new_values
        else:
            raise TypeError('Index must be an int or slice')

    def __delitem__(self, idx: int | slice | str) -> None:
        """Delete an item by index, slice, or name.

        :param idx: Index, slice, or name of item to delete
        """
        if isinstance(idx, (int, slice)):
            del self._data[idx]
        elif isinstance(idx, str):
            for i, item in enumerate(self._data):
                if self._get_key(item) == idx:
                    del self._data[i]
                    return
            raise KeyError(f'No item with unique name "{idx}" found')
        else:
            raise TypeError('Index must be an int, slice, or str')

    def __len__(self) -> int:
        """Return the number of items in the collection."""
        return len(self._data)

    def insert(self, index: int, value: ProtectedType_) -> None:
        """Insert an item at an index.

        :param index: Index to insert at
        :param value: Item to insert
        """
        if not isinstance(index, int):
            raise TypeError('Index must be an integer')
        elif not isinstance(value, tuple(self._protected_types)):
            raise TypeError(f'Items must be one of {self._protected_types}, got {type(value)}')
        if value in self:
            warnings.warn(
                f'Item with unique name "{self._get_key(value)}" already in EasyList, it will be ignored'
            )
            return
        self._data.insert(index, value)

    def _get_key(self, obj) -> str:
        """Get the unique name of an object.

        Can be overridden to use a different attribute as the key.
        :param object: Object to get the key for
        :return: The key of the object
        :rtype: str
        """
        return obj.unique_name

    # Overwriting methods

    def __repr__(self) -> str:
        return (
            f'{self.__class__.__name__} of length {len(self)} of type(s) {self._protected_types}'
        )

    def __contains__(self, item: ProtectedType_ | str) -> bool:
        if isinstance(item, str):
            return any(self._get_key(r) == item for r in self._data)
        return item in self._data

    def __reversed__(self):
        return self._data.__reversed__()

    def sort(self, key: Callable[[ProtectedType_], Any] = None, reverse: bool = False) -> None:
        """Sort the collection according to the given key function.

        :param key: Mapping function to sort by
        :param reverse: Whether to reverse the sort
        """
        self._data.sort(reverse=reverse, key=key)

    def index(self, value: ProtectedType_ | str, start: int = 0, stop: int = None) -> int:
        if stop is None:
            stop = len(self._data)
        if isinstance(value, str):
            for i in range(start, min(stop, len(self._data))):
                if self._get_key(self._data[i]) == value:
                    return i
            raise ValueError(f'{value} is not in EasyList')
        return self._data.index(value, start, stop)

    def pop(self, index: int | str = -1) -> ProtectedType_:
        """Remove and return an item at the given index or unique_name.

        :param index: Index or unique_name of the item to remove
        :return: The removed item
        """
        if isinstance(index, int):
            return self._data.pop(index)
        elif isinstance(index, str):
            for i, item in enumerate(self._data):
                if self._get_key(item) == index:
                    return self._data.pop(i)
            raise KeyError(f'No item with unique name "{index}" found')
        else:
            raise TypeError('Index must be an int or str')

    # Serialization support

    def to_dict(self) -> dict:
        """Convert the EasyList to a dictionary for serialization.

        :return: Dictionary representation of the EasyList
        """
        dict_repr = super().to_dict()
        if self._protected_types != [NewBase]:
            dict_repr['protected_types'] = [
                {'@module': cls_.__module__, '@class': cls_.__name__}
                for cls_ in self._protected_types
            ]  # noqa: E501
        dict_repr['data'] = [item.to_dict() for item in self._data]
        return dict_repr

    @classmethod
    def from_dict(cls, obj_dict: Dict[str, Any]) -> NewBase:
        """Re-create an EasyScience object from a full encoded
        dictionary.

        :param obj_dict: dictionary containing the serialized contents (from `SerializerDict`) of an EasyScience object
        :return: Reformed EasyScience object
        """
        if not SerializerBase._is_serialized_easyscience_object(obj_dict):
            raise ValueError(
                'Input must be a dictionary representing an EasyScience EasyList object.'
            )
        temp_dict = copy.deepcopy(obj_dict)  # Make a copy to avoid mutating the input
        if temp_dict['@class'] == cls.__name__:
            if 'protected_types' in temp_dict:
                protected_types = temp_dict.pop('protected_types')
                for i, type_dict in enumerate(protected_types):
                    if '@module' in type_dict and '@class' in type_dict:
                        modname = type_dict['@module']
                        classname = type_dict['@class']
                        mod = __import__(modname, globals(), locals(), [classname], 0)
                        if hasattr(mod, classname):
                            cls_ = getattr(mod, classname)
                            protected_types[i] = cls_
                        else:
                            raise ImportError(
                                f'Could not import class {classname} from module {modname}'
                            )
                    else:
                        raise ValueError(
                            'Each protected type must be a serialized EasyScience class with @module and @class keys'
                        )  # noqa: E501
            else:
                protected_types = None
            kwargs = SerializerBase.deserialize_dict(temp_dict)
            data = kwargs.pop('data', [])
            return cls(data, protected_types=protected_types, **kwargs)
        else:
            raise ValueError(
                f'Class name in dictionary does not match the expected class: {cls.__name__}.'
            )

__delitem__(idx)

Delete an item by index, slice, or name.

:param idx: Index, slice, or name of item to delete

Source code in src/easyscience/base_classes/easy_list.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
def __delitem__(self, idx: int | slice | str) -> None:
    """Delete an item by index, slice, or name.

    :param idx: Index, slice, or name of item to delete
    """
    if isinstance(idx, (int, slice)):
        del self._data[idx]
    elif isinstance(idx, str):
        for i, item in enumerate(self._data):
            if self._get_key(item) == idx:
                del self._data[i]
                return
        raise KeyError(f'No item with unique name "{idx}" found')
    else:
        raise TypeError('Index must be an int, slice, or str')

__getitem__(idx)

__getitem__(idx: int) -> ProtectedType_
__getitem__(idx: slice) -> 'EasyList[ProtectedType_]'
__getitem__(idx: str) -> ProtectedType_

Get an item by index, slice, or unique_name.

:param idx: Index, slice, or unique_name of the item :return: The item or a new EasyList for slices

Source code in src/easyscience/base_classes/easy_list.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def __getitem__(self, idx: int | slice | str) -> ProtectedType_ | 'EasyList[ProtectedType_]':
    """Get an item by index, slice, or unique_name.

    :param idx: Index, slice, or unique_name of the item
    :return: The item or a new EasyList for slices
    """
    if isinstance(idx, int):
        return self._data[idx]
    elif isinstance(idx, slice):
        return self.__class__(self._data[idx], protected_types=self._protected_types)
    elif isinstance(idx, str):
        element = next((r for r in self._data if self._get_key(r) == idx), None)
        if element is not None:
            return element
        raise KeyError(f'No item with unique name "{idx}" found')
    else:
        raise TypeError('Index must be an int, slice, or str')

__init__(*args, protected_types=None, unique_name=None, display_name=None, **kwargs)

Initialize the EasyList.

:param args: Initial items to add to the list :param protected_types: Types that are allowed in the list. Can be a single NewBase subclass or a list of them. If None, defaults to [NewBase]. :param unique_name: Optional unique name for the list :param display_name: Optional display name for the list

Source code in src/easyscience/base_classes/easy_list.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def __init__(
    self,
    *args: ProtectedType_ | list[ProtectedType_],
    protected_types: list[Type[NewBase]] | Type[NewBase] | None = None,
    unique_name: Optional[str] = None,
    display_name: Optional[str] = None,
    **kwargs: Any,
):
    """Initialize the EasyList.

    :param args: Initial items to add to the list
    :param protected_types: Types that are allowed in the list. Can
        be a single NewBase subclass or a list of them. If None,
        defaults to [NewBase].
    :param unique_name: Optional unique name for the list
    :param display_name: Optional display name for the list
    """
    super().__init__(unique_name=unique_name, display_name=display_name)
    if protected_types is None:
        self._protected_types = [NewBase]
    elif isinstance(protected_types, type) and issubclass(protected_types, NewBase):
        self._protected_types = [protected_types]
    elif isinstance(protected_types, Iterable) and all(
        issubclass(t, NewBase) for t in protected_types
    ):
        self._protected_types = list(protected_types)
    else:
        raise TypeError(
            'protected_types must be a NewBase subclass or an iterable of NewBase subclasses'
        )
    self._data: List[ProtectedType_] = []

    # Add initial items
    for item in args:
        if isinstance(item, list):
            for sub_item in item:
                self.append(sub_item)
        else:
            self.append(item)

    # For deserialization, the dict can't contain an *args, so we check for 'data' in kwargs
    if 'data' in kwargs:
        data = kwargs.pop('data')
        for item in data:
            self.append(item)

__len__()

Return the number of items in the collection.

Source code in src/easyscience/base_classes/easy_list.py
163
164
165
def __len__(self) -> int:
    """Return the number of items in the collection."""
    return len(self._data)

__setitem__(idx, value)

__setitem__(idx: int, value: ProtectedType_) -> None
__setitem__(
    idx: slice, value: Iterable[ProtectedType_]
) -> None

Set an item at an index.

:param idx: Index to set :param value: New value

Source code in src/easyscience/base_classes/easy_list.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
def __setitem__(
    self, idx: int | slice, value: ProtectedType_ | Iterable[ProtectedType_]
) -> None:
    """Set an item at an index.

    :param idx: Index to set
    :param value: New value
    """
    if isinstance(idx, int):
        if not isinstance(value, tuple(self._protected_types)):
            raise TypeError(f'Items must be one of {self._protected_types}, got {type(value)}')
        if value is not self._data[idx] and value in self:
            warnings.warn(
                f'Item with unique name "{self._get_key(value)}" already in EasyList, it will be ignored'
            )
            return
        self._data[idx] = value
    elif isinstance(idx, slice):
        if not isinstance(value, Iterable):
            raise TypeError('Value must be an iterable for slice assignment')
        replaced = self._data[idx]
        new_values = list(value)
        if len(new_values) != len(replaced):
            raise ValueError(
                'Length of new values must match the length of the slice being replaced'
            )
        for i, v in enumerate(new_values):
            if not isinstance(v, tuple(self._protected_types)):
                raise TypeError(f'Items must be one of {self._protected_types}, got {type(v)}')
            if v in self and replaced[i] is not v:
                warnings.warn(
                    f'Item with unique name "{v.unique_name}" already in EasyList, it will be ignored'
                )
                new_values[i] = replaced[
                    i
                ]  # Keep the original value if the new one is a duplicate
        self._data[idx] = new_values
    else:
        raise TypeError('Index must be an int or slice')

from_dict(obj_dict) classmethod

Re-create an EasyScience object from a full encoded dictionary.

:param obj_dict: dictionary containing the serialized contents (from SerializerDict) of an EasyScience object :return: Reformed EasyScience object

Source code in src/easyscience/base_classes/easy_list.py
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
287
288
289
290
291
292
293
294
295
296
297
298
299
@classmethod
def from_dict(cls, obj_dict: Dict[str, Any]) -> NewBase:
    """Re-create an EasyScience object from a full encoded
    dictionary.

    :param obj_dict: dictionary containing the serialized contents (from `SerializerDict`) of an EasyScience object
    :return: Reformed EasyScience object
    """
    if not SerializerBase._is_serialized_easyscience_object(obj_dict):
        raise ValueError(
            'Input must be a dictionary representing an EasyScience EasyList object.'
        )
    temp_dict = copy.deepcopy(obj_dict)  # Make a copy to avoid mutating the input
    if temp_dict['@class'] == cls.__name__:
        if 'protected_types' in temp_dict:
            protected_types = temp_dict.pop('protected_types')
            for i, type_dict in enumerate(protected_types):
                if '@module' in type_dict and '@class' in type_dict:
                    modname = type_dict['@module']
                    classname = type_dict['@class']
                    mod = __import__(modname, globals(), locals(), [classname], 0)
                    if hasattr(mod, classname):
                        cls_ = getattr(mod, classname)
                        protected_types[i] = cls_
                    else:
                        raise ImportError(
                            f'Could not import class {classname} from module {modname}'
                        )
                else:
                    raise ValueError(
                        'Each protected type must be a serialized EasyScience class with @module and @class keys'
                    )  # noqa: E501
        else:
            protected_types = None
        kwargs = SerializerBase.deserialize_dict(temp_dict)
        data = kwargs.pop('data', [])
        return cls(data, protected_types=protected_types, **kwargs)
    else:
        raise ValueError(
            f'Class name in dictionary does not match the expected class: {cls.__name__}.'
        )

insert(index, value)

Insert an item at an index.

:param index: Index to insert at :param value: Item to insert

Source code in src/easyscience/base_classes/easy_list.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
def insert(self, index: int, value: ProtectedType_) -> None:
    """Insert an item at an index.

    :param index: Index to insert at
    :param value: Item to insert
    """
    if not isinstance(index, int):
        raise TypeError('Index must be an integer')
    elif not isinstance(value, tuple(self._protected_types)):
        raise TypeError(f'Items must be one of {self._protected_types}, got {type(value)}')
    if value in self:
        warnings.warn(
            f'Item with unique name "{self._get_key(value)}" already in EasyList, it will be ignored'
        )
        return
    self._data.insert(index, value)

pop(index=-1)

Remove and return an item at the given index or unique_name.

:param index: Index or unique_name of the item to remove :return: The removed item

Source code in src/easyscience/base_classes/easy_list.py
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
def pop(self, index: int | str = -1) -> ProtectedType_:
    """Remove and return an item at the given index or unique_name.

    :param index: Index or unique_name of the item to remove
    :return: The removed item
    """
    if isinstance(index, int):
        return self._data.pop(index)
    elif isinstance(index, str):
        for i, item in enumerate(self._data):
            if self._get_key(item) == index:
                return self._data.pop(i)
        raise KeyError(f'No item with unique name "{index}" found')
    else:
        raise TypeError('Index must be an int or str')

sort(key=None, reverse=False)

Sort the collection according to the given key function.

:param key: Mapping function to sort by :param reverse: Whether to reverse the sort

Source code in src/easyscience/base_classes/easy_list.py
209
210
211
212
213
214
215
def sort(self, key: Callable[[ProtectedType_], Any] = None, reverse: bool = False) -> None:
    """Sort the collection according to the given key function.

    :param key: Mapping function to sort by
    :param reverse: Whether to reverse the sort
    """
    self._data.sort(reverse=reverse, key=key)

to_dict()

Convert the EasyList to a dictionary for serialization.

:return: Dictionary representation of the EasyList

Source code in src/easyscience/base_classes/easy_list.py
245
246
247
248
249
250
251
252
253
254
255
256
257
def to_dict(self) -> dict:
    """Convert the EasyList to a dictionary for serialization.

    :return: Dictionary representation of the EasyList
    """
    dict_repr = super().to_dict()
    if self._protected_types != [NewBase]:
        dict_repr['protected_types'] = [
            {'@module': cls_.__module__, '@class': cls_.__name__}
            for cls_ in self._protected_types
        ]  # noqa: E501
    dict_repr['data'] = [item.to_dict() for item in self._data]
    return dict_repr

model_base

ModelBase

Bases: NewBase

This is the base class for all model classes in EasyScience. It provides methods to get parameters for fitting and analysis as well as proper serialization/deserialization for DescriptorNumber/Parameter attributes.

It assumes that Parameters/DescriptorNumbers are assigned as properties with the getters returning the parameter but the setter only setting the value of the parameter. e.g.

@property
def my_param(self) -> Parameter:
    return self._my_param


@my_param.setter
def my_param(self, new_value: float) -> None:
    self._my_param.value = new_value

Source code in src/easyscience/base_classes/model_base.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
class ModelBase(NewBase):
    """This is the base class for all model classes in EasyScience. It
    provides methods to get parameters for fitting and analysis as well
    as proper serialization/deserialization for
    DescriptorNumber/Parameter attributes.

    It assumes that Parameters/DescriptorNumbers are assigned as properties with the getters returning the parameter
    but the setter only setting the value of the parameter.
    e.g.
    ```python
    @property
    def my_param(self) -> Parameter:
        return self._my_param


    @my_param.setter
    def my_param(self, new_value: float) -> None:
        self._my_param.value = new_value
    ```
    """

    def __init__(self, unique_name: Optional[str] = None, display_name: Optional[str] = None):
        super().__init__(unique_name=unique_name, display_name=display_name)

    def get_all_variables(self) -> List[DescriptorBase]:
        """Get all `Descriptor` and `Parameter` objects as a list.

        :return: List of `Descriptor` and `Parameter` objects.
        """
        vars = []
        for attr_name in dir(self):
            attr = getattr(self, attr_name)
            if isinstance(attr, DescriptorBase):
                vars.append(attr)
            elif hasattr(attr, 'get_all_variables'):
                vars += attr.get_all_variables()
        return vars

    def get_all_parameters(self) -> List[Parameter]:
        """Get all `Parameter` objects as a list.

        :return: List of `Parameter` objects.
        """
        return [param for param in self.get_all_variables() if isinstance(param, Parameter)]

    def get_fittable_parameters(self) -> List[Parameter]:
        """Get all parameters which can be fitted as a list.

        :return: List of `Parameter` objects.
        """
        return [param for param in self.get_all_parameters() if param.independent]

    def get_free_parameters(self) -> List[Parameter]:
        """Get all parameters which are currently free to be fitted as a
        list.

        :return: List of `Parameter` objects.
        """
        return [param for param in self.get_fittable_parameters() if not param.fixed]

    def get_fit_parameters(self) -> List[Parameter]:
        """This is an alias for `get_free_parameters`.

        To be removed when fully moved to new base classes and minimizer
        can be changed.
        """
        return self.get_free_parameters()

    @classmethod
    def from_dict(cls, obj_dict: Dict[str, Any]) -> ModelBase:
        """Re-create an EasyScience object with DescriptorNumber
        attributes from a full encoded dictionary.

        :param obj_dict: dictionary containing the serialized contents (from `SerializerDict`) of an EasyScience object
        :return: Reformed EasyScience object
        """
        if not SerializerBase._is_serialized_easyscience_object(obj_dict):
            raise ValueError('Input must be a dictionary representing an EasyScience object.')
        if obj_dict['@class'] == cls.__name__:
            kwargs = SerializerBase.deserialize_dict(obj_dict)
            parameter_placeholder = {}
            for key, value in kwargs.items():
                if isinstance(value, DescriptorNumber):
                    parameter_placeholder[key] = value
                    kwargs[key] = value.value
            cls_instance = cls(**kwargs)
            for key, value in parameter_placeholder.items():
                try:
                    temp_param = getattr(cls_instance, key)
                    setattr(cls_instance, '_' + key, value)
                    cls_instance._global_object.map.prune(temp_param.unique_name)
                except Exception as e:
                    raise SyntaxError(f"""Could not set parameter {key} during `from_dict` with full deserialized variable. \n'
                            This should be fixed in the class definition. Error: {e}""") from e
            return cls_instance
        else:
            raise ValueError(
                f'Class name in dictionary does not match the expected class: {cls.__name__}.'
            )

from_dict(obj_dict) classmethod

Re-create an EasyScience object with DescriptorNumber attributes from a full encoded dictionary.

:param obj_dict: dictionary containing the serialized contents (from SerializerDict) of an EasyScience object :return: Reformed EasyScience object

Source code in src/easyscience/base_classes/model_base.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
@classmethod
def from_dict(cls, obj_dict: Dict[str, Any]) -> ModelBase:
    """Re-create an EasyScience object with DescriptorNumber
    attributes from a full encoded dictionary.

    :param obj_dict: dictionary containing the serialized contents (from `SerializerDict`) of an EasyScience object
    :return: Reformed EasyScience object
    """
    if not SerializerBase._is_serialized_easyscience_object(obj_dict):
        raise ValueError('Input must be a dictionary representing an EasyScience object.')
    if obj_dict['@class'] == cls.__name__:
        kwargs = SerializerBase.deserialize_dict(obj_dict)
        parameter_placeholder = {}
        for key, value in kwargs.items():
            if isinstance(value, DescriptorNumber):
                parameter_placeholder[key] = value
                kwargs[key] = value.value
        cls_instance = cls(**kwargs)
        for key, value in parameter_placeholder.items():
            try:
                temp_param = getattr(cls_instance, key)
                setattr(cls_instance, '_' + key, value)
                cls_instance._global_object.map.prune(temp_param.unique_name)
            except Exception as e:
                raise SyntaxError(f"""Could not set parameter {key} during `from_dict` with full deserialized variable. \n'
                        This should be fixed in the class definition. Error: {e}""") from e
        return cls_instance
    else:
        raise ValueError(
            f'Class name in dictionary does not match the expected class: {cls.__name__}.'
        )

get_all_parameters()

Get all Parameter objects as a list.

:return: List of Parameter objects.

Source code in src/easyscience/base_classes/model_base.py
60
61
62
63
64
65
def get_all_parameters(self) -> List[Parameter]:
    """Get all `Parameter` objects as a list.

    :return: List of `Parameter` objects.
    """
    return [param for param in self.get_all_variables() if isinstance(param, Parameter)]

get_all_variables()

Get all Descriptor and Parameter objects as a list.

:return: List of Descriptor and Parameter objects.

Source code in src/easyscience/base_classes/model_base.py
46
47
48
49
50
51
52
53
54
55
56
57
58
def get_all_variables(self) -> List[DescriptorBase]:
    """Get all `Descriptor` and `Parameter` objects as a list.

    :return: List of `Descriptor` and `Parameter` objects.
    """
    vars = []
    for attr_name in dir(self):
        attr = getattr(self, attr_name)
        if isinstance(attr, DescriptorBase):
            vars.append(attr)
        elif hasattr(attr, 'get_all_variables'):
            vars += attr.get_all_variables()
    return vars

get_fit_parameters()

This is an alias for get_free_parameters.

To be removed when fully moved to new base classes and minimizer can be changed.

Source code in src/easyscience/base_classes/model_base.py
82
83
84
85
86
87
88
def get_fit_parameters(self) -> List[Parameter]:
    """This is an alias for `get_free_parameters`.

    To be removed when fully moved to new base classes and minimizer
    can be changed.
    """
    return self.get_free_parameters()

get_fittable_parameters()

Get all parameters which can be fitted as a list.

:return: List of Parameter objects.

Source code in src/easyscience/base_classes/model_base.py
67
68
69
70
71
72
def get_fittable_parameters(self) -> List[Parameter]:
    """Get all parameters which can be fitted as a list.

    :return: List of `Parameter` objects.
    """
    return [param for param in self.get_all_parameters() if param.independent]

get_free_parameters()

Get all parameters which are currently free to be fitted as a list.

:return: List of Parameter objects.

Source code in src/easyscience/base_classes/model_base.py
74
75
76
77
78
79
80
def get_free_parameters(self) -> List[Parameter]:
    """Get all parameters which are currently free to be fitted as a
    list.

    :return: List of `Parameter` objects.
    """
    return [param for param in self.get_fittable_parameters() if not param.fixed]

new_base

NewBase

This is the new base class for easyscience objects.

It provides serialization capabilities as well as unique naming and display naming.

Source code in src/easyscience/base_classes/new_base.py
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
class NewBase:
    """This is the new base class for easyscience objects.

    It provides serialization capabilities as well as unique naming and
    display naming.
    """

    def __init__(self, unique_name: Optional[str] = None, display_name: Optional[str] = None):
        self._global_object = global_object
        if unique_name is None:
            unique_name = self._global_object.generate_unique_name(self.__class__.__name__)
            self._default_unique_name = True
        else:
            self._default_unique_name = False
        if not isinstance(unique_name, str):
            raise TypeError('Unique name has to be a string.')
        self._unique_name = unique_name
        self._global_object.map.add_vertex(self, obj_type='created')
        if display_name is not None and not isinstance(display_name, str):
            raise TypeError('Display name must be a string or None')
        self._display_name = display_name

    @property
    def _arg_spec(self) -> Set[str]:
        """This method is used by the serializer to determine which
        arguments are needed by the constructor to deserialize the
        object.
        """
        sign = signature(self.__class__.__init__)
        names = [
            param.name
            for param in sign.parameters.values()
            if param.kind == param.POSITIONAL_OR_KEYWORD
        ]
        return set(names[1:])

    @property
    def unique_name(self) -> str:
        """Get the unique name of the object."""
        return self._unique_name

    @unique_name.setter
    def unique_name(self, new_unique_name: str):
        """Set a new unique name for the object. The old name is still
        kept in the map.

        :param new_unique_name: New unique name for the object
        """
        if not isinstance(new_unique_name, str):
            raise TypeError('Unique name has to be a string.')
        self._unique_name = new_unique_name
        self._global_object.map.add_vertex(self)
        self._default_unique_name = False

    @property
    def display_name(self) -> str:
        """Get a pretty display name.

        :return: The pretty display name.
        """
        display_name = self._display_name
        if display_name is None:
            display_name = self.unique_name
        return display_name

    @display_name.setter
    @property_stack
    def display_name(self, name: str | None) -> None:
        """Set the pretty display name.

        :param name: Pretty display name of the object.
        """
        if name is not None and not isinstance(name, str):
            raise TypeError('Display name must be a string or None')
        self._display_name = name

    def to_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]:
        """Convert an EasyScience object into a full dictionary using
        `SerializerBase`s generic `convert_to_dict` method.

        :param skip: List of field names as strings to skip when forming
            the dictionary
        :return: encoded object containing all information to reform an
            EasyScience object.
        """
        serializer = SerializerBase()
        if skip is None:
            skip = []
        if self._default_unique_name and 'unique_name' not in skip:
            skip.append('unique_name')
        if self._display_name is None:
            skip.append('display_name')
        return serializer._convert_to_dict(self, skip=skip, full_encode=False)

    @classmethod
    def from_dict(cls, obj_dict: Dict[str, Any]) -> NewBase:
        """Re-create an EasyScience object from a full encoded
        dictionary.

        :param obj_dict: dictionary containing the serialized contents (from `SerializerDict`) of an EasyScience object
        :return: Reformed EasyScience object
        """
        if not SerializerBase._is_serialized_easyscience_object(obj_dict):
            raise ValueError('Input must be a dictionary representing an EasyScience object.')
        if obj_dict['@class'] == cls.__name__:
            kwargs = SerializerBase.deserialize_dict(obj_dict)
            return cls(**kwargs)
        else:
            raise ValueError(
                f'Class name in dictionary does not match the expected class: {cls.__name__}.'
            )

    def __dir__(self) -> Iterable[str]:
        """This creates auto-completion and helps out in iPython
        notebooks.

        :return: list of function and parameter names for auto-
            completion
        """
        new_class_objs = list(k for k in dir(self.__class__) if not k.startswith('_'))
        return sorted(new_class_objs)

    def __copy__(self) -> NewBase:
        """Return a copy of the object."""
        temp = self.to_dict(skip=['unique_name'])
        new_obj = self.__class__.from_dict(temp)
        return new_obj

    def __deepcopy__(self, memo):
        return self.__copy__()

    def __repr__(self) -> str:
        return f'{self.__class__.__name__} `{self.unique_name}`'

__copy__()

Return a copy of the object.

Source code in src/easyscience/base_classes/new_base.py
145
146
147
148
149
def __copy__(self) -> NewBase:
    """Return a copy of the object."""
    temp = self.to_dict(skip=['unique_name'])
    new_obj = self.__class__.from_dict(temp)
    return new_obj

__dir__()

This creates auto-completion and helps out in iPython notebooks.

:return: list of function and parameter names for auto- completion

Source code in src/easyscience/base_classes/new_base.py
135
136
137
138
139
140
141
142
143
def __dir__(self) -> Iterable[str]:
    """This creates auto-completion and helps out in iPython
    notebooks.

    :return: list of function and parameter names for auto-
        completion
    """
    new_class_objs = list(k for k in dir(self.__class__) if not k.startswith('_'))
    return sorted(new_class_objs)

display_name property writable

Get a pretty display name.

:return: The pretty display name.

from_dict(obj_dict) classmethod

Re-create an EasyScience object from a full encoded dictionary.

:param obj_dict: dictionary containing the serialized contents (from SerializerDict) of an EasyScience object :return: Reformed EasyScience object

Source code in src/easyscience/base_classes/new_base.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@classmethod
def from_dict(cls, obj_dict: Dict[str, Any]) -> NewBase:
    """Re-create an EasyScience object from a full encoded
    dictionary.

    :param obj_dict: dictionary containing the serialized contents (from `SerializerDict`) of an EasyScience object
    :return: Reformed EasyScience object
    """
    if not SerializerBase._is_serialized_easyscience_object(obj_dict):
        raise ValueError('Input must be a dictionary representing an EasyScience object.')
    if obj_dict['@class'] == cls.__name__:
        kwargs = SerializerBase.deserialize_dict(obj_dict)
        return cls(**kwargs)
    else:
        raise ValueError(
            f'Class name in dictionary does not match the expected class: {cls.__name__}.'
        )

to_dict(skip=None)

Convert an EasyScience object into a full dictionary using SerializerBases generic convert_to_dict method.

:param skip: List of field names as strings to skip when forming the dictionary :return: encoded object containing all information to reform an EasyScience object.

Source code in src/easyscience/base_classes/new_base.py
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
def to_dict(self, skip: Optional[List[str]] = None) -> Dict[str, Any]:
    """Convert an EasyScience object into a full dictionary using
    `SerializerBase`s generic `convert_to_dict` method.

    :param skip: List of field names as strings to skip when forming
        the dictionary
    :return: encoded object containing all information to reform an
        EasyScience object.
    """
    serializer = SerializerBase()
    if skip is None:
        skip = []
    if self._default_unique_name and 'unique_name' not in skip:
        skip.append('unique_name')
    if self._display_name is None:
        skip.append('display_name')
    return serializer._convert_to_dict(self, skip=skip, full_encode=False)

unique_name property writable

Get the unique name of the object.

obj_base

ObjBase

Bases: BasedBase

This is the base class for which all higher level classes are built off of.

NOTE: This object is serializable only if parameters are supplied as: ObjBase(a=value, b=value). For Parameter or Descriptor objects we can cheat with ObjBase(*[Descriptor(...), Parameter(...), ...]).

Source code in src/easyscience/base_classes/obj_base.py
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
class ObjBase(BasedBase):
    """This is the base class for which all higher level classes are
    built off of.

    NOTE: This object is serializable only if parameters are supplied as:
    `ObjBase(a=value, b=value)`. For `Parameter` or `Descriptor` objects we can
    cheat with `ObjBase(*[Descriptor(...), Parameter(...), ...])`.
    """

    def __init__(
        self,
        name: str,
        unique_name: Optional[str] = None,
        *args: Optional[SerializerComponent],
        **kwargs: Optional[SerializerComponent],
    ):
        """Set up the base class.

        :param name: Name of this object
        :param args: Any arguments?
        :param kwargs: Fields which this class should contain
        """
        super(ObjBase, self).__init__(name=name, unique_name=unique_name)
        # If Parameter or Descriptor is given as arguments...
        for arg in args:
            if issubclass(type(arg), (ObjBase, DescriptorBase)):
                kwargs[getattr(arg, 'name')] = arg
        # Set kwargs, also useful for serialization
        known_keys = self.__dict__.keys()
        self._kwargs = kwargs
        for key in kwargs.keys():
            if key in known_keys:
                raise AttributeError('Kwargs cannot overwrite class attributes in ObjBase.')
            if issubclass(type(kwargs[key]), (BasedBase, DescriptorBase)) or 'CollectionBase' in [
                c.__name__ for c in type(kwargs[key]).__bases__
            ]:
                self._global_object.map.add_edge(self, kwargs[key])
                self._global_object.map.reset_type(kwargs[key], 'created_internal')
            addLoggedProp(
                self,
                key,
                self.__getter(key),
                self.__setter(key),
                get_id=key,
                my_self=self,
                test_class=ObjBase,
            )

    def _add_component(self, key: str, component: SerializerComponent) -> None:
        """Dynamically add a component to the class. This is an internal
        method, though can be called remotely. The recommended
        alternative is to use typing, i.e.

        .. code-block:: python

            class Foo(Bar):
                def __init__(self, foo: Parameter, bar: Parameter):
                    super(Foo, self).__init__(bar=bar)
                    self._add_component('foo', foo)

        :param key: Name of component to be added
        :param component: Component to be added
        :return: None
        """
        self._kwargs[key] = component
        self._global_object.map.add_edge(self, component)
        self._global_object.map.reset_type(component, 'created_internal')
        addLoggedProp(
            self,
            key,
            self.__getter(key),
            self.__setter(key),
            get_id=key,
            my_self=self,
            test_class=ObjBase,
        )

    def __setattr__(self, key: str, value: SerializerComponent) -> None:
        # Assume that the annotation is a ClassVar
        old_obj = None
        if (
            hasattr(self.__class__, '__annotations__')
            and key in self.__class__.__annotations__
            and hasattr(self.__class__.__annotations__[key], '__args__')
            and issubclass(
                getattr(value, '__old_class__', value.__class__),
                self.__class__.__annotations__[key].__args__,
            )
        ):
            if issubclass(type(getattr(self, key, None)), (BasedBase, DescriptorBase)):
                old_obj = self.__getattribute__(key)
                self._global_object.map.prune_vertex_from_edge(self, old_obj)
            self._add_component(key, value)
        else:
            if hasattr(self, key) and issubclass(type(value), (BasedBase, DescriptorBase)):
                old_obj = self.__getattribute__(key)
                self._global_object.map.prune_vertex_from_edge(self, old_obj)
                self._global_object.map.add_edge(self, value)
        super(ObjBase, self).__setattr__(key, value)
        # Update the interface bindings if something changed (BasedBase and Descriptor)
        if old_obj is not None:
            old_interface = getattr(self, 'interface', None)
            if old_interface is not None:
                self.generate_bindings()

    def __repr__(self) -> str:
        return f'{self.__class__.__name__} `{getattr(self, "name")}`'

    @staticmethod
    def __getter(key: str) -> Callable[[SerializerComponent], SerializerComponent]:
        def getter(obj: SerializerComponent) -> SerializerComponent:
            return obj._kwargs[key]

        return getter

    @staticmethod
    def __setter(key: str) -> Callable[[SerializerComponent], None]:
        def setter(obj: SerializerComponent, value: float) -> None:
            if issubclass(obj._kwargs[key].__class__, (DescriptorBase)) and not issubclass(
                value.__class__, (DescriptorBase)
            ):
                obj._kwargs[key].value = value
            else:
                obj._kwargs[key] = value

        return setter

__init__(name, unique_name=None, *args, **kwargs)

Set up the base class.

:param name: Name of this object :param args: Any arguments? :param kwargs: Fields which this class should contain

Source code in src/easyscience/base_classes/obj_base.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def __init__(
    self,
    name: str,
    unique_name: Optional[str] = None,
    *args: Optional[SerializerComponent],
    **kwargs: Optional[SerializerComponent],
):
    """Set up the base class.

    :param name: Name of this object
    :param args: Any arguments?
    :param kwargs: Fields which this class should contain
    """
    super(ObjBase, self).__init__(name=name, unique_name=unique_name)
    # If Parameter or Descriptor is given as arguments...
    for arg in args:
        if issubclass(type(arg), (ObjBase, DescriptorBase)):
            kwargs[getattr(arg, 'name')] = arg
    # Set kwargs, also useful for serialization
    known_keys = self.__dict__.keys()
    self._kwargs = kwargs
    for key in kwargs.keys():
        if key in known_keys:
            raise AttributeError('Kwargs cannot overwrite class attributes in ObjBase.')
        if issubclass(type(kwargs[key]), (BasedBase, DescriptorBase)) or 'CollectionBase' in [
            c.__name__ for c in type(kwargs[key]).__bases__
        ]:
            self._global_object.map.add_edge(self, kwargs[key])
            self._global_object.map.reset_type(kwargs[key], 'created_internal')
        addLoggedProp(
            self,
            key,
            self.__getter(key),
            self.__setter(key),
            get_id=key,
            my_self=self,
            test_class=ObjBase,
        )