MemoryAttribute   A
last analyzed

Complexity

Total Complexity 2

Size/Duplication

Total Lines 85
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 85
rs 10
wmc 2

2 Methods

Rating   Name   Duplication   Size   Complexity  
B __init__() 0 24 1
B __setitem__() 0 25 1
1
# This Python file uses the following encoding: utf-8
2
3
"""
4
The :mod:`galactic.context.memory` module give the ability to define
5
:class:`Context <galactic.context.Context>` that resides in memory.
6
"""
7
8
from typing import Mapping, Iterable, Union
9
10
from galactic.context import Context, Model, Population, Attribute, Individual
11
from galactic.context.mixins import ContextHolder, PopulationHolder, ModelHolder, \
12
    ConcreteAttribute, ConcreteIndividual, AttributesHolder, IndividualsHolder, ValuesHolder
13
14
15
# pylint: disable=too-few-public-methods,abstract-method,super-init-not-called
16
class MemoryContext(
17
        PopulationHolder['MemoryPopulation'],
18
        ModelHolder['MemoryModel'],
19
        Context['MemoryPopulation', 'MemoryModel', 'MemoryIndividual', 'MemoryAttribute']
20
):
21
    """
22
    The :class:`MemoryContext` class is designed to define contexts in memory. It inherits of all
23
    the behavior from the :class:`Context <galactic.context.Context>` class and allows direct
24
    creation and modification of a context.
25
26
    It's possible to create a context without nothing.
27
28
    Example
29
    -------
30
31
        >>> from galactic.context.memory import MemoryContext
32
        >>> context = MemoryContext()
33
        >>> print(context)
34
        {'population': [], 'model': {}}
35
36
    It's possible to create a context specifying the model definition.
37
38
    Example
39
    -------
40
41
        >>> from galactic.context.memory import MemoryContext
42
        >>> context = MemoryContext(definition={'mybool': bool, 'myint': int})
43
        >>> print(context)
44
        {'population': [], 'model': {'mybool': <class 'bool'>, 'myint': <class 'int'>}}
45
46
    It's possible to create a context specifying the model definition and the list of individual
47
    identifiers.
48
49
    Example
50
    -------
51
52
        >>> context = MemoryContext(
53
        ...     definition={'mybool': bool, 'myint': int},
54
        ...     individuals=['0', '1']
55
        ... )
56
        >>> print(context)
57
        {'population': ['0', '1'], 'model': {'mybool': <class 'bool'>, 'myint': <class 'int'>}}
58
59
    It's possible to create a context specifying the model definition and the individual values.
60
61
    Example
62
    -------
63
64
        >>> context = MemoryContext(
65
        ...     definition={'mybool': bool, 'myint': int},
66
        ...     individuals={'0': {'mybool': True}, '1':{'myint': 1}}
67
        ... )
68
        >>> {ident: str(context.population[ident]) for ident in context.population}
69
        {'0': "{'mybool': True, 'myint': 0}", '1': "{'mybool': False, 'myint': 1}"}
70
71
    .. versionadded:: 0.0.1
72
    """
73
74
    def __init__(self, **kwargs):
75
        """
76
        Initialise a context in memory.
77
78
        Keyword Arguments
79
        -----------------
80
            definition : :class:`Mapping[str, type] <python:collections.abc.Mapping>`
81
                definition of the context by a mapping from name of attributes to their type
82
            individuals : :class:`Union[Iterable[str], Mapping[str, Mapping[str, object]]]`
83
                initial iterable of individual identifiers or a mapping from individual
84
                identifiers to individual values
85
86
        Raises
87
        ------
88
            KeyError
89
                if an attribute is not in the definition
90
            ValueError
91
                if a value does not correspond to an attribute type
92
            TypeError
93
                if the definition or if the individuals parameter are not of the correct type
94
95
        .. versionadded:: 0.0.1
96
        """
97
        definition = MemoryContext._definition(**kwargs)
98
        individuals = MemoryContext._individuals(**kwargs)
99
100
        super().__init__(
101
            model=MemoryModel(self, {} if definition is None else definition),
102
            population=MemoryPopulation(self, [])
103
        )
104
105
        if isinstance(individuals, Mapping):
106
            for identifier, values in individuals.items():
107
                self.population[identifier] = values
108
        else:
109
            if individuals is not None:
110
                for identifier in individuals:
111
                    self.population[identifier] = {}
112
113
    @staticmethod
114
    def _definition(**kwargs) -> Mapping[str, type]:
