zeed-movs-viewer 0.0.0__py3-none-any.whl → 0.0.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.
movsviewer/automation.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from contextlib import contextmanager
2
- from logging import info
3
- from os import listdir
2
+ from logging import getLogger
4
3
  from pathlib import Path
5
4
  from tempfile import TemporaryDirectory
6
5
  from typing import TYPE_CHECKING
@@ -34,6 +33,8 @@ if TYPE_CHECKING:
34
33
 
35
34
  from selenium.webdriver.remote.webdriver import WebDriver
36
35
 
36
+ logger = getLogger(__name__)
37
+
37
38
 
38
39
  def get_options(dtemp: str) -> Options:
39
40
  options = Options()
@@ -123,9 +124,11 @@ def get_movimenti(
123
124
  _c(wait, '#select>option[value=TESTO]')
124
125
  Select(_p(wait, '#select')).select_by_value('TESTO')
125
126
 
126
- info('prima: %s', listdir(dtemp))
127
+ pdtemp = Path(dtemp)
128
+
129
+ logger.info('prima: %s', list(pdtemp.iterdir()))
127
130
  _c(wait, '#downloadApi').click()
128
131
  _i(wait, '.waiting')
129
- info('dopo: %s', listdir(dtemp))
132
+ logger.info('dopo: %s', list(pdtemp.iterdir()))
130
133
 
131
- yield Path(dtemp) / 'ListaMovimenti.txt'
134
+ yield pdtemp / 'ListaMovimenti.txt'
movsviewer/chartview.py CHANGED
@@ -15,12 +15,17 @@ from typing import cast
15
15
  from typing import override
16
16
 
17
17
  from guilib.chartwidget.chartwidget import ChartWidget
18
+ from guilib.chartwidget.model import Column
19
+ from guilib.chartwidget.model import ColumnHeader
20
+ from guilib.chartwidget.model import Info
21
+ from guilib.chartwidget.model import InfoProto
18
22
  from guilib.chartwidget.modelgui import SeriesModel
19
23
  from guilib.chartwidget.modelgui import SeriesModelUnit
20
24
  from guilib.chartwidget.viewmodel import SortFilterViewModel
21
25
  from guilib.dates.converters import date2days
22
26
  from guilib.dates.converters import date2QDateTime
23
27
  from movslib.model import ZERO
28
+ from movslib.reader import read
24
29
  from PySide6.QtCharts import QBarCategoryAxis
25
30
  from PySide6.QtCharts import QBarSeries
26
31
  from PySide6.QtCharts import QBarSet
@@ -30,15 +35,10 @@ from PySide6.QtCharts import QLineSeries
30
35
  from PySide6.QtCharts import QValueAxis
31
36
  from PySide6.QtCore import Qt
32
37
 
33
- from movsviewer.reader import read
34
-
35
38
  if TYPE_CHECKING:
36
39
  from collections.abc import Iterable
37
40
  from collections.abc import Sequence
38
41
 
39
- from guilib.chartwidget.model import Column
40
- from guilib.chartwidget.model import ColumnHeader
41
- from guilib.chartwidget.model import Info
42
42
  from movslib.model import Row
43
43
  from PySide6.QtWidgets import QGraphicsSceneMouseEvent
44
44
  from PySide6.QtWidgets import QGraphicsSceneWheelEvent
@@ -226,43 +226,12 @@ class Chart(QChart):
226
226
  def mouseMoveEvent(self, event: 'QGraphicsSceneMouseEvent') -> None:
227
227
  super().mouseMoveEvent(event)
228
228
 
229
- x_curr, y_curr = cast(tuple[float, float], event.pos().toTuple())
230
- x_prev, y_prev = cast(tuple[float, float], event.lastPos().toTuple())
229
+ x_curr, y_curr = cast('tuple[float, float]', event.pos().toTuple())
230
+ x_prev, y_prev = cast('tuple[float, float]', event.lastPos().toTuple())
231
231
  self.scroll(x_prev - x_curr, y_curr - y_prev)
232
232
 
233
233
 
234
- class CH:
235
- def __init__(self, name: str) -> None:
236
- self.name = name
237
-
238
- @override
239
- def __eq__(self, other: object) -> bool:
240
- if not isinstance(other, CH):
241
- return NotImplemented
242
- return self.name == other.name
243
-
244
-
245
- class C:
246
- def __init__(
247
- self, header: 'ColumnHeader', howmuch: 'Decimal | None'
248
- ) -> None:
249
- self.header = header
250
- self.howmuch = howmuch
251
-
252
-
253
- class I: # noqa: E742
254
- def __init__(self, when: 'date', columns: 'Sequence[Column]') -> None:
255
- self.when = when
256
- self.columns = columns
257
-
258
- def howmuch(self, column_header: 'ColumnHeader') -> 'Decimal | None':
259
- for column in self.columns:
260
- if column.header == column_header:
261
- return column.howmuch
262
- return None
263
-
264
-
265
- MONEY_HEADER = CH('money')
234
+ MONEY_HEADER = ColumnHeader('money')
266
235
 
267
236
 
268
237
  @dataclass
@@ -301,8 +270,7 @@ class SMFMoney(SMFHelper):
301
270
  howmuch_f: float,
302
271
  ) -> None:
