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/corev2.py
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
"""TODO"""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# This file is part of Vhatable api.
|
|
6
|
+
#
|
|
7
|
+
# Vhatable api is free software: you can redistribute it and/or modify
|
|
8
|
+
# it under the terms of the GNU General Public License as published by
|
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
# (at your option) any later version.
|
|
11
|
+
#
|
|
12
|
+
# Vhatable api is distributed in the hope that it will be useful,
|
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
# GNU General Public License for more details.
|
|
16
|
+
#
|
|
17
|
+
# You should have received a copy of the GNU General Public License
|
|
18
|
+
# along with Vhatable api. If not, see <http://www.gnu.org/licenses/>.
|
|
19
|
+
#
|
|
20
|
+
# Copyright 2023 Frédéric MARTIN
|
|
21
|
+
#
|
|
22
|
+
# Contributors list :
|
|
23
|
+
#
|
|
24
|
+
# Frédéric MARTIN frederic.martin.fma@gmail.com
|
|
25
|
+
#
|
|
26
|
+
|
|
27
|
+
import logging
|
|
28
|
+
import time
|
|
29
|
+
from collections import OrderedDict
|
|
30
|
+
from functools import wraps
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Time:
|
|
34
|
+
"""TODO"""
|
|
35
|
+
|
|
36
|
+
# pylint: disable=too-few-public-methods
|
|
37
|
+
|
|
38
|
+
def __init__(self, logger_name, label="execution time: {time:.15f}"):
|
|
39
|
+
self.log = logging.getLogger(logger_name)
|
|
40
|
+
self.enabled = False
|
|
41
|
+
self.label = label
|
|
42
|
+
|
|
43
|
+
def __call__(self, original_func):
|
|
44
|
+
@wraps(original_func)
|
|
45
|
+
def time_wrapper(*args, **kwargs):
|
|
46
|
+
self.log.debug("args: %s", args)
|
|
47
|
+
self.log.debug("kwargs: %s", kwargs)
|
|
48
|
+
start = time.time()
|
|
49
|
+
res = original_func(*args, **kwargs)
|
|
50
|
+
end = time.time()
|
|
51
|
+
if len(args) > 0:
|
|
52
|
+
first_arg = args[0]
|
|
53
|
+
self.enabled = getattr(first_arg, "verbose", self.enabled)
|
|
54
|
+
diff = end - start
|
|
55
|
+
msg = self.label.format(time=diff)
|
|
56
|
+
self.log.debug(msg)
|
|
57
|
+
if self.enabled:
|
|
58
|
+
print(msg)
|
|
59
|
+
return res
|
|
60
|
+
|
|
61
|
+
return time_wrapper
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class AutoTime:
|
|
65
|
+
"""TODO"""
|
|
66
|
+
|
|
67
|
+
# pylint: disable=too-few-public-methods
|
|
68
|
+
|
|
69
|
+
def __init__(self, label="execution time: {time:.15f}"):
|
|
70
|
+
self.enabled = False
|
|
71
|
+
self.label = label
|
|
72
|
+
|
|
73
|
+
def __call__(self, original_func):
|
|
74
|
+
@wraps(original_func)
|
|
75
|
+
def time_wrapper(*args, **kwargs):
|
|
76
|
+
logger_name = args[0].__class__.__name__
|
|
77
|
+
logger_name += "." + original_func.__name__
|
|
78
|
+
log = logging.getLogger(logger_name)
|
|
79
|
+
log.debug("args: %s", args)
|
|
80
|
+
log.debug("kwargs: %s", kwargs)
|
|
81
|
+
start = time.time()
|
|
82
|
+
res = original_func(*args, **kwargs)
|
|
83
|
+
end = time.time()
|
|
84
|
+
if len(args) > 0:
|
|
85
|
+
first_arg = args[0]
|
|
86
|
+
self.enabled = getattr(first_arg, "verbose", self.enabled)
|
|
87
|
+
diff = end - start
|
|
88
|
+
msg = self.label.format(time=diff)
|
|
89
|
+
log.debug(msg)
|
|
90
|
+
if self.enabled:
|
|
91
|
+
print(msg)
|
|
92
|
+
return res
|
|
93
|
+
|
|
94
|
+
return time_wrapper
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Unit:
|
|
98
|
+
"""TODO"""
|
|
99
|
+
|
|
100
|
+
def __init__(self, typ="transform", debug=0, identifier=None):
|
|
101
|
+
self.log = logging.getLogger(__name__)
|
|
102
|
+
self._debug = debug
|
|
103
|
+
self._verbose = False
|
|
104
|
+
self.identifier = identifier
|
|
105
|
+
self._attrs = []
|
|
106
|
+
self._gattrs = ["debug", "verbose"]
|
|
107
|
+
self._attrs_ro = []
|
|
108
|
+
self.typ = typ
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def debug(self):
|
|
112
|
+
"""TODO"""
|
|
113
|
+
return self._debug
|
|
114
|
+
|
|
115
|
+
@debug.setter
|
|
116
|
+
def debug(self, debug):
|
|
117
|
+
"""TODO"""
|
|
118
|
+
self._debug = debug
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def verbose(self):
|
|
122
|
+
"""TODO"""
|
|
123
|
+
return self._verbose
|
|
124
|
+
|
|
125
|
+
@verbose.setter
|
|
126
|
+
def verbose(self, verbose):
|
|
127
|
+
"""TODO"""
|
|
128
|
+
self._verbose = verbose
|
|
129
|
+
|
|
130
|
+
def setup(self, from_args):
|
|
131
|
+
"""TODO"""
|
|
132
|
+
for attr in self._gattrs:
|
|
133
|
+
if hasattr(from_args, attr):
|
|
134
|
+
setattr(self, attr, getattr(from_args, attr))
|
|
135
|
+
self.log.debug("setup:gattr: %s : %s", attr, getattr(self, attr))
|
|
136
|
+
for attr in self._attrs:
|
|
137
|
+
if self.identifier:
|
|
138
|
+
attr_with_prefix = self.identifier + "_" + attr
|
|
139
|
+
if hasattr(from_args, attr_with_prefix):
|
|
140
|
+
setattr(self, attr, getattr(from_args, attr_with_prefix))
|
|
141
|
+
else:
|
|
142
|
+
if hasattr(from_args, attr):
|
|
143
|
+
setattr(self, attr, getattr(from_args, attr))
|
|
144
|
+
self.log.debug("setup:attr: %s : %s", attr, getattr(self, attr))
|
|
145
|
+
return self
|
|
146
|
+
|
|
147
|
+
def __str__(self):
|
|
148
|
+
rows = []
|
|
149
|
+
prefix = ""
|
|
150
|
+
if self.identifier:
|
|
151
|
+
prefix = self.identifier + "_"
|
|
152
|
+
|
|
153
|
+
rows.append("Unit: " + self.__class__.__name__ + ":")
|
|
154
|
+
for attr in self._gattrs:
|
|
155
|
+
attr_val = getattr(self, attr)
|
|
156
|
+
rows.append(f" - global: {attr}: {attr_val}")
|
|
157
|
+
for attr in self._attrs:
|
|
158
|
+
attr_val = getattr(self, attr)
|
|
159
|
+
rows.append(f" - {prefix}{attr}: {attr_val}")
|
|
160
|
+
for attr in self._attrs_ro:
|
|
161
|
+
attr_val = getattr(self, attr)
|
|
162
|
+
rows.append(f" - readonly: {prefix}{attr}: {attr_val}")
|
|
163
|
+
rows.append("")
|
|
164
|
+
return "\n".join(rows)
|
|
165
|
+
|
|
166
|
+
def __call__(self, rows):
|
|
167
|
+
raise NotImplementedError()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class AbstractProcessor:
|
|
171
|
+
"""TODO"""
|
|
172
|
+
|
|
173
|
+
# pylint: disable=too-many-instance-attributes
|
|
174
|
+
|
|
175
|
+
def __init__(
|
|
176
|
+
self,
|
|
177
|
+
debug=0,
|
|
178
|
+
verbose=False,
|
|
179
|
+
rows=None,
|
|
180
|
+
column_names=None,
|
|
181
|
+
autokeys=True,
|
|
182
|
+
messages=None,
|
|
183
|
+
identifier=None,
|
|
184
|
+
):
|
|
185
|
+
# pylint: disable=too-many-arguments
|
|
186
|
+
self._attrs = ["debug", "verbose", "column_names"]
|
|
187
|
+
self._attrs_ro = ["identifier", "messages"]
|
|
188
|
+
classname = str(self.__class__.__name__.lower())
|
|
189
|
+
self.log = logging.getLogger("vhatable." + classname)
|
|
190
|
+
self.identifier = identifier
|
|
191
|
+
self.units = OrderedDict()
|
|
192
|
+
self.column_names = []
|
|
193
|
+
self._rows = []
|
|
194
|
+
self._raw_cache = None
|
|
195
|
+
self._post_init = False
|
|
196
|
+
self.messages = {}
|
|
197
|
+
self.messages[self.identifier] = {}
|
|
198
|
+
self.debug = debug
|
|
199
|
+
self.verbose = verbose
|
|
200
|
+
if rows:
|
|
201
|
+
self._rows = rows
|
|
202
|
+
if column_names:
|
|
203
|
+
self.column_names = column_names
|
|
204
|
+
elif autokeys and rows and len(rows) >= 1:
|
|
205
|
+
self.column_names = rows[0].keys()
|
|
206
|
+
self._update_messages(messages)
|
|
207
|
+
|
|
208
|
+
def _update_messages(self, messages):
|
|
209
|
+
if messages and self.identifier in messages:
|
|
210
|
+
messages = messages[self.identifier]
|
|
211
|
+
self.messages[self.identifier].update(messages)
|
|
212
|
+
|
|
213
|
+
def _format_messages(self, key, data):
|
|
214
|
+
return self.messages[self.identifier][key].format(**data)
|
|
215
|
+
|
|
216
|
+
def add_unit(self, unit):
|
|
217
|
+
"""TODO"""
|
|
218
|
+
self.units[unit.identifier] = unit
|
|
219
|
+
|
|
220
|
+
def post_init(self):
|
|
221
|
+
"""TODO"""
|
|
222
|
+
self._post_init = True
|
|
223
|
+
|
|
224
|
+
def __getattr__(self, attr):
|
|
225
|
+
if attr in self.units:
|
|
226
|
+
return self.units[attr]
|
|
227
|
+
classname = self.__class__.__name__
|
|
228
|
+
raise AttributeError(f"'{classname}' object has no attribute '{attr}'")
|
|
229
|
+
|
|
230
|
+
def __str__(self):
|
|
231
|
+
rows = []
|
|
232
|
+
rows.append("Processor - " + self.__class__.__name__ + ":")
|
|
233
|
+
for attr in self._attrs:
|
|
234
|
+
attr_val = getattr(self, attr)
|
|
235
|
+
rows.append(f" - {attr}: {attr_val}")
|
|
236
|
+
for attr in self._attrs_ro:
|
|
237
|
+
attr_val = getattr(self, attr)
|
|
238
|
+
rows.append(f" - readonly: {attr}: {attr_val}")
|
|
239
|
+
rows.append("")
|
|
240
|
+
for unit in self.units.values():
|
|
241
|
+
rows.append(str(unit))
|
|
242
|
+
return "\n".join(rows)
|
|
243
|
+
|
|
244
|
+
def pre_get_raw_hook(self):
|
|
245
|
+
"""When get_raw method is called, this method is called just before
|
|
246
|
+
doing anything. It could be use full to run some extra configuration
|
|
247
|
+
steps.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
def get_raw(self):
|
|
251
|
+
"""TODO"""
|
|
252
|
+
if self._raw_cache is not None:
|
|
253
|
+
if self.debug >= 2:
|
|
254
|
+
self.log.debug("cached rows: %s", self._raw_cache)
|
|
255
|
+
return self._raw_cache
|
|
256
|
+
if not self._post_init:
|
|
257
|
+
raise ValueError("post_init method was not called.")
|
|
258
|
+
self.log.debug("Units: %s", self.units)
|
|
259
|
+
self.log.debug("Rows: %s", self._rows)
|
|
260
|
+
self.pre_get_raw_hook()
|
|
261
|
+
pipeline = self._rows
|
|
262
|
+
for unit in self.units.values():
|
|
263
|
+
if unit.typ == "transform":
|
|
264
|
+
self.log.debug("unit:transform: %s", unit)
|
|
265
|
+
self.log.debug("unit:transform:begin: %s", unit.__class__.__name__)
|
|
266
|
+
pipeline = unit(pipeline)
|
|
267
|
+
self.log.debug("unit:transform:end : %s", unit.__class__.__name__)
|
|
268
|
+
self._raw_cache = list(pipeline)
|
|
269
|
+
if self.debug >= 2:
|
|
270
|
+
self.log.debug("pipeline rows: %s", self._raw_cache)
|
|
271
|
+
return self._raw_cache
|
|
272
|
+
|
|
273
|
+
def load(self, rows):
|
|
274
|
+
"""TODO"""
|
|
275
|
+
self._rows = rows
|
|
276
|
+
return self
|
|
277
|
+
|
|
278
|
+
def setup(self, from_args):
|
|
279
|
+
"""TODO"""
|
|
280
|
+
self.post_init()
|
|
281
|
+
for attr in self._attrs:
|
|
282
|
+
if hasattr(from_args, attr):
|
|
283
|
+
setattr(self, attr, getattr(from_args, attr))
|
|
284
|
+
self.log.debug("setup:attr: %s : %s", attr, getattr(self, attr))
|
|
285
|
+
for unit in self.units.values():
|
|
286
|
+
unit.setup(from_args)
|
|
287
|
+
return self
|
|
288
|
+
|
|
289
|
+
@Time("vhatable.corev2.run")
|
|
290
|
+
def run(self):
|
|
291
|
+
"""TODO"""
|
|
292
|
+
for row in self.get_raw():
|
|
293
|
+
print(row)
|
|
294
|
+
return True
|
vhatable/demo.py
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"""TODO"""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
from vhatable.cellv2 import ComplexCellBuilder
|
|
12
|
+
from vhatable.filters import PartialOr
|
|
13
|
+
from vhatable.filtersv2 import EqualMultipleOr, PartialMultipleAnd
|
|
14
|
+
from vhatable.processors import API, ProcessorBuilder, ProcessorFactory, UpdateProcessor
|
|
15
|
+
from vhatable.units import UpdateRowsUnit
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DemoAPI(API):
|
|
19
|
+
"""TODO"""
|
|
20
|
+
|
|
21
|
+
URL = "https://epguides.frecar.no/show/bigbangtheory/"
|
|
22
|
+
CACHEFILENAME = "/tmp/vhatable-demo--cache-big-bang.json"
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
super().__init__()
|
|
26
|
+
self.data = self._load_json()
|
|
27
|
+
|
|
28
|
+
def _persist(self, data):
|
|
29
|
+
# pylint: disable=unspecified-encoding
|
|
30
|
+
with open(self.CACHEFILENAME, "w") as json_file:
|
|
31
|
+
json.dump(data, json_file)
|
|
32
|
+
|
|
33
|
+
def reset(self):
|
|
34
|
+
"""remove cache file to reset the current state"""
|
|
35
|
+
os.remove(self.CACHEFILENAME)
|
|
36
|
+
|
|
37
|
+
def _load_json(self):
|
|
38
|
+
"""TODO"""
|
|
39
|
+
# pylint: disable=unspecified-encoding
|
|
40
|
+
data = None
|
|
41
|
+
if os.path.isfile(self.CACHEFILENAME):
|
|
42
|
+
with open(self.CACHEFILENAME) as user_file:
|
|
43
|
+
file_contents = user_file.read()
|
|
44
|
+
data = json.loads(file_contents)
|
|
45
|
+
return data
|
|
46
|
+
|
|
47
|
+
data = requests.get(self.URL, timeout=60).json()
|
|
48
|
+
res = {}
|
|
49
|
+
cpt = 0
|
|
50
|
+
for season in data:
|
|
51
|
+
for episode in data[season]:
|
|
52
|
+
cpt += 1
|
|
53
|
+
episode["ID"] = cpt
|
|
54
|
+
episode["notes"] = "TODO"
|
|
55
|
+
res[cpt] = episode
|
|
56
|
+
self._persist(res)
|
|
57
|
+
return res
|
|
58
|
+
|
|
59
|
+
def list(self):
|
|
60
|
+
"""TODO"""
|
|
61
|
+
return self.data.values()
|
|
62
|
+
|
|
63
|
+
def column_names(self):
|
|
64
|
+
"""TODO"""
|
|
65
|
+
return ["ID", "season", "number", "title", "release_date", "show", "notes"]
|
|
66
|
+
|
|
67
|
+
def delete(self, row):
|
|
68
|
+
"""TODO"""
|
|
69
|
+
row_id = str(row["ID"])
|
|
70
|
+
row = self.data.pop(row_id)
|
|
71
|
+
self._persist(self.data)
|
|
72
|
+
return True, row
|
|
73
|
+
|
|
74
|
+
def get(self, row_id):
|
|
75
|
+
row_id = str(row_id)
|
|
76
|
+
row = self.data.get(row_id)
|
|
77
|
+
if row is None:
|
|
78
|
+
raise ValueError("row id not found " + row_id)
|
|
79
|
+
return row
|
|
80
|
+
|
|
81
|
+
def update(self, row):
|
|
82
|
+
row_id = str(row["ID"])
|
|
83
|
+
old_row = self.data.get(row_id)
|
|
84
|
+
if old_row is None:
|
|
85
|
+
raise ValueError("row id not found " + row_id)
|
|
86
|
+
if old_row != row:
|
|
87
|
+
self.data[row_id] = row
|
|
88
|
+
self._persist(self.data)
|
|
89
|
+
return True, row
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
api = DemoAPI()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def newfactory():
|
|
96
|
+
"""TODO"""
|
|
97
|
+
messages = {}
|
|
98
|
+
messages["delete"] = {
|
|
99
|
+
"nothing_to_do": "Nothing to delete.",
|
|
100
|
+
"done": (
|
|
101
|
+
"{prefix}{_position}/{_count}: The episode '{title}' ({ID}) was deleted. ({_time}s)"
|
|
102
|
+
),
|
|
103
|
+
"done_failure": (
|
|
104
|
+
"{prefix}{_position}/{_count}: Failure "
|
|
105
|
+
"The episode '{title}' ({ID}) was not deleted. ({_time}s)"
|
|
106
|
+
),
|
|
107
|
+
}
|
|
108
|
+
messages["update"] = {
|
|
109
|
+
"nothing_to_do": "Nothing to update.",
|
|
110
|
+
"done": (
|
|
111
|
+
"{prefix}{_position}/{_count}: The episode '{title}' ({ID}) was updated. ({_time}s)"
|
|
112
|
+
),
|
|
113
|
+
"done_failure": (
|
|
114
|
+
"{prefix}{_position}/{_count}: Failure "
|
|
115
|
+
"The episode '{title}' ({ID}) was not updated. ({_time}s)"
|
|
116
|
+
),
|
|
117
|
+
}
|
|
118
|
+
messages["count"] = {"count_elt": "Episodes found: {count}"}
|
|
119
|
+
factory = ProcessorFactory()
|
|
120
|
+
factory.set_default_processor_builder(
|
|
121
|
+
ProcessorBuilder(api=api, autoload=True, messages=messages)
|
|
122
|
+
)
|
|
123
|
+
factory.add_processor(
|
|
124
|
+
"update",
|
|
125
|
+
UpdateProcessor,
|
|
126
|
+
builder=ProcessorBuilder(
|
|
127
|
+
api=api,
|
|
128
|
+
autoload=True,
|
|
129
|
+
# we override default updatable columns,
|
|
130
|
+
# only notes is updatable
|
|
131
|
+
units=[UpdateRowsUnit(["notes"])],
|
|
132
|
+
messages=messages,
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
factory.add_custom_cell(
|
|
136
|
+
ComplexCellBuilder(
|
|
137
|
+
"show", fmt="{title} {imdb_id} {epguide_name}", fmtv="{title} {imdb_id} {epguide_name}"
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
factory.set_align_cell("title", "l")
|
|
141
|
+
factory.set_align_cell("show", "c")
|
|
142
|
+
factory.set_header_cell("number", "episode")
|
|
143
|
+
return factory
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def add_list_cmd_options(parser):
|
|
147
|
+
"""Add all default option to the current parser."""
|
|
148
|
+
parser.add_argument(
|
|
149
|
+
"--count",
|
|
150
|
+
default=False,
|
|
151
|
+
action="store_true",
|
|
152
|
+
help="Count the number of the displayed rows",
|
|
153
|
+
dest="count",
|
|
154
|
+
)
|
|
155
|
+
parser.add_argument(
|
|
156
|
+
"--vertical",
|
|
157
|
+
default=False,
|
|
158
|
+
action="store_true",
|
|
159
|
+
help="Use vertical display",
|
|
160
|
+
dest="vertical",
|
|
161
|
+
)
|
|
162
|
+
parser.add_argument(
|
|
163
|
+
"--json", default=False, action="store_true", help="Display rows as json", dest="json"
|
|
164
|
+
)
|
|
165
|
+
parser.add_argument(
|
|
166
|
+
"--json-raw", default=False, action="store_true", help="Display raw json", dest="json_raw"
|
|
167
|
+
)
|
|
168
|
+
parser.add_argument(
|
|
169
|
+
"--csv", default=False, action="store_true", help="Display rows as csv", dest="csv"
|
|
170
|
+
)
|
|
171
|
+
parser.add_argument(
|
|
172
|
+
"--start", default=0, type=int, help="Only display the N first rows", dest="slice_start"
|
|
173
|
+
)
|
|
174
|
+
parser.add_argument(
|
|
175
|
+
"--end", default=0, type=int, help="Only display the N last rows", dest="slice_end"
|
|
176
|
+
)
|
|
177
|
+
parser.add_argument(
|
|
178
|
+
"--limit",
|
|
179
|
+
default=0,
|
|
180
|
+
type=int,
|
|
181
|
+
help="limit the number of rows to display",
|
|
182
|
+
dest="slice_limit",
|
|
183
|
+
)
|
|
184
|
+
parser.add_argument("--sort-by", help="sort row by column", dest="sort_sort_by")
|
|
185
|
+
parser.add_argument(
|
|
186
|
+
"--sort-reversed",
|
|
187
|
+
help="Reverse order while sorting",
|
|
188
|
+
default=False,
|
|
189
|
+
action="store_true",
|
|
190
|
+
dest="sort_reverse",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def add_list_command(main_subparsers):
|
|
195
|
+
"""TODO"""
|
|
196
|
+
|
|
197
|
+
def command(args):
|
|
198
|
+
"""This command will remove an episode from the cache."""
|
|
199
|
+
factory = newfactory()
|
|
200
|
+
factory.add_filters(PartialMultipleAnd("title", args.titles))
|
|
201
|
+
factory.add_filters(PartialOr("release_date", args.release_dates))
|
|
202
|
+
factory.add_filters(PartialOr("season", args.seasons))
|
|
203
|
+
factory.add_filters(PartialOr("number", args.episodes))
|
|
204
|
+
factory.add_filters(EqualMultipleOr("ID", args.ids))
|
|
205
|
+
processor = factory.create(args)
|
|
206
|
+
print(processor)
|
|
207
|
+
processor.run()
|
|
208
|
+
print()
|
|
209
|
+
|
|
210
|
+
parser = main_subparsers.add_parser("list")
|
|
211
|
+
parser.set_defaults(func=command)
|
|
212
|
+
add_list_cmd_options(parser)
|
|
213
|
+
|
|
214
|
+
parser.add_argument("titles", nargs="*", help="Filter by title")
|
|
215
|
+
|
|
216
|
+
parser.add_argument(
|
|
217
|
+
"--release-date", action="append", help="Filter by release date", dest="release_dates"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
parser.add_argument("--season", action="append", help="Filter by season", dest="seasons")
|
|
221
|
+
|
|
222
|
+
parser.add_argument("--id", action="append", type=int, help="Filter by id", dest="ids")
|
|
223
|
+
|
|
224
|
+
parser.add_argument("--episode", action="append", help="Filter by episode", dest="episodes")
|
|
225
|
+
|
|
226
|
+
parser.add_argument(
|
|
227
|
+
"--delete", default=False, action="store_true", help="Delete selected rows", dest="delete"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
parser.add_argument(
|
|
231
|
+
"--update", default=False, action="store_true", help="update selected rows", dest="update"
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
parser.add_argument(
|
|
235
|
+
"--notes", default=None, action="store", help="update notes column", dest="update_notes"
|
|
236
|
+
)
|
|
237
|
+
parser.add_argument(
|
|
238
|
+
"--update-title",
|
|
239
|
+
default=None,
|
|
240
|
+
action="store",
|
|
241
|
+
help="update title column",
|
|
242
|
+
dest="update_title",
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def add_delete_command(main_subparsers):
|
|
247
|
+
"""TODO"""
|
|
248
|
+
|
|
249
|
+
def command(args):
|
|
250
|
+
"""This command will remove some episode from the list.
|
|
251
|
+
This implementation is not the best since we load the entire of
|
|
252
|
+
episodes and we filter them.
|
|
253
|
+
Another solution: We can use the list of IDs to retrieve episode one by
|
|
254
|
+
one and then build a list of them. No more filtering.
|
|
255
|
+
A more performant implementation.
|
|
256
|
+
"""
|
|
257
|
+
factory = newfactory()
|
|
258
|
+
factory.default_processor_identifier = "delete"
|
|
259
|
+
factory.add_filters(EqualMultipleOr("ID", args.ids))
|
|
260
|
+
processor = factory.create(args)
|
|
261
|
+
print(processor)
|
|
262
|
+
processor.run()
|
|
263
|
+
print()
|
|
264
|
+
|
|
265
|
+
parser = main_subparsers.add_parser("delete")
|
|
266
|
+
parser.set_defaults(func=command)
|
|
267
|
+
parser.add_argument("ids", nargs="+", type=int, help="Delete episode by id")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def add_delete_commandv2(main_subparsers):
|
|
271
|
+
"""TODO"""
|
|
272
|
+
|
|
273
|
+
def command(args):
|
|
274
|
+
"""This command will remove some episode from the list.
|
|
275
|
+
We retrieve episode one by one and then build a list
|
|
276
|
+
of them. No more filtering.
|
|
277
|
+
A more performant implementation.
|
|
278
|
+
"""
|
|
279
|
+
rows = []
|
|
280
|
+
for row_id in args.ids:
|
|
281
|
+
rows.append(api.get(row_id))
|
|
282
|
+
|
|
283
|
+
factory = newfactory()
|
|
284
|
+
factory.default_processor_identifier = "delete"
|
|
285
|
+
processor = factory.create(args)
|
|
286
|
+
processor.load(rows)
|
|
287
|
+
print(processor)
|
|
288
|
+
processor.run()
|
|
289
|
+
print()
|
|
290
|
+
|
|
291
|
+
parser = main_subparsers.add_parser("deletev2")
|
|
292
|
+
parser.set_defaults(func=command)
|
|
293
|
+
parser.add_argument("ids", nargs="+", type=int, help="Delete episode by id")
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def add_reset_command(main_subparsers):
|
|
297
|
+
"""TODO"""
|
|
298
|
+
|
|
299
|
+
def command(args):
|
|
300
|
+
"""Reset the local cache"""
|
|
301
|
+
# pylint: disable=unused-argument
|
|
302
|
+
api.reset()
|
|
303
|
+
|
|
304
|
+
parser = main_subparsers.add_parser("reset")
|
|
305
|
+
parser.set_defaults(func=command)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def add_update_command(main_subparsers):
|
|
309
|
+
"""TODO"""
|
|
310
|
+
|
|
311
|
+
def command(args):
|
|
312
|
+
"""This command will remove some episode from the list.
|
|
313
|
+
We retrieve episode one by one and then build a list
|
|
314
|
+
of them. No more filtering.
|
|
315
|
+
A more performant implementation.
|
|
316
|
+
"""
|
|
317
|
+
rows = []
|
|
318
|
+
for row_id in args.ids:
|
|
319
|
+
rows.append(api.get(row_id))
|
|
320
|
+
|
|
321
|
+
factory = newfactory()
|
|
322
|
+
factory.default_processor_identifier = "update"
|
|
323
|
+
processor = factory.create(args)
|
|
324
|
+
processor.load(rows)
|
|
325
|
+
print(processor)
|
|
326
|
+
processor.run()
|
|
327
|
+
print()
|
|
328
|
+
|
|
329
|
+
parser = main_subparsers.add_parser("update")
|
|
330
|
+
parser.set_defaults(func=command)
|
|
331
|
+
parser.add_argument("ids", nargs="+", type=int, help="Update episode by id")
|
|
332
|
+
parser.add_argument(
|
|
333
|
+
"--notes", default=None, action="store", help="update notes column", dest="update_notes"
|
|
334
|
+
)
|
|
335
|
+
parser.add_argument(
|
|
336
|
+
"--title", default=None, action="store", help="update title column", dest="update_title"
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def main():
|
|
341
|
+
"""Main entrypoint of this sample program."""
|
|
342
|
+
log = logging.getLogger()
|
|
343
|
+
log.setLevel(logging.INFO)
|
|
344
|
+
stdout = logging.StreamHandler(sys.stdout)
|
|
345
|
+
fmt = "%(asctime)s:%(name)s:%(funcName)s:%(levelname)s:%(message)s"
|
|
346
|
+
formatter = logging.Formatter(fmt)
|
|
347
|
+
stdout.setFormatter(formatter)
|
|
348
|
+
log.addHandler(stdout)
|
|
349
|
+
|
|
350
|
+
main_parser = argparse.ArgumentParser(description="Demo cli")
|
|
351
|
+
main_parser.add_argument("-d", "--debug", type=int, default=0, help="debug level", dest="debug")
|
|
352
|
+
main_subparsers = main_parser.add_subparsers()
|
|
353
|
+
|
|
354
|
+
add_list_command(main_subparsers)
|
|
355
|
+
add_delete_command(main_subparsers)
|
|
356
|
+
add_delete_commandv2(main_subparsers)
|
|
357
|
+
add_reset_command(main_subparsers)
|
|
358
|
+
add_update_command(main_subparsers)
|
|
359
|
+
|
|
360
|
+
args = main_parser.parse_args()
|
|
361
|
+
if args.debug >= 1:
|
|
362
|
+
log.setLevel(logging.DEBUG)
|
|
363
|
+
if "func" in args:
|
|
364
|
+
print("Demo: ", args.func.__doc__ + "\n")
|
|
365
|
+
sys.exit(args.func(args))
|
|
366
|
+
else:
|
|
367
|
+
print("ERROR: Missing command. see -h")
|
|
368
|
+
sys.exit(2)
|