115
        """
116
        Get the definition from the keyword arguments.
117
118
        Keyword arguments
119
        -----------------
120
            definition : :class:`Mapping[str, type] <python:collections.abc.Mapping>`
121
                definition of the context by a mapping from name of attributes to their type
122
123
        Raises
124
        ------
125
            TypeError
126
                if the definition parameter is not of the correct type
127
128
        .. versionadded:: 0.0.2
129
        """
130
        if 'definition' in kwargs:
131
            definition = kwargs['definition']
132
            if not isinstance(definition, Mapping):
133
                raise TypeError
134
        else:
135
            definition = None
136
        return definition
137
138
    @staticmethod
139
    def _individuals(**kwargs) -> Union[Iterable[str], Mapping[str, Mapping[str, object]]]:
140
        """
141
        Get the individuals from the keyword arguments.
142
143
        Keyword arguments
144
        -----------------
145
            individuals : :class:`Union[Iterable[str], Mapping[str, Mapping[str, object]]]`
146
                initial iterable of individual identifiers or a mapping from individual
147
                identifiers to individual values
148
149
        Raises
150
        ------
151
            TypeError
152
                if the definition parameter is not of the correct type
153
154
        .. versionadded:: 0.0.2
155
        """
156
        if 'individuals' in kwargs:
157
            individuals = kwargs['individuals']
158
            if not isinstance(individuals, (Iterable, Mapping)):
159
                raise TypeError
160
        else:
161
            individuals = None
162
        return individuals
163
164
165
# pylint: disable=too-few-public-methods,abstract-method,super-init-not-called
166
class MemoryModel(
167
        ContextHolder[MemoryContext],
168
        AttributesHolder['MemoryAttribute'],
169
        Model[MemoryContext, 'MemoryPopulation', 'MemoryAttribute']
170
):
171
    """
172
    The :class:`MemoryModel` class is designed to define models that resides in memory. It inherits
173
    of all the behavior from the :class:`Model <galactic.context.Model>` class.
174
175
    It's possible to change or to set attribute values.
176
177
    Example
178
    -------
179
180
        >>> from galactic.context.memory import MemoryContext
181
        >>> context = MemoryContext(
182
        ...     definition={'mybool': bool, 'myint': int},
183
        ...     individuals=['0', '1']
184
        ... )
185
        >>> model = context.model
186
        >>> model['mybool'] = int
187
        >>> model['myint2'] = int
188
        >>> print(context.population['0'])
189
        {'mybool': 0, 'myint': 0, 'myint2': 0}
190
191
    It's possible to delete an attribute using its name.
192
193
    Example
194
    -------
195
196
        >>> from galactic.context.memory import MemoryContext
197
        >>> context = MemoryContext(
198
        ...     definition={'mybool': bool, 'myint': int},
199
        ...     individuals=['0', '1']
200
        ... )
201
        >>> model = context.model
202
        >>> del model['mybool']
203
        >>> {ident: str(context.population[ident]) for ident in context.population}
204
        {'0': "{'myint': 0}", '1': "{'myint': 0}"}
205
206
207
    .. versionadded:: 0.0.1
208
    """
209
210
    def __init__(
211
            self,
212
            context: MemoryContext,
213
            definition: Mapping[str, 'type']
214
    ):
215
        """
216
        Initialise a model in memory.
217
218
        Parameters
219
        ----------
220
            context : :class:`MemoryContext`
221
                the underlying context
222
            definition : :class:`Mapping[str, type] <python:collections.abc.Mapping>`
223
                the attributes definition
224
225
        .. versionadded:: 0.0.1
226
        """
227
        super().__init__(
228
            context=context,
229
            attributes=[MemoryAttribute(self, name, cls) for name, cls in definition.items()]
230
        )
231
232
    def __delitem__(self, name: str):
233
        """
234
        Delete an attribute from a :class:`MemoryModel`.
235
236
        Parameters
237
        ----------
238
            name : :class:`str`
239
                attribute name
240
241
        Raises
242
        ------
243
            KeyError
244
                if the attribute is not in the model
245
246
        .. versionadded:: 0.0.1
247
        """
248
        super().__delitem__(name)
249
        for identifier in self.population:
250
            del self.population[identifier][name]
251
252
    def __setitem__(self, name: str, cls: type):
253
        """
254
        Set an attribute for a :class:`MemoryModel`.
255
256
        Parameters
257
        ----------
258
            name : :class:`str`
259
                the attribute name
260
            cls : :class:`python:type`
261
                the attribute type
262
263
        .. versionadded:: 0.0.1
264
        """
265
        if name in self:
266
            if self[name].type != cls:
267
                super().__setitem__(name, MemoryAttribute(self, name, cls))
268
                for identifier in self.population:
269
                    try:
270
                        self.population[identifier][name] = cls(self.population[identifier][name])
271
                    except (ValueError, TypeError):
272
                        self.population[identifier][name] = cls()
273
        else:
274
            super().__setitem__(name, MemoryAttribute(self, name, cls))
275
            for identifier in self.population:
276
                self.population[identifier][name] = cls()
277
278
279
# pylint: disable=too-few-public-methods,abstract-method,super-init-not-called
280
class MemoryPopulation(
281
        ContextHolder[MemoryContext],
282
        IndividualsHolder['MemoryIndividual'],
283
        Population[MemoryContext, MemoryModel, 'MemoryIndividual']
284
):
285
    """
286
    The :class:`MemoryPopulation` class is designed to define populations that resides in memory. It
287
    inherits of all the behavior from the :class:`Population <galactic.context.Population>` class.
288
289
    It's possible to change or to set individual values.
290
291
    Example
292
    -------
293
294
        >>> from galactic.context.memory import MemoryContext
295
        >>> context = MemoryContext(
296
        ...     definition={'mybool': bool, 'myint': int},
297
        ...     individuals=['0', '1']
298
        ... )
299
        >>> population = context.population
300
        >>> population['0'] = {'mybool': True}
301
        >>> population['2'] = {'myint': 1}
302
        >>> print(population['0'])
303
        {'mybool': True, 'myint': 0}
304
        >>> print(population['2'])
305
        {'mybool': False, 'myint': 1}
306
307
    It's possible to delete an individual using its identifier.
308
309
    Example
310
    -------
311
312
        >>> from galactic.context.memory import MemoryContext
313
        >>> context = MemoryContext(
314
        ...     definition={'mybool': bool, 'myint': int},
315
        ...     individuals=['0', '1']
316
        ... )
317
        >>> population = context.population
318
        >>> del population['0']
319
        >>> {ident: str(context.population[ident]) for ident in context.population}
320
        {'1': "{'mybool': False, 'myint': 0}"}
321
322
    .. versionadded:: 0.0.1
323
    """
324
325
    def __init__(
326
            self,
327
            context: MemoryContext,
328
            identifiers: Iterable[str]
329
    ):
330
        """
331
        Initialise a population in memory.
332
333
        Parameters
334
        ----------
335
            context : :class:`MemoryContext`
336
                the underlying context
337
            identifiers : :class:`Iterable[str] <python:collections.abc.Iterable>`
338
                an iterable of identifiers
339
340
        .. versionadded:: 0.0.1
341
        """
342
        super().__init__(
343
            context=context,
344
            individuals=[MemoryIndividual(self, identifier) for identifier in identifiers]
345
        )
346
347
    def __setitem__(self, identifier: str, values: Mapping[str, object]):
348
        """
349
        Set an individual for a :class:`MemoryPopulation`.
350
351
        Parameters
352
        ----------
353
            identifier : :class:`str`
354
                the individual identifier
355
            values : :class:`Mapping[str, object] <<python:collections.abc.mapping>>`
356
                the initial values for some attributes
357
358
        Raises
359
        ------
360
            KeyError
361
                if an attribute name does not belong to the underlying model.
362
            ValueError
363
                if an attribute value does not correspond to its type
364
365
        .. versionadded:: 0.0.1
366
        """
367
        try:
368
            individual = self[identifier]
369
        except KeyError:
370
            individual = MemoryIndividual(self, identifier)
371
            super().__setitem__(identifier, individual)
372
        for name, value in values.items():
373
            if name not in self.model:
374
                raise KeyError
375
            individual[name] = value
376
377
378
# pylint: disable=too-few-public-methods,abstract-method,super-init-not-called
379
class MemoryIndividual(
380
        PopulationHolder[MemoryPopulation],
381
        ValuesHolder,
382
        ConcreteIndividual,
383
        Individual[
384
            MemoryContext,
385
            MemoryPopulation,
386
            MemoryModel,
387
            'MemoryIndividual',
388
            'MemoryAttribute'
389
        ]
390
):
391
    """
392
    The :class:`MemoryIndividual` is designed to define individuals that resides in memory. It
393
    inherits of all the behavior from the :class:`Individual <galactic.context.Individual>` class.
394
395
    It's possible to modify a value for an individual using an attribute name.
396
397
    Example
398
    -------
399
400
        >>> from galactic.context.memory import MemoryContext
401
        >>> context = MemoryContext(
402
        ...     definition={'mybool': bool, 'myint': int},
403
        ...     individuals=['0', '1']
404
        ... )
405
        >>> individual = context.population['0']
406
        >>> individual['mybool'] = True
407
        >>> individual['myint'] = 1
408
        >>> {ident: str(context.population[ident]) for ident in context.population}
409
        {'0': "{'mybool': True, 'myint': 1}", '1': "{'mybool': False, 'myint': 0}"}
410
411
    .. versionadded:: 0.0.1
412
    """