303
272
  self.line_series.append(when_d, 0)
304
- # TODO @me: fix hover to deal with a variable number of items in series
305
- # TOD000
273
+ # TODO: fix hover to deal with a variable number of items in series
306
274
  self.line_series.append(when_d, howmuch_f)
307
275
 
308
276
  self.shared.y_min = min(howmuch, self.shared.y_min)
@@ -391,7 +359,7 @@ class SMFMoneyByYear(SMFHelper):
391
359
  self.shared.y_max = Decimal(self.acc)
392
360
 
393
361
 
394
- def series_model_factory(infos: 'Sequence[Info]') -> 'SeriesModel':
362
+ def series_model_factory(infos: 'Sequence[InfoProto]') -> 'SeriesModel':
395
363
  """Extract money from info; accumulate and step; group by month / year."""
396
364
  shared = SMFShared(
397
365
  x_min=date.max,
@@ -449,7 +417,7 @@ class ChartWidgetWrapper(ChartWidget):
449
417
  _, data = read(self.data_path)
450
418
  # convert data to infos
451
419
  infos = [
452
- I(row.date, [C(MONEY_HEADER, row.money)])
420
+ Info(row.date, [Column(MONEY_HEADER, row.money)])
453
421
  for row in sorted(data, key=lambda row: row.date)
454
422
  ]
455
423
  self.model.update(infos)
movsviewer/constants.py CHANGED
@@ -12,5 +12,5 @@ SETTINGSUI_UI_PATH: Final = _resource('settingsui.ui')
12
12
  GECKODRIVER_PATH: Final = _resource('geckodriver.exe')
13
13
 
14
14
  SETTINGS_USERNAME: Final = 'username'
15
- SETTINGS_PASSWORD: Final = 'password'
15
+ SETTINGS_PASSWORD: Final = 'password' # noqa:S105
16
16
  SETTINGS_DATA_PATHS: Final = 'dataPaths'
movsviewer/mainui.py CHANGED
@@ -4,7 +4,11 @@ from typing import TYPE_CHECKING
4
4
  from typing import Final
5
5
  from typing import cast
6
6
 
7
+ from guilib.chartwidget.viewmodel import (
8
+ SortFilterViewModel as SortFilterViewModel2,
9
+ )
7
10
  from guilib.multitabs.widget import MultiTabs
11
+ from guilib.qwtplot.plot import Plot
8
12
  from guilib.searchsheet.widget import SearchSheet
9
13
  from PySide6.QtCore import QCoreApplication
10
14
  from PySide6.QtCore import QItemSelection
@@ -24,9 +28,9 @@ from PySide6.QtWidgets import QPlainTextEdit
24
28
  from PySide6.QtWidgets import QToolButton
25
29
  from PySide6.QtWidgets import QWidget
26
30
 
27
- from movsviewer.chartview import ChartWidgetWrapper
28
31
  from movsviewer.constants import MAINUI_UI_PATH
29
32
  from movsviewer.constants import SETTINGSUI_UI_PATH
33
+ from movsviewer.plotutils import load_infos
30
34
  from movsviewer.settings import Settings
31
35
  from movsviewer.validator import Validator
32
36
  from movsviewer.viewmodel import SortFilterViewModel
@@ -75,7 +79,7 @@ def new_settingsui(settings: Settings) -> Settingsui:
75
79
  )
76
80
  _set_data_paths(settingsui.dataPaths, file_names)
77
81
 
78
- settingsui = cast(Settingsui, QUiLoader().load(SETTINGSUI_UI_PATH))
82
+ settingsui = cast('Settingsui', QUiLoader().load(SETTINGSUI_UI_PATH))
79
83
  settingsui.usernameLineEdit.setText(settings.username)
80
84
  settingsui.passwordLineEdit.setText(settings.password)
81
85
  _set_data_paths(settingsui.dataPaths, settings.data_paths)
@@ -88,7 +92,7 @@ def new_settingsui(settings: Settings) -> Settingsui:
88
92
 
89
93
  class NewMainui:
90
94
  sheets_charts: Final[
91
- dict[str, tuple[SearchSheet, ChartWidgetWrapper, int]]
95
+ dict[str, tuple[SearchSheet, SortFilterViewModel2, int]]
92
96
  ] = {}
93
97
 
