vhatable 1.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vhatable/__init__.py +27 -0
- vhatable/cell.py +344 -0
- vhatable/cellv2.py +227 -0
- vhatable/core.py +784 -0
- vhatable/corev2.py +294 -0
- vhatable/demo.py +368 -0
- vhatable/filters.py +209 -0
- vhatable/filtersv2.py +121 -0
- vhatable/processors.py +635 -0
- vhatable/sample.py +666 -0
- vhatable/samplev2.py +212 -0
- vhatable/units.py +430 -0
- vhatable-1.2.1.dist-info/METADATA +66 -0
- vhatable-1.2.1.dist-info/RECORD +16 -0
- vhatable-1.2.1.dist-info/WHEEL +4 -0
- vhatable-1.2.1.dist-info/licenses/LICENSE.txt +674 -0
vhatable/filters.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
"""This module contains only filters, they are design to be apply to a row
|
|
3
|
+
of a Vertical or Horizotal table."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# This file is part of Vhatable cli.
|
|
7
|
+
#
|
|
8
|
+
# Vhatable cli is free software: you can redistribute it and/or modify
|
|
9
|
+
# it under the terms of the GNU General Public License as published by
|
|
10
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# Vhatable cli is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU General Public License
|
|
19
|
+
# along with Vhatable cli. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
#
|
|
21
|
+
# Copyright 2023 Frédéric MARTIN
|
|
22
|
+
#
|
|
23
|
+
# Contributors list :
|
|
24
|
+
#
|
|
25
|
+
# Frédéric MARTIN frederic.martin.fma@gmail.com
|
|
26
|
+
#
|
|
27
|
+
|
|
28
|
+
import datetime
|
|
29
|
+
import logging
|
|
30
|
+
import re
|
|
31
|
+
|
|
32
|
+
from .cell import BaseCell
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Filter:
|
|
36
|
+
"""TODO"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, prop, values=None):
|
|
39
|
+
"""prop name and value(s)"""
|
|
40
|
+
self.prop = prop
|
|
41
|
+
self.values = values
|
|
42
|
+
classname = self.__class__.__name__.lower()
|
|
43
|
+
self.log = logging.getLogger("vhatablecli.filters." + classname)
|
|
44
|
+
|
|
45
|
+
def is_enable(self):
|
|
46
|
+
"""Return true is the filter is enabled and should be applied."""
|
|
47
|
+
if self.values is None:
|
|
48
|
+
return False
|
|
49
|
+
if isinstance(self.values, list):
|
|
50
|
+
# pylint: disable=len-as-condition
|
|
51
|
+
if len(self.values) == 0:
|
|
52
|
+
return False
|
|
53
|
+
return any(i is not None for i in self.values)
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
def get_val(self, row):
|
|
57
|
+
"""return values from the current row that need to be tested again the
|
|
58
|
+
current filter."""
|
|
59
|
+
if isinstance(self.prop, list):
|
|
60
|
+
vals = {}
|
|
61
|
+
for prop in self.prop:
|
|
62
|
+
vals[prop] = self._get_val(row, prop)
|
|
63
|
+
return vals
|
|
64
|
+
return self._get_val(row, self.prop)
|
|
65
|
+
|
|
66
|
+
def _get_val(self, row, prop):
|
|
67
|
+
val = row.get(prop)
|
|
68
|
+
if val is None:
|
|
69
|
+
raise ValueError("missing key : " + self.prop)
|
|
70
|
+
# self.log.debug("type: %s", type(val))
|
|
71
|
+
# self.log.debug("value: '%s'", val)
|
|
72
|
+
return val
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class PartialOr(Filter):
|
|
76
|
+
"""Get the current property into the current row, and match the result with
|
|
77
|
+
a list of values"""
|
|
78
|
+
|
|
79
|
+
def __init__(self, prop, values, ignorecase=False, match_raw=False):
|
|
80
|
+
super().__init__(prop, values)
|
|
81
|
+
self.match_raw = match_raw
|
|
82
|
+
if self.is_enable():
|
|
83
|
+
if not isinstance(values, list):
|
|
84
|
+
raise ValueError("input values should be a list")
|
|
85
|
+
self.log.debug("values: %s", self.values)
|
|
86
|
+
pattern = r"^.*(" + "|".join(self.values) + ").*$"
|
|
87
|
+
self.log.debug("raw pattern: %s", pattern)
|
|
88
|
+
if ignorecase:
|
|
89
|
+
self.regex = re.compile(pattern, re.IGNORECASE)
|
|
90
|
+
else:
|
|
91
|
+
self.regex = re.compile(pattern)
|
|
92
|
+
|
|
93
|
+
def __str__(self):
|
|
94
|
+
return "PartialOr: " + str(self.values)
|
|
95
|
+
|
|
96
|
+
def __call__(self, row):
|
|
97
|
+
# pylint: disable=too-many-return-statements
|
|
98
|
+
if not self.is_enable():
|
|
99
|
+
return True
|
|
100
|
+
vals = self.get_val(row)
|
|
101
|
+
if isinstance(vals, dict):
|
|
102
|
+
for val in list(vals.values()):
|
|
103
|
+
return self.__match(val)
|
|
104
|
+
else:
|
|
105
|
+
return self.__match(vals)
|
|
106
|
+
|
|
107
|
+
def __match(self, val):
|
|
108
|
+
if isinstance(val, str):
|
|
109
|
+
if self.regex.match(val):
|
|
110
|
+
return True
|
|
111
|
+
elif isinstance(val, BaseCell):
|
|
112
|
+
self.log.debug("cell type: %s", type(val))
|
|
113
|
+
if self.match_raw:
|
|
114
|
+
if self.regex.match(val.value):
|
|
115
|
+
return True
|
|
116
|
+
else:
|
|
117
|
+
if val.match(self.regex):
|
|
118
|
+
return True
|
|
119
|
+
else:
|
|
120
|
+
if self.regex.match(val):
|
|
121
|
+
return True
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class PartialMultipleAnd(Filter):
|
|
126
|
+
"""Get the current property into the current row, and match the result with
|
|
127
|
+
a list of values"""
|
|
128
|
+
|
|
129
|
+
def __init__(self, propvalues, ignorecase=False):
|
|
130
|
+
super().__init__(list(propvalues.keys()), list(propvalues.values()))
|
|
131
|
+
self.regex = {}
|
|
132
|
+
self.propvalues = propvalues
|
|
133
|
+
if self.is_enable():
|
|
134
|
+
for key, value in list(propvalues.items()):
|
|
135
|
+
self.regex[key] = None
|
|
136
|
+
if value is not None:
|
|
137
|
+
pattern = r"^.*" + value + ".*$"
|
|
138
|
+
if ignorecase:
|
|
139
|
+
self.regex[key] = re.compile(pattern, re.IGNORECASE)
|
|
140
|
+
else:
|
|
141
|
+
self.regex[key] = re.compile(pattern)
|
|
142
|
+
|
|
143
|
+
def __call__(self, row):
|
|
144
|
+
if not self.is_enable():
|
|
145
|
+
return True
|
|
146
|
+
vals = self.get_val(row)
|
|
147
|
+
for key, val in list(vals.items()):
|
|
148
|
+
if self.regex[key] and not self.regex[key].match(val):
|
|
149
|
+
return False
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class PartialDate(Filter):
|
|
154
|
+
"""Get the current property into the current row, and match the result with
|
|
155
|
+
a list of values.
|
|
156
|
+
This filter will be activated/enabled if 'value' is not None.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
def __init__(self, prop, value):
|
|
160
|
+
super().__init__(prop, value)
|
|
161
|
+
if self.is_enable():
|
|
162
|
+
pattern = r"^.*" + value + ".*$"
|
|
163
|
+
self.regex = re.compile(pattern)
|
|
164
|
+
|
|
165
|
+
def __call__(self, row):
|
|
166
|
+
if not self.is_enable():
|
|
167
|
+
return True
|
|
168
|
+
val = self.get_val(row)
|
|
169
|
+
if isinstance(val, dict):
|
|
170
|
+
# no idea how it is possible
|
|
171
|
+
for val2 in list(val.values()):
|
|
172
|
+
if self.regex.match(val2):
|
|
173
|
+
return True
|
|
174
|
+
else:
|
|
175
|
+
formatt = "{da:%Y-%m-%d %H:%M:%S}"
|
|
176
|
+
val = formatt.format(da=datetime.datetime.fromtimestamp(val / 1000))
|
|
177
|
+
self.log.debug("type of value: %s", type(val))
|
|
178
|
+
self.log.debug("value: %s", val)
|
|
179
|
+
if self.regex.match(val):
|
|
180
|
+
return True
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class Equal(Filter):
|
|
185
|
+
"""Get the current property into the current row, and test equality.
|
|
186
|
+
Only one value is possible"""
|
|
187
|
+
|
|
188
|
+
def __call__(self, row):
|
|
189
|
+
if not self.is_enable():
|
|
190
|
+
return True
|
|
191
|
+
val = self.get_val(row)
|
|
192
|
+
return val == self.values
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class Equals(Filter):
|
|
196
|
+
"""Deprecated. Use EqualMultipleOr.
|
|
197
|
+
Get the current property into the current row, and test equality.
|
|
198
|
+
A list of values will be tested"""
|
|
199
|
+
|
|
200
|
+
def __call__(self, row):
|
|
201
|
+
if not self.is_enable():
|
|
202
|
+
return True
|
|
203
|
+
val = self.get_val(row)
|
|
204
|
+
self.log.debug("type of value: %s", type(val))
|
|
205
|
+
self.log.debug("value: %s", val)
|
|
206
|
+
for v in self.values:
|
|
207
|
+
self.log.debug("comp: %s", v)
|
|
208
|
+
self.log.debug("comp type: %s", type(v))
|
|
209
|
+
return val in self.values
|
vhatable/filtersv2.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
"""This module contains only filters, they are design to be apply to a row
|
|
3
|
+
of a Vertical or Horizotal table."""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# This file is part of Vhatable cli.
|
|
7
|
+
#
|
|
8
|
+
# Vhatable cli is free software: you can redistribute it and/or modify
|
|
9
|
+
# it under the terms of the GNU General Public License as published by
|
|
10
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# Vhatable cli is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU General Public License
|
|
19
|
+
# along with Vhatable cli. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
#
|
|
21
|
+
# Copyright 2023 Frédéric MARTIN
|
|
22
|
+
#
|
|
23
|
+
# Contributors list :
|
|
24
|
+
#
|
|
25
|
+
# Frédéric MARTIN frederic.martin.fma@gmail.com
|
|
26
|
+
#
|
|
27
|
+
|
|
28
|
+
from datetime import datetime, timedelta
|
|
29
|
+
|
|
30
|
+
from .filters import Equals, Filter
|
|
31
|
+
|
|
32
|
+
# Equal | Partial | Greater | Older | Newer
|
|
33
|
+
# Single value | Multiple Value
|
|
34
|
+
# And | Or
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class EqualMultipleOr(Equals):
|
|
38
|
+
"""
|
|
39
|
+
Get the current property into the current row, and test equality with any
|
|
40
|
+
provided values."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class PartialMultipleAnd(Filter):
|
|
44
|
+
"""Get the current property into the current row, and match the result with
|
|
45
|
+
ALL values of the list"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, prop, values):
|
|
48
|
+
super().__init__(prop, values)
|
|
49
|
+
if self.is_enable():
|
|
50
|
+
if not isinstance(values, list):
|
|
51
|
+
raise ValueError("input values should be a list")
|
|
52
|
+
self.log.debug("values: %s", self.values)
|
|
53
|
+
|
|
54
|
+
def __str__(self):
|
|
55
|
+
return "PartialMultipleAnd: " + str(self.values)
|
|
56
|
+
|
|
57
|
+
def __call__(self, row):
|
|
58
|
+
if not self.is_enable():
|
|
59
|
+
return True
|
|
60
|
+
# comp must be a cell
|
|
61
|
+
comp = str(self.get_val(row))
|
|
62
|
+
lower_comp = comp.lower()
|
|
63
|
+
for val in self.values:
|
|
64
|
+
lower_val = val.lower()
|
|
65
|
+
if val == lower_val:
|
|
66
|
+
if lower_val not in lower_comp:
|
|
67
|
+
return False
|
|
68
|
+
else:
|
|
69
|
+
if val not in comp:
|
|
70
|
+
return False
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class OlderThanFilter(Filter):
|
|
75
|
+
"""Get the property into the row and return only row older than N days.
|
|
76
|
+
This filter can only be used with DateCells, inner value should be a Long.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(self, prop, nb_days):
|
|
80
|
+
super().__init__(prop, nb_days)
|
|
81
|
+
if self.is_enable():
|
|
82
|
+
date = self._get_today() - timedelta(days=nb_days)
|
|
83
|
+
self.timestamp_to_compare = date.timestamp()
|
|
84
|
+
|
|
85
|
+
def _get_today(self):
|
|
86
|
+
return datetime.today()
|
|
87
|
+
|
|
88
|
+
def __call__(self, row):
|
|
89
|
+
if not self.is_enable():
|
|
90
|
+
return True
|
|
91
|
+
cell = self.get_val(row)
|
|
92
|
+
value = cell.value
|
|
93
|
+
if cell.millisecond:
|
|
94
|
+
value = value / 1000
|
|
95
|
+
# self.log.debug("cell: %s, %s", cell, value)
|
|
96
|
+
return self.timestamp_to_compare > value
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class NewerThanFilter(Filter):
|
|
100
|
+
"""Get the property into the row and return only row newer than N days.
|
|
101
|
+
This filter can only be used with DateCells, inner value should be a Long.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(self, prop, nb_days):
|
|
105
|
+
super().__init__(prop, nb_days)
|
|
106
|
+
if self.is_enable():
|
|
107
|
+
date = self._get_today() - timedelta(days=nb_days)
|
|
108
|
+
self.timestamp_to_compare = date.timestamp()
|
|
109
|
+
|
|
110
|
+
def _get_today(self):
|
|
111
|
+
return datetime.today()
|
|
112
|
+
|
|
113
|
+
def __call__(self, row):
|
|
114
|
+
if not self.is_enable():
|
|
115
|
+
return True
|
|
116
|
+
cell = self.get_val(row)
|
|
117
|
+
value = cell.value
|
|
118
|
+
if cell.millisecond:
|
|
119
|
+
value = value / 1000
|
|
120
|
+
# self.log.debug("cell: %s, %s", cell, value)
|
|
121
|
+
return self.timestamp_to_compare < value
|