Passed
Pull Request — master (#4)
by Christophe
01:00
created

MemoryContext.__init__()   F

Complexity

Conditions 10

Size

Total Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
c 1
b 0
f 0
dl 0
loc 49
rs 3.7894

How to fix   Complexity   

Complexity

Complex classes like MemoryContext.__init__() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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
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
        if 'definition' in kwargs:
98
            definition = kwargs['definition']
99
            if not isinstance(definition, Mapping):
100
                raise TypeError
101
        else:
102
            definition = None
103
104
        if 'individuals' in kwargs:
105
            individuals = kwargs['individuals']
106
            if not isinstance(individuals, (Iterable, Mapping)):
107
                raise TypeError
108
        else:
109
            individuals = None
110
111
        super().__init__(
112
            model=MemoryModel(self, {} if definition is None else definition),
113
            population=MemoryPopulation(self, [])
114
        )
115
116
        if isinstance(individuals, Mapping):
117
            for identifier, values in individuals.items():
118
                self.population[identifier] = values
119
        else:
120
            if individuals is not None:
121
                for identifier in individuals:
122
                    self.population[identifier] = {}
123
124
125
# pylint: disable=too-few-public-methods,abstract-method,super-init-not-called
126
class MemoryModel(
127
        ContextHolder[MemoryContext],
128
        AttributesHolder['MemoryAttribute'],
129
        Model[MemoryContext, 'MemoryPopulation', 'MemoryAttribute']
130
):
131
    """
132
    The :class:`MemoryModel` class is designed to define models that resides in memory. It inherits
133
    of all the behavior from the :class:`Model <galactic.context.Model>` class.
134
135
    It's possible to change or to set attribute values.
136
137
    Example
138
    -------
139
140
        >>> from galactic.context.memory import MemoryContext
141
        >>> context = MemoryContext(
142
        ...     definition={'mybool': bool, 'myint': int},
143
        ...     individuals=['0', '1']
144
        ... )
145
        >>> model = context.model
146
        >>> model['mybool'] = int
147
        >>> model['myint2'] = int
148
        >>> print(context.population['0'])
149
        {'mybool': 0, 'myint': 0, 'myint2': 0}
150
151
    It's possible to delete an attribute using its name.
152
153
    Example
154
    -------
155
156
        >>> from galactic.context.memory import MemoryContext
157
        >>> context = MemoryContext(
158
        ...     definition={'mybool': bool, 'myint': int},
159
        ...     individuals=['0', '1']
160
        ... )
161
        >>> model = context.model
162
        >>> del model['mybool']
163
        >>> {ident: str(context.population[ident]) for ident in context.population}
164
        {'0': "{'myint': 0}", '1': "{'myint': 0}"}
165
166
167
    .. versionadded:: 0.0.1
168
    """
169
170
    def __init__(
171
            self,
172
            context: MemoryContext,
173
            definition: Mapping[str, 'type']
174
    ):
175
        """
176
        Initialise a model in memory.
177
178
        Parameters
179
        ----------
180
            context : :class:`MemoryContext`
181
                the underlying context
182
            definition : :class:`Mapping[str, type] <python:collections.abc.Mapping>`
183
                the attributes definition
184
185
        .. versionadded:: 0.0.1
186
        """
187
        super().__init__(
188
            context=context,
189
            attributes=[MemoryAttribute(self, name, cls) for name, cls in definition.items()]
190
        )
191
192
    def __delitem__(self, name: str):
193
        """
194
        Delete an attribute from a :class:`MemoryModel`.
195
196
        Parameters
197
        ----------
198
            name : :class:`str`
199
                attribute name
200
201
        Raises
202
        ------
203
            KeyError
204
                if the attribute is not in the model
205
206
        .. versionadded:: 0.0.1
207
        """
208
        super().__delitem__(name)
209
        for identifier in self.population:
210
            del self.population[identifier][name]
211
212
    def __setitem__(self, name: str, cls: type):
213
        """
214
        Set an attribute for a :class:`MemoryModel`.
215
216
        Parameters
217
        ----------
218
            name : :class:`str`
219
                the attribute name
220
            cls : :class:`python:type`
221
                the attribute type
222
223
        .. versionadded:: 0.0.1
224
        """
225
        if name in self:
226
            if self[name].type != cls:
227
                super().__setitem__(name, MemoryAttribute(self, name, cls))
228
                for identifier in self.population:
229
                    try:
230
                        self.population[identifier][name] = cls(self.population[identifier][name])
231
                    except (ValueError, TypeError):
232
                        self.population[identifier][name] = cls()
233
        else:
234
            super().__setitem__(name, MemoryAttribute(self, name, cls))
235
            for identifier in self.population:
236
                self.population[identifier][name] = cls()
237
238
239
# pylint: disable=too-few-public-methods,abstract-method,super-init-not-called
240
class MemoryPopulation(
241
        ContextHolder[MemoryContext],
242
        IndividualsHolder['MemoryIndividual'],
243
        Population[MemoryContext, MemoryModel, 'MemoryIndividual']
244
):
245
    """
246
    The :class:`MemoryPopulation` class is designed to define populations that resides in memory. It
247
    inherits of all the behavior from the :class:`Population <galactic.context.Population>` class.
248
249
    It's possible to change or to set individual values.
250
251
    Example
252
    -------
253
254
        >>> from galactic.context.memory import MemoryContext
255
        >>> context = MemoryContext(
256
        ...     definition={'mybool': bool, 'myint': int},
257
        ...     individuals=['0', '1']
258
        ... )
259
        >>> population = context.population
260
        >>> population['0'] = {'mybool': True}
261
        >>> population['2'] = {'myint': 1}
262
        >>> print(population['0'])
263
        {'mybool': True, 'myint': 0}
264
        >>> print(population['2'])
265
        {'mybool': False, 'myint': 1}
266
267
    It's possible to delete an individual using its identifier.
268
269
    Example
270
    -------
271
272
        >>> from galactic.context.memory import MemoryContext
273
        >>> context = MemoryContext(
274
        ...     definition={'mybool': bool, 'myint': int},
275
        ...     individuals=['0', '1']
276
        ... )
277
        >>> population = context.population
278
        >>> del population['0']
279
        >>> {ident: str(context.population[ident]) for ident in context.population}
280
        {'1': "{'mybool': False, 'myint': 0}"}
281
282
    .. versionadded:: 0.0.1
283
    """
284
285
    def __init__(
286
            self,
287
            context: MemoryContext,
288
            identifiers: Iterable[str]
289
    ):
290
        """
291
        Initialise a population in memory.
292
293
        Parameters
294
        ----------
295
            context : :class:`MemoryContext`
296
                the underlying context
297
            identifiers : :class:`Iterable[str] <python:collections.abc.Iterable>`
298
                an iterable of identifiers
299
300
        .. versionadded:: 0.0.1
301
        """
302
        super().__init__(
303
            context=context,
304
            individuals=[MemoryIndividual(self, identifier) for identifier in identifiers]
305
        )
306
307
    def __setitem__(self, identifier: str, values: Mapping[str, object]):
308
        """
309
        Set an individual for a :class:`MemoryPopulation`.
310
311
        Parameters
312
        ----------
313
            identifier : :class:`str`
314
                the individual identifier
315
            values : :class:`Mapping[str, object] <<python:collections.abc.mapping>>`
316
                the initial values for some attributes
317
318
        Raises
319
        ------
320
            KeyError
321
                if an attribute name does not belong to the underlying model.
322
            ValueError
323
                if an attribute value does not correspond to its type
324
325
        .. versionadded:: 0.0.1
326
        """
327
        try:
328
            individual = self[identifier]
329
        except KeyError:
330
            individual = MemoryIndividual(self, identifier)
331
            super().__setitem__(identifier, individual)
332
        for name, value in values.items():
333
            if name not in self.model:
334
                raise KeyError
335
            individual[name] = value
336
337
338
# pylint: disable=too-few-public-methods,abstract-method,super-init-not-called
339
class MemoryIndividual(
340
        PopulationHolder[MemoryPopulation],
341
        ValuesHolder,
342
        ConcreteIndividual,
343
        Individual[
344
            MemoryContext,
345
            MemoryPopulation,
346
            MemoryModel,
347
            'MemoryIndividual',
348
            'MemoryAttribute'
349
        ]
350
):
351
    """
352
    The :class:`MemoryIndividual` is designed to define individuals that resides in memory. It
353
    inherits of all the behavior from the :class:`Individual <galactic.context.Individual>` class.
354
355
    It's possible to modify a value for an individual using an attribute name.
356
357
    Example
358
    -------
359
360
        >>> from galactic.context.memory import MemoryContext
361
        >>> context = MemoryContext(
362
        ...     definition={'mybool': bool, 'myint': int},
363
        ...     individuals=['0', '1']
364
        ... )
365
        >>> individual = context.population['0']
366
        >>> individual['mybool'] = True
367
        >>> individual['myint'] = 1
368
        >>> {ident: str(context.population[ident]) for ident in context.population}
369
        {'0': "{'mybool': True, 'myint': 1}", '1': "{'mybool': False, 'myint': 0}"}
370
371
    .. versionadded:: 0.0.1
372
    """
373
374
    def __init__(
375
            self,
376
            population: MemoryPopulation,
377
            identifier: str
378
    ):
379
        """
380
        Initialise an individual.
381
382
        Parameters
383
        ----------
384
            population : :class:`MemoryPopulation`
385
                the population
386
            identifier : :class:`str`
387
                the individual identifier
388
389
        .. versionadded:: 0.0.1
390
        """
391
        super().__init__(
392
            population=population,
393
            identifier=identifier,
394
            values={name: attribute.type() for name, attribute in population.model.items()}
395
        )
396
397
    def __setitem__(self, name: str, value):
398
        """
399
        Set an individual's attribute value using either the attribute or its name.
400
401
        Parameters
402
        ----------
403
            name: :class:`str`
404
                the attribute name
405
            value : :class:`object`
406
                the value
407
408
        Raises
409
        ------
410
            KeyError
411
                if the attribute does not belong to the underlying context
412
            ValueError
413
                if the value passed in argument has its type different of the attribute type
414
415
        .. versionadded:: 0.0.1
416
        """
417
        attribute = self.model[name]
418
        if not isinstance(value, attribute.type):
419
            value = attribute.type(value)
420
        super().__setitem__(name, value)
421
422
    def __delitem__(self, name: str):
423
        """
424
        Delete an individual value.
425
426
        Parameters
427
        ----------
428
            name: :class:`str`
429
                the attribute name
430
431
        Raises
432
        ------
433
            ValueError
434
                if the attribute is in the model (which should be always the case)
435
436
        .. versionadded:: 0.0.1
437
        """
438
        if name in self.model:
439
            raise ValueError
440
        else:
441
            super().__delitem__(name)
442
443
444
# pylint: disable=too-few-public-methods,abstract-method,super-init-not-called
445
class MemoryAttribute(
446
        ModelHolder[MemoryModel],
447
        ConcreteAttribute,
448
        Attribute[
449
            MemoryContext,
450
            MemoryPopulation,
451
            MemoryModel,
452
            MemoryIndividual,
453
            'MemoryAttribute'
454
        ]
455
):
456
    """
457
    The :class:`MemoryAttribute` is designed to define attributes that resides in memory. It
458
    inherits of all the behavior from the :class:`Attribute <galactic.context.Attribute>` class.
459
460
    It's possible to modify a value for an attribute using an individual identifier.
461
462
    Example
463
    -------
464
465
        >>> from galactic.context.memory import MemoryContext
466
        >>> context = MemoryContext(
467
        ...     definition={'mybool': bool, 'myint': int},
468
        ...     individuals=['0', '1']
469
        ... )
470
        >>> attribute = context.model['myint']
471
        >>> attribute['0'] = 3
472
        >>> attribute['1'] = 4
473
        >>> {ident: str(context.population[ident]) for ident in context.population}
474
        {'0': "{'mybool': False, 'myint': 3}", '1': "{'mybool': False, 'myint': 4}"}
475
476
    .. versionadded:: 0.0.1
477
    """
478
479
    def __init__(
480
            self,
481
            model: MemoryModel,
482
            name: str,
483
            cls: 'type'
484
    ):
485
        """
486
        Initialise an attribute.
487
488
        Parameters
489
        ----------
490
            model : :class:`MemoryModel`
491
                the underlying model
492
            name : :class:`str`
493
                the attribute name
494
            cls : :class:`type <python:type>`
495
                the attribute type
496
497
        .. versionadded:: 0.0.1
498
        """
499
        super().__init__(
500
            model=model,
501
            name=name,
502
            type=cls
503
        )
504
505
    def __setitem__(self, identifier: str, value):
506
        """
507
        Set an individual's attribute value using either the individual or its identifier.
508
509
        This method raises a :class:`KeyError` exception if the attribute does not belong to
510
        the underlying context and a :class:`ValueError` exception if the value passed in argument
511
        has its type different of the attribute type.
512
513
        Parameters
514
        ----------
515
            identifier : :class:`str`
516
                the individual identifier
517
            value : :class:`object`
518
                the value
519
520
        Raises
521
        ------
522
            KeyError
523
                if the individual does not exists
524
            ValueError
525
                if the value is not of the attribute type
526
527
        .. versionadded:: 0.0.1
528
        """
529
        self.population[identifier][self.name] = value
530