|
6 | 6 | import re
|
7 | 7 | import fnmatch
|
8 | 8 | import warnings
|
| 9 | +from copy import copy |
9 | 10 | from collections import OrderedDict, Iterable
|
10 | 11 |
|
11 | 12 | import numpy as np
|
@@ -91,7 +92,7 @@ def __init__(self, *args, **kwargs):
|
91 | 92 |
|
92 | 93 | if len(args) == 1:
|
93 | 94 | a0 = args[0]
|
94 |
| - if isinstance(a0, str): |
| 95 | + if isinstance(a0, basestring): |
95 | 96 | # assume a0 is a filename
|
96 | 97 | self.load(a0)
|
97 | 98 | else:
|
@@ -1391,6 +1392,104 @@ def display(k, v, is_metadata=False):
|
1391 | 1392 | return res
|
1392 | 1393 |
|
1393 | 1394 |
|
| 1395 | +# XXX: I wonder if we shouldn't create an AbstractSession instead of defining the _disabled() |
| 1396 | +# private method below. |
| 1397 | +# Auto-completion on any instance of a class that inherits from FrozenSession |
| 1398 | +# should not propose the add(), update(), filter(), transpose() and compact() methods |
| 1399 | +class FrozenSession(Session): |
| 1400 | + |
| 1401 | + def __init__(self, *args, **kwargs): |
| 1402 | + meta = kwargs.pop('meta', None) |
| 1403 | + |
| 1404 | + if len(args) == 1 and isinstance(args[0], basestring): |
| 1405 | + filepath, args = args[0], args[1:] |
| 1406 | + else: |
| 1407 | + filepath = None |
| 1408 | + |
| 1409 | + if args or kwargs: |
| 1410 | + raise ValueError("All items of '{cls}' must be defined in the class declaration." |
| 1411 | + .format(cls=self.__class__.__name__)) |
| 1412 | + if meta: |
| 1413 | + kwargs['meta'] = meta |
| 1414 | + |
| 1415 | + if filepath: |
| 1416 | + args = [filepath] |
| 1417 | + else: |
| 1418 | + # feed the kwargs dict with all items declared as class attributes |
| 1419 | + for key, value in vars(self.__class__).items(): |
| 1420 | + if not key.startswith('_'): |
| 1421 | + kwargs[key] = value |
| 1422 | + |
| 1423 | + Session.__init__(self, *args, **kwargs) |
| 1424 | + object.__setattr__(self, 'add', self._disabled) |
| 1425 | + |
| 1426 | + def __setitem__(self, key, value): |
| 1427 | + self._check_key_value(key, value) |
| 1428 | + |
| 1429 | + # we need to keep the attribute in sync (initially to mask the class attribute) |
| 1430 | + object.__setattr__(self, key, value) |
| 1431 | + self._objects[key] = value |
| 1432 | + |
| 1433 | + def __setattr__(self, key, value): |
| 1434 | + if key != 'meta': |
| 1435 | + self._check_key_value(key, value) |
| 1436 | + |
| 1437 | + # update the real attribute |
| 1438 | + object.__setattr__(self, key, value) |
| 1439 | + # update self._objects |
| 1440 | + Session.__setattr__(self, key, value) |
| 1441 | + |
| 1442 | + def _check_key_value(self, key, value): |
| 1443 | + cls = self.__class__ |
| 1444 | + attr_def = getattr(cls, key, None) |
| 1445 | + if attr_def is None: |
| 1446 | + raise ValueError("The '{item}' item has not been found in the '{cls}' class declaration. " |
| 1447 | + "Adding a new item after creating an instance of the '{cls}' class is not permitted." |
| 1448 | + .format(item=key, cls=cls.__name__)) |
| 1449 | + if (isinstance(value, (int, float, basestring, np.generic)) and value != attr_def) \ |
| 1450 | + or (isinstance(value, (Axis, Group)) and not value.equals(attr_def)): |
| 1451 | + raise TypeError("The '{key}' item is of kind '{cls_name}' which cannot by modified." |
| 1452 | + .format(key=key, cls_name=attr_def.__class__.__name__)) |
| 1453 | + if type(value) != type(attr_def): |
| 1454 | + raise TypeError("Expected object of type '{attr_cls}'. Got object of type '{value_cls}'." |
| 1455 | + .format(attr_cls=attr_def.__class__.__name__, value_cls=value.__class__.__name__)) |
| 1456 | + if isinstance(attr_def, Array): |
| 1457 | + try: |
| 1458 | + attr_def.axes.check_compatible(value.axes) |
| 1459 | + except ValueError as e: |
| 1460 | + msg = str(e).replace("incompatible axes:", "Incompatible axes for array '{key}':".format(key=key)) |
| 1461 | + raise ValueError(msg) |
| 1462 | + elif isinstance(value, np.ndarray) and value.shape != attr_def.shape: |
| 1463 | + raise ValueError("Incompatible shape for Numpy array '{key}'. " |
| 1464 | + "Expected shape {attr_shape} but got {value_shape}." |
| 1465 | + .format(key=key, attr_shape=attr_def.shape, value_shape=value.shape)) |
| 1466 | + |
| 1467 | + def copy(self): |
| 1468 | + instance = self.__class__() |
| 1469 | + for key, value in self.items(): |
| 1470 | + instance[key] = copy(value) |
| 1471 | + return instance |
| 1472 | + |
| 1473 | + def apply(self, func, *args, **kwargs): |
| 1474 | + kind = kwargs.pop('kind', Array) |
| 1475 | + instance = self.__class__() |
| 1476 | + for key, value in self.items(): |
| 1477 | + instance[key] = func(value, *args, **kwargs) if isinstance(value, kind) else value |
| 1478 | + return instance |
| 1479 | + |
| 1480 | + def _disabled(self, *args, **kwargs): |
| 1481 | + """This method will not work because adding or removing item and modifying axes of declared arrays |
| 1482 | + is not permitted.""" |
| 1483 | + raise ValueError( |
| 1484 | + "Adding or removing item and modifying axes of declared arrays is not permitted.".format( |
| 1485 | + cls=self.__class__.__name__ |
| 1486 | + ) |
| 1487 | + ) |
| 1488 | + |
| 1489 | + __delitem__ = __delattr__ = _disabled |
| 1490 | + update = filter = transpose = compact = _disabled |
| 1491 | + |
| 1492 | + |
1394 | 1493 | def _exclude_private_vars(vars_dict):
|
1395 | 1494 | return {k: v for k, v in vars_dict.items() if not k.startswith('_')}
|
1396 | 1495 |
|
|
0 commit comments