94
98
  settings: Settings
@@ -97,7 +101,7 @@ class NewMainui:
97
101
 
98
102
  def __call__(self, settings: Settings, settingsui: Settingsui) -> QWidget:
99
103
  self.settings = settings
100
- self.mainui = cast(Mainui, QUiLoader().load(MAINUI_UI_PATH))
104
+ self.mainui = cast('Mainui', QUiLoader().load(MAINUI_UI_PATH))
101
105
 
102
106
  self.multi_tabs = MultiTabs(self.mainui.centralwidget)
103
107
  self.mainui.gridLayout.addWidget(self.multi_tabs, 0, 0, 1, 1)
@@ -127,19 +131,21 @@ class NewMainui:
127
131
  return
128
132
 
129
133
  data_paths = self.settings.data_paths[:]
130
- for data_path, (sheet, chart, idx) in self.sheets_charts.items():
134
+ for data_path, (sheet, model2, idx) in self.sheets_charts.items():
131
135
  if data_path in data_paths:
132
136
  data_paths.remove(data_path)
133
137
  sheet.reload()
134
- chart.reload()
138
+ model2.update(load_infos((data_path, 'money')))
135
139
  else:
136
140
  self.multi_tabs.remove_double_box(idx)
137
141
  del self.sheets_charts[data_path]
138
142
  for data_path in data_paths:
139
143
  sheet, model = self.new_search_sheet(data_path)
140
- chart = ChartWidgetWrapper(data_path)
141
- idx = self.multi_tabs.add_double_box(sheet, chart, model.name)
142
- self.sheets_charts[data_path] = (sheet, chart, idx)
144
+ model2 = SortFilterViewModel2()
145
+ plot = Plot(model2, None)
146
+ model2.update(load_infos((data_path, 'money')))
147
+ idx = self.multi_tabs.add_double_box(sheet, plot, model.name)
148
+ self.sheets_charts[data_path] = (sheet, model2, idx)
143
149
 