413
414
    def __init__(
415
            self,
416
            population: MemoryPopulation,
417
            identifier: str
418
    ):
419
        """
420
        Initialise an individual.
421
422
        Parameters
423
        ----------
424
            population : :class:`MemoryPopulation`
425
                the population
426
            identifier : :class:`str`
427
                the individual identifier
428
429
        .. versionadded:: 0.0.1
430
        """
431
        super().__init__(
432
            population=population,
433
            identifier=identifier,
434
            values={name: attribute.type() for name, attribute in population.model.items()}
435
        )
436
437
    def __setitem__(self, name: str, value):
438
        """
439
        Set an individual's attribute value using either the attribute or its name.
440
441
        Parameters
442
        ----------
443
            name: :class:`str`
444
                the attribute name
445
            value : :class:`object`
446
                the value
447
448
        Raises
449
        ------
450
            KeyError
451
                if the attribute does not belong to the underlying context
452
            ValueError
453
                if the value passed in argument has its type different of the attribute type
454
455
        .. versionadded:: 0.0.1
456
        """
457
        attribute = self.model[name]
458
        if not isinstance(value, attribute.type):
459
            value = attribute.type(value)
460
        super().__setitem__(name, value)
461
462
    def __delitem__(self, name: str):
463
        """
464
        Delete an individual value.
465
466
        Parameters
467
        ----------
468
            name: :class:`str`
469
                the attribute name
470
471
        Raises
472
        ------
473
            ValueError
474
                if the attribute is in the model (which should be always the case)
475
476
        .. versionadded:: 0.0.1
477
        """
478
        if name in self.model:
479
            raise ValueError
480
        else:
481
            super().__delitem__(name)
482
483
484
# pylint: disable=too-few-public-methods,abstract-method,super-init-not-called
485
class MemoryAttribute(
486
        ModelHolder[MemoryModel],
487
        ConcreteAttribute,
488
        Attribute[
489
            MemoryContext,
490
            MemoryPopulation,
491
            MemoryModel,
492
            MemoryIndividual,
493
            'MemoryAttribute'
494
        ]
495
):
496
    """
497
    The :class:`MemoryAttribute` is designed to define attributes that resides in memory. It
498
    inherits of all the behavior from the :class:`Attribute <galactic.context.Attribute>` class.
499
500
    It's possible to modify a value for an attribute using an individual identifier.
501
502
    Example
503
    -------
504
505
        >>> from galactic.context.memory import MemoryContext
506
        >>> context = MemoryContext(
507
        ...     definition={'mybool': bool, 'myint': int},
508
        ...     individuals=['0', '1']
509
        ... )
510
        >>> attribute = context.model['myint']
511
        >>> attribute['0'] = 3
512
        >>> attribute['1'] = 4
513
        >>> {ident: str(context.population[ident]) for ident in context.population}
514
        {'0': "{'mybool': False, 'myint': 3}", '1': "{'mybool': False, 'myint': 4}"}
515
516
    .. versionadded:: 0.0.1
517
    """
518
519
    def __init__(
520
            self,
521
            model: MemoryModel,
522
            name: str,
523
            cls: 'type'
524
    ):
525
        """
526
        Initialise an attribute.
527
528
        Parameters
529
        ----------
530
            model : :class:`MemoryModel`
531
                the underlying model
532
            name : :class:`str`
533
                the attribute name
534
            cls : :class:`type <python:type>`
535
                the attribute type
536
537
        .. versionadded:: 0.0.1
538
        """
539
        super().__init__(
540
            model=model,
541
            name=name,
542
            type=cls
543
        )
544
545
    def __setitem__(self, identifier: str, value):
546
        """
547
        Set an individual's attribute value using either the individual or its identifier.
548
549
        This method raises a :class:`KeyError` exception if the attribute does not belong to
550
        the underlying context and a :class:`ValueError` exception if the value passed in argument
551
        has its type different of the attribute type.
552
553
        Parameters
554
        ----------
555
            identifier : :class:`str`
556
                the individual identifier
557
            value : :class:`object`
558
                the value
559
560
        Raises
561
        ------
562
            KeyError
563
                if the individual does not exists
564
            ValueError
565
                if the value is not of the attribute type
566
567
        .. versionadded:: 0.0.1
568
        """
569
        self.population[identifier][self.name] = value
570