Skip to content

Commit f9c1cbe

Browse files
committed
fix larray-project#832 : implemented FrozenSession class
1 parent f4e63f1 commit f9c1cbe

File tree

4 files changed

+206
-5
lines changed

4 files changed

+206
-5
lines changed

doc/source/api.rst

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,6 @@ Modifying
790790

791791
Session.add
792792
Session.update
793-
Session.get
794793
Session.apply
795794
Session.transpose
796795

@@ -816,6 +815,74 @@ Load/Save
816815
Session.to_hdf
817816
Session.to_pickle
818817

818+
.. _api-frozensession:
819+
820+
FrozenSession
821+
=============
822+
823+
.. autosummary::
824+
:toctree: _generated/
825+
826+
FrozenSession
827+
828+
Exploring
829+
---------
830+
831+
.. autosummary::
832+
:toctree: _generated/
833+
834+
FrozenSession.names
835+
FrozenSession.keys
836+
FrozenSession.values
837+
FrozenSession.items
838+
FrozenSession.summary
839+
840+
Copying
841+
-------
842+
843+
.. autosummary::
844+
:toctree: _generated/
845+
846+
FrozenSession.copy
847+
848+
Testing
849+
-------
850+
851+
.. autosummary::
852+
:toctree: _generated/
853+
854+
FrozenSession.element_equals
855+
FrozenSession.equals
856+
857+
Selecting
858+
---------
859+
860+
.. autosummary::
861+
:toctree: _generated/
862+
863+
FrozenSession.get
864+
865+
Modifying
866+
---------
867+
868+
.. autosummary::
869+
:toctree: _generated/
870+
871+
FrozenSession.apply
872+
873+
Load/Save
874+
---------
875+
876+
.. autosummary::
877+
:toctree: _generated/
878+
879+
FrozenSession.load
880+
FrozenSession.save
881+
FrozenSession.to_csv
882+
FrozenSession.to_excel
883+
FrozenSession.to_hdf
884+
FrozenSession.to_pickle
885+
819886
.. _api-editor:
820887

821888
Editor

larray/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
full_like, sequence, labels_array, ndtest, asarray, identity, diag,
1010
eye, all, any, sum, prod, cumsum, cumprod, min, max, mean, ptp, var,
1111
std, median, percentile, stack, zip_array_values, zip_array_items)
12-
from larray.core.session import Session, local_arrays, global_arrays, arrays
12+
from larray.core.session import Session, FrozenSession, local_arrays, global_arrays, arrays
1313
from larray.core.constants import nan, inf, pi, e, euler_gamma
1414
from larray.core.metadata import Metadata
1515
from larray.core.ufuncs import wrap_elementwise_array_func, maximum, minimum, where
@@ -58,7 +58,7 @@
5858
'all', 'any', 'sum', 'prod', 'cumsum', 'cumprod', 'min', 'max', 'mean', 'ptp', 'var', 'std',
5959
'median', 'percentile', 'stack', 'zip_array_values', 'zip_array_items',
6060
# session
61-
'Session', 'local_arrays', 'global_arrays', 'arrays',
61+
'Session', 'FrozenSession', 'local_arrays', 'global_arrays', 'arrays',
6262
# constants
6363
'nan', 'inf', 'pi', 'e', 'euler_gamma',
6464
# metadata

larray/core/session.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import re
77
import fnmatch
88
import warnings
9+
from copy import copy
910
from collections import OrderedDict, Iterable
1011

1112
import numpy as np
@@ -91,7 +92,7 @@ def __init__(self, *args, **kwargs):
9192

9293
if len(args) == 1:
9394
a0 = args[0]
94-
if isinstance(a0, str):
95+
if isinstance(a0, basestring):
9596
# assume a0 is a filename
9697
self.load(a0)
9798
else:
@@ -1391,6 +1392,94 @@ def display(k, v, is_metadata=False):
13911392
return res
13921393