144
150
  def update_status_bar(
145
151
  self, model: SortFilterViewModel, selection_model: QItemSelectionModel
@@ -0,0 +1,38 @@
1
+ from collections import defaultdict
2
+ from datetime import date
3
+ from itertools import accumulate
4
+ from operator import attrgetter
5
+ from typing import TYPE_CHECKING
6
+
7
+ from guilib.chartwidget.model import Column
8
+ from guilib.chartwidget.model import ColumnHeader
9
+ from guilib.chartwidget.model import ColumnProto
10
+ from guilib.chartwidget.model import Info
11
+ from guilib.chartwidget.model import InfoProto
12
+ from movslib.movs import read_txt
13
+
14
+ if TYPE_CHECKING:
15
+ from collections.abc import Iterable
16
+ from decimal import Decimal
17
+
18
+ from movslib.model import Row
19
+ from movslib.model import Rows
20
+
21
+
22
+ def _acc(rows: 'Rows') -> 'Iterable[tuple[date, Decimal]]':
23
+ def func(a: 'tuple[date, Decimal]', b: 'Row') -> 'tuple[date, Decimal]':
24
+ return (b.date, a[1] + b.money)
25
+
26
+ it = iter(sorted(rows, key=attrgetter('date')))
27
+ head = next(it)
28
+ return accumulate(it, func, initial=(head.date, head.money))
29
+
30
+
31
+ def load_infos(*fn_names: tuple[str, str]) -> list[InfoProto]:
32
+ tmp = defaultdict[date, list[ColumnProto]](list)
33
+ for fn, name in fn_names:
34
+ _, rows = read_txt(fn, name)
35
+ ch = ColumnHeader(rows.name)
36
+ for d, m in _acc(rows):
37
+ tmp[d].append(Column(ch, m))
38
+ return [Info(d, tmp[d]) for d in sorted(tmp)]
movsviewer/settings.py CHANGED
@@ -15,7 +15,7 @@ class Settings:
15
15
  @property
16
16
  def username(self) -> str:
17
17
  value = self.settings.value(SETTINGS_USERNAME)
18
- return cast(str, value) if value is not None else ''
18
+ return cast('str', value) if value is not None else ''
19
19
 
20
20
  @username.setter
21
21
  def username(self, username: str) -> None:
@@ -24,7 +24,7 @@ class Settings:
24
24
  @property
25
25
  def password(self) -> str:
26
26
  value = self.settings.value(SETTINGS_PASSWORD)
27
- return cast(str, value) if value is not None else ''
27
+ return cast('str', value) if value is not None else ''
28
28
 
29
29
  @password.setter
30
30
  def password(self, password: str) -> None:
movsviewer/validator.py CHANGED
@@ -1,63 +1,13 @@
1
- from datetime import UTC
2
- from datetime import date
3
- from datetime import datetime
4
- from pathlib import Path
5
1
  from typing import TYPE_CHECKING
6
2
 
3
+ from movsvalidator.movsvalidator import validate
7
4
  from PySide6.QtWidgets import QMessageBox
8
5
  from PySide6.QtWidgets import QWidget
9
6
 
10
- from movsviewer.reader import read
11
-
12
7
  if TYPE_CHECKING:
13
- from movslib.model import KV
14
- from movslib.model import Rows
15
-
16
8
  from movsviewer.settings import Settings
17
9
 
18
10
 
19
- def validate_saldo(kv: 'KV', csv: 'Rows', messages: list[str]) -> bool:
20
- messages.append(f'bpol.saldo_al: {kv.saldo_al}')
21
- if kv.saldo_al:
22
- ultimo_update = (datetime.now(tz=UTC).date() - kv.saldo_al).days
23
- messages.append(
24
- f'ultimo update: {ultimo_update} giorni fa'
25
- )
26
- messages.append(
27
- f'bpol.saldo_contabile: {float(kv.saldo_contabile):_}'
28
- )
29
- messages.append(
30
- f'bpol.saldo_disponibile: {float(kv.saldo_disponibile):_}'
31
- )
32
-
33
- s = sum(item.money for item in csv)
34
- messages.append(f'Σ (item.accredito - item.addebito): {float(s):_}')
35
- ret = kv.saldo_contabile == s == kv.saldo_disponibile
36
- if not ret:
37
- delta = max(
38
- [abs(kv.saldo_contabile - s), abs(s - kv.saldo_disponibile)]
39
- )
40
- messages.append(f'Δ: {float(delta):_}')
41
- return ret
42
-
43
-
44
- def validate_dates(csv: 'Rows', messages: list[str]) -> bool:
45
- data_contabile: date | None = None
46
- for row in csv:
47
- if data_contabile is not None and data_contabile < row.data_contabile:
48
- messages.append(f'{data_contabile} < {row.data_contabile}!')
49
- return False
50
- return True
51
-
52
-
53
- def validate(fn: str, messages: list[str]) -> bool:
54
- messages.append(fn)
55
- kv, csv = read(fn, Path(fn).stem)
56
- return all(
57
- [validate_saldo(kv, csv, messages), validate_dates(csv, messages)]
58
- )
59
-
60
-
61
11
  class Validator:
62
12
  def __init__(self, parent: QWidget, settings: 'Settings') -> None:
63
13
  self.parent = parent
movsviewer/viewmodel.py CHANGED
@@ -13,6 +13,7 @@ from guilib.searchsheet.model import SearchableModel
13
13
  from movslib.model import ZERO
14
14
  from movslib.model import Row
15
15
  from movslib.model import Rows
16
+ from movslib.reader import read
16
17
  from PySide6.QtCore import QAbstractTableModel
17
18
  from PySide6.QtCore import QItemSelectionModel
18
19
  from PySide6.QtCore import QModelIndex
@@ -22,8 +23,6 @@ from PySide6.QtCore import Qt
22
23
  from PySide6.QtGui import QBrush
23
24
  from PySide6.QtGui import QColor
24
25
 
25
- from movsviewer.reader import read
26
-
27
26
  if TYPE_CHECKING:
28
27
  from PySide6.QtWidgets import QStatusBar
29
28
 
@@ -96,7 +95,7 @@ class ViewModel(QAbstractTableModel):
96
95
  if role == Qt.ItemDataRole.BackgroundRole:
97
96
  max_, min_, val = self._max, self._min, _abs(self._data[row])
98
97
  perc = (
99
- (val - min_) / (max_ - min_) if max_ != min_ else Decimal(0.5)
98
+ (val - min_) / (max_ - min_) if max_ != min_ else Decimal('0.5')
100
99
  )
101
100
 
102
101
  hue = int(perc * 120) # 0..359 ; red=0, green=120
@@ -106,7 +105,9 @@ class ViewModel(QAbstractTableModel):
106
105
  return QBrush(QColor.fromHsl(hue, saturation, lightness))
107
106
 
108
107
  if role == Qt.ItemDataRole.UserRole:
109
- return cast(T_FIELDS, getattr(self._data[row], FIELD_NAMES[column]))
108
+ return cast(
109
+ 'T_FIELDS', getattr(self._data[row], FIELD_NAMES[column])
110
+ )
110
111
 
111
112
  return None
112
113
 
@@ -148,7 +149,7 @@ class SortFilterViewModel(SearchableModel):
148
149
 
149
150
  @override
150
151
  def sourceModel(self) -> ViewModel:
151
- return cast(ViewModel, super().sourceModel())
152
+ return cast('ViewModel', super().sourceModel())
152
153
 
153
154
  @override
154
155
  def sort(