Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2"""
3@file
4@brief Classes which defines column for class @see cl IterRow
5"""
6from inspect import isfunction
7from .iter_exceptions import IterException, NotAllowedOperation
8from .others_types import long, NA, EmptyGroup, GroupByContainer
10from .column_operator import OperatorId, OperatorMul, ColumnOperator, OperatorAdd
11from .column_operator import OperatorDiv, OperatorPow, OperatorSub, OperatorDivN, OperatorMod
12from .column_operator import OperatorEq, OperatorNe, OperatorGe, OperatorLe, OperatorGt, OperatorLt
13from .column_operator import OperatorNot, OperatorOr, OperatorAnd
14from .column_operator import OperatorFunc
16from .column_group_operator import OperatorGroupLen, OperatorGroupAvg
19def private_function_type():
20 "no documentation"
21 pass
24class ColumnType:
26 """
27 Defines a column of a table.
28 """
30 _default_name = "__unk__"
31 _str_type = {int: 'int', long: 'long', NA: 'NA',
32 float: 'float', str: 'str',
33 type(private_function_type): 'func',
34 }
36 def IsColumnType(self):
37 """
38 checks it is a column type which used by an operator
39 """
40 return True
42 @property
43 def ShortName(self):
44 """
45 a short name (tells the column type)
46 """
47 return "any"
49 @property
50 def Name(self):
51 """
52 property
53 """
54 return self._name
56 @property
57 def Type(self):
58 """
59 property
60 """
61 return self._type
63 @property
64 def Parent(self):
65 """
66 property
67 """
68 return self._parent
70 @property
71 def Func(self):
72 """
73 property
74 """
75 return self._func
77 def __init__(
78 self, name, typ, func=None, parent=tuple(), op=None, owner=None):
79 """
80 initiates the column
82 @param name name of the column
83 @param typ type of the data it will contain (can be None)
84 @param func a function, if None, if will be the identity
85 @param parent precise a list of parents if this column was part of a formula
86 @param op operator to apply between the column
87 @param owner table which contains the column (only for further validation)
89 function is a function: ``f: x --> y``
90 """
91 self._name = name
92 self._type = typ
93 self._value = None
94 self._parent = parent
95 self._op = op
96 self._owner = owner
98 if not isinstance(op, ColumnOperator):
99 raise IterException(
100 "op should be a ColumnOperator not: {0}".format(
101 type(op)))
103 if not isinstance(parent, tuple):
104 raise TypeError("we expect a tuple for parameter parent")
105 for p in parent:
106 p.IsColumnType()
108 if typ not in [int, float, long, str, None, NA,
109 type(private_function_type)]:
110 raise IterException(
111 "type should in [int,float,str,long,function]: " +
112 str(typ))
114 if isfunction(func):
115 self._func = func
116 elif func is None:
117 self._func = None
118 else:
119 raise IterException(
120 "type of func should in [int,float,str,long,function]: {0}".format(
121 str(func)))
123 if "_func" not in self.__dict__:
124 raise IterException("this column is missing a function")
126 def __str__(self):
127 """
128 usual
129 """
130 ps = "|".join([_.ShortName for _ in self._parent])
131 if self._value is not None:
132 return "CT({0},<{1}>,op:{2},#P={3})={4}".format(
133 self._name, ColumnType._str_type[self._type], str(self._op), ps, self())
134 else:
135 return "CT({0},<{1}>,op:{2},#P={3}) [no loop started]".format(
136 self._name, ColumnType._str_type[self._type], str(self._op), ps)
138 def __call__(self):
139 """
140 returns func(value)
141 """
142 if self._func is None:
143 if len(self._parent) == 0:
144 if self._value is None:
145 raise ValueError(
146 "method set must be called before for column {0}".format(
147 str(self)))
148 else:
149 res = self._value
150 elif self._op is None:
151 raise ValueError(
152 "there are parents but no operator for column {0}\nParents:\n{1}".format(
153 str(self),
154 self.print_parent()))
155 else:
156 try:
157 res = self._op(self._parent)
158 except TypeError as e:
159 raise IterException(
160 "unable(1) to apply an operator for column op=<{0}>, col={1}, TYPE={2} TYPE_OP={3} TYPE_PARENT={4}".format(
161 str(
162 self._op), str(self), type(self), type(
163 self._op), type(
164 self._parent))) from e
165 except AttributeError as ee:
166 raise IterException(
167 "unable(2) to apply an operator for column op=<{0}>, col={1}, TYPE={2} TYPE_OP={3} TYPE_PARENT={4}".format(
168 str(
169 self._op), str(self), type(self), type(
170 self._op), type(
171 self._parent))) from ee
173 if isinstance(res, ColumnType):
174 raise IterException(
175 "this evaluation(*) cannot return a ColumnType for this column: {0}".format(str(self)))
176 else:
177 # we use a shortcut
178 try:
179 res = self._func(self._value)
180 except TypeError as e:
181 raise IterException(
182 "unable to compute the value of {0}\n{1}".format(
183 str(self),
184 self.print_parent())) from e
186 if isinstance(res, ColumnType):
187 raise IterException(
188 "this evaluation cannot return a ColumnType for this column: {0}".format(
189 str(self)))
191 self.set(res)
192 return res
194 def set(self, value):
195 """
196 Sets a value for this column.
198 @param value anything in ``[int, float, long, str, function]``
199 """
200 if isinstance(value, (int, str, float, long, NA)):
201 self._value = value
202 elif isinstance(value, EmptyGroup):
203 # for an empty group
204 self._value = value
205 elif isinstance(value, list):
206 # for a group
207 self._value = value
208 else:
209 raise IterException(
210 "type of value should be in [int,float,str,long] not {0} for the column {1}".format(
211 type(value),
212 str(self)))
214 def set_none(self):
215 """
216 After a loop on a database, we should put None back as a value.
217 """
218 for p in self._parent:
219 p.set_none()
220 self._value = None
222 def set_name(self, new_name):
223 """
224 Changes the name of the column.
226 @param newname new name
227 """
228 self._name = new_name
230 def set_owner(self, new_owner):
231 """
232 Changes the owner of the column.
234 @param newname new name
235 """
236 self._owner = new_owner
238 def print_parent(self):
239 """
240 Returns a string showing the dependencies of this columns.
242 Example:
243 @code
244 this_columns
245 parent1
246 parent11
247 parent12
248 parent2
249 @endcode
250 """
251 if self._parent is None:
252 return self.__str__()
253 else:
254 rows = [self.__str__()]
255 for p in self._parent:
256 rs = [" " + _ for _ in p.print_parent().split("\n")]
257 rows.extend(rs)
258 return "\n".join(rows)
260 ######################################
261 # functions which create other columns
262 ######################################
264 def copy(self, new_owner):
265 """
266 Returns a copy of this class.
268 @param new_owner new owner
269 @return ColumnType
270 """
271 return ColumnType(self._name, self._type, func=None, parent=(
272 self,), op=OperatorId(), owner=new_owner)
274 #######################################
275 # operations
276 #######################################
278 def __mul__(self, column):
279 """
280 These operators should be able to translate an expression
281 into function operating on the values.
283 @param column a function or an int or a float or a long or a str or a ColumnType
284 @return a ColumnType
285 """
286 if isinstance(column, ColumnType):
287 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
288 self, column), op=OperatorMul())
289 else:
290 return self.__mul__(ColumnConstantType(column))
292 def __add__(self, column):
293 """
294 These operators should be able to translate an expression
295 into function operating on the values.
297 @param column a function or an int or a float or a long or a str or a ColumnType
298 @return a ColumnType
299 """
300 if isinstance(column, ColumnType):
301 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
302 self, column), op=OperatorAdd())
303 else:
304 return self.__add__(ColumnConstantType(column))
306 def __sub__(self, column):
307 """
308 These operators should be able to translate an expression
309 into function operating on the values.
311 @param column a function or an int or a float or a long or a str or a ColumnType
312 @return a ColumnType
313 """
314 if isinstance(column, ColumnType):
315 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
316 self, column), op=OperatorSub())
317 else:
318 return self.__sub__(ColumnConstantType(column))
320 def __truediv__(self, column):
321 """
322 These operators should be able to translate an expression
323 into function operating on the values.
325 @param column a function or an int or a float or a long or a str or a ColumnType
326 @return a ColumnType
327 """
328 if isinstance(column, ColumnType):
329 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
330 self, column), op=OperatorDiv())
331 else:
332 return self.__truediv__(ColumnConstantType(column))
334 def __floordiv__(self, column):
335 """
336 These operators should be able to translate an expression
337 into function operating on the values.
339 @param column a function or an int or a float or a long or a str or a ColumnType
340 @return a ColumnType
341 """
342 if isinstance(column, ColumnType):
343 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
344 self, column), op=OperatorDivN())
345 else:
346 return self.__floordiv__(ColumnConstantType(column))
348 def __mod__(self, column):
349 """
350 these operators should be able to translate an expression
351 into function operating on the values
353 @param column a function or an int or a float or a long or a str or a ColumnType
354 @return a ColumnType
355 """
356 if isinstance(column, ColumnType):
357 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
358 self, column), op=OperatorMod())
359 else:
360 return self.__mod__(ColumnConstantType(column))
362 def __pow__(self, column):
363 """
364 these operators should be able to translate an expression
365 into function operating on the values
367 @param column a function or an int or a float or a long or a str or a ColumnType
368 @return a ColumnType
369 """
370 if isinstance(column, ColumnType):
371 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
372 self, column), op=OperatorPow())
373 else:
374 return self.__pow__(ColumnConstantType(column))
376 #######################################
377 # test
378 #######################################
380 def __eq__(self, column):
381 """
382 these operators should be able to translate an expression
383 into function operating on the values
385 @param column a function or an int or a float or a long or a str or a ColumnType
386 @return a ColumnType
387 """
388 if isinstance(column, ColumnType):
389 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
390 self, column), op=OperatorEq())
391 else:
392 return self.__eq__(ColumnConstantType(column))
394 def __lt__(self, column):
395 """
396 these operators should be able to translate an expression
397 into function operating on the values
399 @param column a function or an int or a float or a long or a str or a ColumnType
400 @return a ColumnType
401 """
402 if isinstance(column, ColumnType):
403 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
404 self, column), op=OperatorLt())
405 else:
406 return self.__lt__(ColumnConstantType(column))
408 def __le__(self, column):
409 """
410 these operators should be able to translate an expression
411 into function operating on the values
413 @param column a function or an int or a float or a long or a str or a ColumnType
414 @return a ColumnType
415 """
416 if isinstance(column, ColumnType):
417 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
418 self, column), op=OperatorLe())
419 else:
420 return self.__le__(ColumnConstantType(column))
422 def __gt__(self, column):
423 """
424 these operators should be able to translate an expression
425 into function operating on the values
427 @param column a function or an int or a float or a long or a str or a ColumnType
428 @return a ColumnType
429 """
430 if isinstance(column, ColumnType):
431 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
432 self, column), op=OperatorGt())
433 else:
434 return self.__gt__(ColumnConstantType(column))
436 def __ge__(self, column):
437 """
438 these operators should be able to translate an expression
439 into function operating on the values
441 @param column a function or an int or a float or a long or a str or a ColumnType
442 @return a ColumnType
443 """
444 if isinstance(column, ColumnType):
445 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
446 self, column), op=OperatorGe())
447 else:
448 return self.__ge__(ColumnConstantType(column))
450 def __ne__(self, column):
451 """
452 these operators should be able to translate an expression
453 into function operating on the values
455 @param column a function or an int or a float or a long or a str or a ColumnType
456 @return a ColumnType
457 """
458 if isinstance(column, ColumnType):
459 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
460 self, column), op=OperatorNe())
461 else:
462 return self.__ne__(ColumnConstantType(column))
464 #######################################
465 # logical
466 #######################################
468 def Not(self):
469 """
470 ``not`` cannot be overriden
471 """
472 return self.__not__()
474 def __not__(self):
475 """
476 these operators should be able to translate an expression
477 into function operating on the values
479 @return a ColumnType
480 """
481 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
482 self,), op=OperatorNot())
484 def Or(self, column):
485 """
486 ``or`` cannot be overriden
487 """
488 return self.__or__(column)
490 def __or__(self, column):
491 """
492 these operators should be able to translate an expression
493 into function operating on the values
495 @param column a function or an int or a float or a long or a str or a ColumnType
496 @return a ColumnType
497 """
498 if isinstance(column, ColumnType):
499 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
500 self, column), op=OperatorOr())
501 else:
502 return self.__or__(ColumnConstantType(column))
504 def And(self, column):
505 """
506 ``and`` cannot be overriden
507 """
508 return self.__and__(column)
510 def __and__(self, column):
511 """
512 these operators should be able to translate an expression
513 into function operating on the values
515 @param column a function or an int or a float or a long or a str or a ColumnType
516 @return a ColumnType
517 """
518 if isinstance(column, ColumnType):
519 return ColumnType(ColumnType._default_name, self._type, func=None, parent=(
520 self, column), op=OperatorAnd())
521 else:
522 return self.__and__(ColumnConstantType(column))
524 #######################################
525 # group function
526 #######################################
528 def len(self):
529 """
530 returns a group columns to count the number of observations
531 """
532 return ColumnGroupType(
533 ColumnType._default_name, int, parent=(self,), op=OperatorGroupLen())
535 def count(self):
536 """
537 returns a group columns to count the number of observations
538 """
539 return self.len()
541 def avg(self):
542 """
543 returns a group columns to return an average
544 """
545 return ColumnGroupType(
546 ColumnType._default_name, float, parent=(self,), op=OperatorGroupAvg())
549class ColumnConstantType(ColumnType):
551 """
552 defines a constant as a column
553 """
555 def __init__(self, const):
556 self._value = const
557 self._func = lambda x, c=self._value: c
558 self._parent = None
559 self._op = None
560 self._type = type(const)
561 self._const = const
562 self._owner = None
564 if isinstance(const, (int, float, long, str, NA)):
565 pass
566 else:
567 raise ValueError(
568 "this value is not a constant: {0}".format(
569 str(const)))
571 @property
572 def ShortName(self):
573 """
574 a short name (tells the column type)
575 """
576 return "cst"
578 def set_none(self):
579 """
580 do nothing (it is a constant)
581 """
582 pass
584 def set(self, value):
585 """
586 do nothing (it is a constant)
588 @param value anything in [int,float,long,str, function ]
589 """
590 pass
592 def __call__(self):
593 """
594 return the constant
595 """
596 return self._const
598 def __str__(self):
599 """
600 usual
601 """
602 return "cst({0})".format(self())
605class ColumnTableType(ColumnType):
607 """
608 defines a table column (not coming from an expression)
609 """
611 def __init__(self, name, typ, owner):
612 """
613 constructor
615 @param name name of the column
616 @param typ type of the column
617 @param owner owner of this column
618 """
619 self._name = name
620 self._func = None
621 self._parent = None
622 self._op = None
623 self._type = typ
624 self._owner = owner
626 @property
627 def ShortName(self):
628 """
629 a short name (tells the column type)
630 """
631 return "col"
633 def set_none(self):
634 """
635 after a loop on a database, we should put None back as a value
636 """
637 self._value = None
639 def __call__(self):
640 """
641 returns the content
642 """
643 if self._value is None:
644 raise IterException(
645 "this column should contain a value: {0}".format(
646 str(self)))
647 return self._value
649 def __str__(self):
650 """
651 usual
652 """
653 return "col({0},{1})".format(
654 self._name, ColumnType._str_type[self._type])
657class ColumnGroupType(ColumnType):
659 """
660 defines a column which processes a group of rows (after a groupby)
661 """
663 def __init__(self, name, typ, parent, op):
664 """
665 constructor
667 @param name name of the column
668 @param typ type of the column
669 @param owner owner of this column
670 @param op operator
671 """
672 self._name = name
673 self._value = None
674 self._parent = parent
675 self._opgr = op
676 self._op = OperatorId()
677 self._type = typ
678 self._owner = None
679 self._func = None
681 @property
682 def ShortName(self):
683 """
684 a short name (tells the column type)
685 """
686 return "group"
688 def set_none(self):
689 """
690 after a loop on a database, we should put None back as a value
691 """
692 self._value = None
694 def __call__(self):
695 """
696 returns the content
697 """
698 if isinstance(self._value, GroupByContainer):
699 try:
700 return self._opgr(self._value)
701 except TypeError as e:
702 raise IterException(
703 "unable(1) to apply an operator for column op=<{0}>, col={1}, TYPE={2} TYPE_OP={3}".format(
704 str(
705 self._op), str(self), type(self), type(
706 self._op))) from e
707 except AttributeError as ee:
708 raise IterException(
709 "unable(2) to apply an operator for column op=<{0}>, col={1}, TYPE={2} TYPE_OP={3}".format(
710 str(
711 self._op), str(self), type(self), type(
712 self._op))) from ee
713 else:
714 return super().__call__()
716 def __str__(self):
717 """
718 usual
719 """
720 return "CGT[{0}]({1})".format(str(self._opgr), self._name)
722 def set(self, value):
723 """
724 sets a value for this column
726 @param value anything in [int,float,long,str, function ]
727 """
728 self._value = value
729 if hasattr(value, "__iter__") and \
730 not isinstance(value, str) and \
731 not isinstance(value, GroupByContainer):
732 raise IterException(
733 "type of value should be GroupByContainer not {0} for the column {1}".format(
734 type(value),
735 str(self)))
737 def __mul__(self, column):
738 """
739 forbidden
740 """
741 raise NotAllowedOperation()
743 def __add__(self, column):
744 """
745 forbidden
746 """
747 raise NotAllowedOperation()
749 def __sub__(self, column):
750 """
751 forbidden
752 """
753 raise NotAllowedOperation()
755 def __truediv__(self, column):
756 """
757 forbidden
758 """
759 raise NotAllowedOperation()
761 def __floordiv__(self, column):
762 """
763 forbidden
764 """
765 raise NotAllowedOperation()
767 def __mod__(self, column):
768 """
769 forbidden
770 """
771 raise NotAllowedOperation()
773 def __pow__(self, column):
774 """
775 forbidden
776 """
777 raise NotAllowedOperation()
780class CFT(ColumnType):
782 """
783 defines a function
784 """
786 def __init__(self, func, *args):
787 """
788 constructor (a function cannot accept keywords)
790 @param func contained function
791 @param args list of @see cl ColumnType
792 """
793 self._name = None
794 self._func = None
795 self._parent = None
796 self._op = OperatorFunc(func)
797 self._type = type(private_function_type)
798 self._owner = None
799 self._thisfunc = func
800 self._parent = tuple(args)
802 for _ in args:
803 if not isinstance(_, ColumnType):
804 raise TypeError(
805 "Expecting a column type, not {}".format(type(_)))
807 @property
808 def ShortName(self):
809 """
810 a short name (tells the column type)
811 """
812 return "func"
814 def set_none(self):
815 """
816 after a loop on a database, we should put None back as a value
817 """
818 self._value = None
820 def __str__(self):
821 """
822 usual
823 """
824 return "func({0},{1})".format(
825 self._name, ColumnType._str_type[self._type])