13931394

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+
def __init__(self, *args, **kwargs):
1401+
meta = kwargs.pop('meta', None)
1402+
1403+
if len(args) == 1 and isinstance(args[0], basestring):
1404+
filepath, args = args[0], args[1:]
1405+
else:
1406+
filepath = None
1407+
1408+
if args or kwargs:
1409+
raise ValueError("All items of '{cls}' must be defined in the class declaration."
1410+
.format(cls=self.__class__.__name__))
1411+
if meta:
1412+
kwargs['meta'] = meta
1413+
1414+
if filepath:
1415+
args = [filepath]
1416+
else:
1417+
# feed the kwargs dict with all items declared as class attributes
1418+
for key, value in vars(self.__class__).items():
1419+
if not key.startswith('_'):
1420+
kwargs[key] = value
1421+
1422+
Session.__init__(self, *args, **kwargs)
1423+
1424+
def __setitem__(self, key, value):
1425+
self._check_key_value(key, value)
1426+
1427+
# we need to keep the attribute in sync (initially to mask the class attribute)
1428+
object.__setattr__(self, key, value)
1429+
self._objects[key] = value
1430+
1431+
def __setattr__(self, key, value):
1432+
if key != 'meta':
1433+
self._check_key_value(key, value)
1434+
1435+
# update the real attribute
1436+
object.__setattr__(self, key, value)
1437+
# update self._objects
1438+
Session.__setattr__(self, key, value)
1439+
1440+
def _check_key_value(self, key, value):
1441+
cls = self.__class__
1442+
attr_def = getattr(cls, key, None)
1443+
if attr_def is None:
1444+
raise ValueError("The '{item}' item has not been found in the '{cls}' class declaration. "
1445+
"Adding a new item after creating an instance of the '{cls}' class is not permitted."
1446+
.format(item=key, cls=cls.__name__))
1447+
if type(value) != type(attr_def):
1448+
raise TypeError("Expected object of type {attr_cls}. Got object of type {value_cls}."
1449+
.format(attr_cls=attr_def.__class__.__name__, value_cls=value.__class__.__name__))
1450+
if isinstance(attr_def, Array):
1451+
try:
1452+
attr_def.axes.check_compatible(value.axes)
1453+
except ValueError as e:
1454+
msg = str(e).replace("incompatible axes:", "incompatible axes for array '{key}':".format(key=key))
1455+
raise ValueError(msg)
1456+
1457+
def copy(self):
1458+
instance = self.__class__()
1459+
for key, value in self.items():
1460+
instance[key] = copy(value)
1461+
return instance
1462+
1463+
def apply(self, func, *args, **kwargs):
1464+
kind = kwargs.pop('kind', Array)
1465+
instance = self.__class__()
1466+
for key, value in self.items():
1467+
instance[key] = func(value, *args, **kwargs) if isinstance(value, kind) else value
1468+
return instance
1469+
1470+
def _disabled(self, *args, **kwargs):
1471+
"""This method will not work because adding or removing item and modifying axes of declared arrays
1472+
is not permitted."""
1473+
raise ValueError(
1474+
"Adding or removing item and modifying axes of declared arrays is not permitted.".format(
1475+
cls=self.__class__.__name__
1476+
)
1477+
)
1478+
1479+
__delitem__ = __delattr__ = _disabled
1480+
update = filter = transpose = compact = _disabled
1481+
1482+
13941483
def _exclude_private_vars(vars_dict):
13951484
return {k: v for k, v in vars_dict.items() if not k.startswith('_')}
13961485

larray/tests/test_session.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from larray.tests.common import assert_array_nan_equal, inputpath, tmp_path, meta, needs_xlwings
1111
from larray import (Session, Axis, Array, Group, isnan, zeros_like, ndtest, ones_like, ones, full,
12-
local_arrays, global_arrays, arrays)
12+
local_arrays, global_arrays, arrays, FrozenSession)
1313
from larray.util.misc import pickle, PY2
1414

1515

@@ -50,6 +50,27 @@ def session():
5050
('a01', a01), ('ano01', ano01), ('c', c), ('d', d), ('e', e), ('g', g), ('f', f), ('h', h)])
5151

5252

53+
class TestFrozenSession(FrozenSession):
54+
b = b
55+
b024 = b024
56+
a = a
57+
a2 = a2
58+
anonymous = anonymous
59+
a01 = a01
60+
ano01 = ano01
61+
c = c
62+
d = d
63+
e = e
64+
g = g
65+
f = f
66+
h = h
67+
68+
69+
@pytest.fixture()
70+
def frozensession():
71+
return TestFrozenSession()
72+
73+
5374
def test_init_session(meta):
5475
s = Session(b, b024, a, a01, a2=a2, anonymous=anonymous, ano01=ano01, c=c, d=d, e=e, f=f, g=g, h=h)
5576
assert s.names == ['a', 'a01', 'a2', 'ano01', 'anonymous', 'b', 'b024', 'c', 'd', 'e', 'f', 'g', 'h']
@@ -70,6 +91,30 @@ def test_init_session(meta):
7091
assert s.meta == meta
7192

7293

94+
def test_create_frozensession_instance(meta):
95+
fs = TestFrozenSession()
96+
assert fs.names == ['a', 'a01', 'a2', 'ano01', 'anonymous', 'b', 'b024', 'c', 'd', 'e', 'f', 'g', 'h']
97+
98+
# metadata
99+
fs = TestFrozenSession(meta=meta)
100+
assert fs.meta == meta
101+
102+
# load from file
103+
fs = TestFrozenSession(inputpath('test_session.h5'))
104+
assert fs.names == ['a', 'a01', 'a2', 'ano01', 'anonymous', 'b', 'b024', 'e', 'f', 'g', 'h']
105+
106+
# No new items can be passed to the constructor
107+
expected_error_msg = "All items of 'TestFrozenSession' must be defined in the class declaration."
108+
109+
with pytest.raises(ValueError) as e_args:
110+
fs = TestFrozenSession([('i', ndtest((3, 3)))])
111+
assert e_args.value.args[0] == expected_error_msg
112+
113+
with pytest.raises(ValueError) as e_kwargs:
114+
fs = TestFrozenSession(i=ndtest((3, 3)))
115+
assert e_kwargs.value.args[0] == expected_error_msg
116+
117+
73118
def test_getitem(session):
74119
assert session['a'] is a
75120
assert session['a2'] is a2

0 commit comments

Comments
 (0)