pyqttoolkit 0.9.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.
- pyqttoolkit/__init__.py +20 -0
- pyqttoolkit/_version.py +21 -0
- pyqttoolkit/application/__init__.py +18 -0
- pyqttoolkit/application/application.py +86 -0
- pyqttoolkit/application/garbage_collector.py +65 -0
- pyqttoolkit/colors/__init__.py +84 -0
- pyqttoolkit/config/__init__.py +20 -0
- pyqttoolkit/config/base.py +22 -0
- pyqttoolkit/config/composite.py +31 -0
- pyqttoolkit/config/default.py +27 -0
- pyqttoolkit/config/environment.py +35 -0
- pyqttoolkit/config/multi_json_file.py +93 -0
- pyqttoolkit/config/with_signal.py +30 -0
- pyqttoolkit/config/with_value_changed.py +31 -0
- pyqttoolkit/data/__init__.py +21 -0
- pyqttoolkit/data/bits.py +23 -0
- pyqttoolkit/data/cache.py +64 -0
- pyqttoolkit/data/interval.py +26 -0
- pyqttoolkit/data/message.py +29 -0
- pyqttoolkit/data/names.py +24 -0
- pyqttoolkit/datetime/__init__.py +16 -0
- pyqttoolkit/datetime/datetime.py +51 -0
- pyqttoolkit/datetime/qdatetime.py +53 -0
- pyqttoolkit/dependencies/__init__.py +56 -0
- pyqttoolkit/errors/__init__.py +19 -0
- pyqttoolkit/exceptions/__init__.py +18 -0
- pyqttoolkit/filesystem/__init__.py +24 -0
- pyqttoolkit/logs/__init__.py +140 -0
- pyqttoolkit/metadata/__init__.py +23 -0
- pyqttoolkit/metadata/dataframe_metadata_manager.py +105 -0
- pyqttoolkit/metadata/dict_metadata_manager.py +63 -0
- pyqttoolkit/metadata/id_generator.py +20 -0
- pyqttoolkit/metadata/metadata_manager_base.py +54 -0
- pyqttoolkit/models/__init__.py +26 -0
- pyqttoolkit/models/code_text.py +43 -0
- pyqttoolkit/models/dataframetable.py +135 -0
- pyqttoolkit/models/dictpropertytree.py +155 -0
- pyqttoolkit/models/dicttable.py +140 -0
- pyqttoolkit/models/json_text.py +52 -0
- pyqttoolkit/models/list_model.py +52 -0
- pyqttoolkit/models/module.py +26 -0
- pyqttoolkit/models/roles.py +23 -0
- pyqttoolkit/models/series_data.py +68 -0
- pyqttoolkit/models/span.py +36 -0
- pyqttoolkit/models/validation.py +27 -0
- pyqttoolkit/modules/__init__.py +18 -0
- pyqttoolkit/modules/events/__init__.py +19 -0
- pyqttoolkit/modules/events/add_signal.py +31 -0
- pyqttoolkit/modules/events/module_opening.py +21 -0
- pyqttoolkit/modules/events/rejectable.py +29 -0
- pyqttoolkit/modules/launcher_config.py +55 -0
- pyqttoolkit/modules/module.py +111 -0
- pyqttoolkit/properties/__init__.py +18 -0
- pyqttoolkit/properties/auto.py +221 -0
- pyqttoolkit/properties/throttled.py +77 -0
- pyqttoolkit/scripting/__init__.py +19 -0
- pyqttoolkit/scripting/script_context.py +36 -0
- pyqttoolkit/scripting/script_runner.py +132 -0
- pyqttoolkit/scripting/script_template.py +42 -0
- pyqttoolkit/services/__init__.py +32 -0
- pyqttoolkit/services/autosave.py +41 -0
- pyqttoolkit/services/email.py +25 -0
- pyqttoolkit/services/event_registry.py +52 -0
- pyqttoolkit/services/file_dialog.py +71 -0
- pyqttoolkit/services/hotkey_manager.py +137 -0
- pyqttoolkit/services/link_manager.py +135 -0
- pyqttoolkit/services/message_board.py +90 -0
- pyqttoolkit/services/module_service.py +154 -0
- pyqttoolkit/services/object_converter.py +45 -0
- pyqttoolkit/services/project_updater.py +149 -0
- pyqttoolkit/services/task_runner.py +281 -0
- pyqttoolkit/services/theme_manager.py +46 -0
- pyqttoolkit/services/tool_window_service.py +46 -0
- pyqttoolkit/viewmanagers/__init__.py +17 -0
- pyqttoolkit/viewmanagers/tool.py +23 -0
- pyqttoolkit/views/__init__.py +50 -0
- pyqttoolkit/views/bulk_value_selector.py +112 -0
- pyqttoolkit/views/check_box.py +36 -0
- pyqttoolkit/views/code/__init__.py +18 -0
- pyqttoolkit/views/code/code_edit.py +268 -0
- pyqttoolkit/views/code/python_syntax.py +226 -0
- pyqttoolkit/views/combo_box.py +101 -0
- pyqttoolkit/views/count_selector.py +59 -0
- pyqttoolkit/views/datetime.py +111 -0
- pyqttoolkit/views/datetime_range_selector.py +231 -0
- pyqttoolkit/views/default_text_line_edit.py +50 -0
- pyqttoolkit/views/delegates.py +117 -0
- pyqttoolkit/views/dtype_edit.py +117 -0
- pyqttoolkit/views/editable_combo_box.py +45 -0
- pyqttoolkit/views/file_selector.py +51 -0
- pyqttoolkit/views/icon.py +67 -0
- pyqttoolkit/views/icon_button.py +129 -0
- pyqttoolkit/views/json_edit.py +21 -0
- pyqttoolkit/views/labelled.py +29 -0
- pyqttoolkit/views/layout/__init__.py +21 -0
- pyqttoolkit/views/layout/collabsible.py +59 -0
- pyqttoolkit/views/layout/group_box.py +48 -0
- pyqttoolkit/views/layout/group_panel.py +188 -0
- pyqttoolkit/views/layout/lines.py +30 -0
- pyqttoolkit/views/layout/link.py +38 -0
- pyqttoolkit/views/line_edit.py +61 -0
- pyqttoolkit/views/linkable.py +49 -0
- pyqttoolkit/views/list_view.py +43 -0
- pyqttoolkit/views/main_window.py +91 -0
- pyqttoolkit/views/metadata_selector.py +84 -0
- pyqttoolkit/views/modulewindow.py +81 -0
- pyqttoolkit/views/plot/__init__.py +26 -0
- pyqttoolkit/views/plot/legend_control.py +121 -0
- pyqttoolkit/views/plot/matplotlib/__init__.py +23 -0
- pyqttoolkit/views/plot/matplotlib/base.py +1056 -0
- pyqttoolkit/views/plot/matplotlib/colorbar.py +75 -0
- pyqttoolkit/views/plot/matplotlib/colormap.py +48 -0
- pyqttoolkit/views/plot/matplotlib/font.py +55 -0
- pyqttoolkit/views/plot/plot_options.py +211 -0
- pyqttoolkit/views/plot/plot_toolbar_options.py +133 -0
- pyqttoolkit/views/plot/size.py +41 -0
- pyqttoolkit/views/plot/tool_type.py +26 -0
- pyqttoolkit/views/plot/toolbar.py +100 -0
- pyqttoolkit/views/popout.py +41 -0
- pyqttoolkit/views/popup.py +41 -0
- pyqttoolkit/views/property_tree.py +26 -0
- pyqttoolkit/views/shrunk_push_button.py +34 -0
- pyqttoolkit/views/steppable_combo_box.py +67 -0
- pyqttoolkit/views/styleable.py +37 -0
- pyqttoolkit/views/table.py +146 -0
- pyqttoolkit/views/text_edit.py +31 -0
- pyqttoolkit/views/tool_window.py +35 -0
- pyqttoolkit-0.9.1.dist-info/METADATA +69 -0
- pyqttoolkit-0.9.1.dist-info/RECORD +133 -0
- pyqttoolkit-0.9.1.dist-info/WHEEL +5 -0
- pyqttoolkit-0.9.1.dist-info/licenses/LICENSE.txt +674 -0
- pyqttoolkit-0.9.1.dist-info/licenses/LICENSE_SHORT +16 -0
- pyqttoolkit-0.9.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from collections import deque
|
|
18
|
+
|
|
19
|
+
class Cache:
|
|
20
|
+
def __init__(self, size):
|
|
21
|
+
self._storage = deque(maxlen=size)
|
|
22
|
+
|
|
23
|
+
def __contains__(self, key):
|
|
24
|
+
for k, v in self._storage:
|
|
25
|
+
if k == key:
|
|
26
|
+
return True
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
def __getitem__(self, key):
|
|
30
|
+
for k, v in self._storage:
|
|
31
|
+
if k == key:
|
|
32
|
+
return v
|
|
33
|
+
raise KeyError(key)
|
|
34
|
+
|
|
35
|
+
def __setitem__(self, key, value):
|
|
36
|
+
if key in self:
|
|
37
|
+
del self[key]
|
|
38
|
+
self._storage.append((key, value))
|
|
39
|
+
|
|
40
|
+
def __delitem__(self, key):
|
|
41
|
+
if key not in self:
|
|
42
|
+
raise KeyError(key)
|
|
43
|
+
for k, v in self._storage:
|
|
44
|
+
if k == key:
|
|
45
|
+
self._storage.remove((key, v))
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
def delete_group(self, group):
|
|
49
|
+
to_remove = []
|
|
50
|
+
for k, v in self._storage:
|
|
51
|
+
if k.startswith(group):
|
|
52
|
+
to_remove.append((k, v))
|
|
53
|
+
for v in to_remove:
|
|
54
|
+
self._storage.remove(v)
|
|
55
|
+
|
|
56
|
+
def setdefault(self, key, default_factory):
|
|
57
|
+
if key in self:
|
|
58
|
+
return self[key]
|
|
59
|
+
value = default_factory()
|
|
60
|
+
self[key] = value
|
|
61
|
+
return value
|
|
62
|
+
|
|
63
|
+
def clear(self):
|
|
64
|
+
self._storage.clear()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
import pandas as pd
|
|
18
|
+
|
|
19
|
+
def calculate_interval(values, sort=True):
|
|
20
|
+
if len(values) < 2:
|
|
21
|
+
return None
|
|
22
|
+
values_series = pd.Series(values)
|
|
23
|
+
if sort:
|
|
24
|
+
values_series = values_series.sort_values()
|
|
25
|
+
# We want to ensure that the calculated interval is accurate for 90% of values to avoid mistakenly losing values
|
|
26
|
+
return (values_series - values_series.shift(1)).quantile(0.1)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from enum import Enum, IntEnum
|
|
18
|
+
|
|
19
|
+
class MessageType(Enum):
|
|
20
|
+
validation_error = 0
|
|
21
|
+
confirmation = 1
|
|
22
|
+
application_exception = 2
|
|
23
|
+
|
|
24
|
+
class MessageResponse(IntEnum):
|
|
25
|
+
ok = 1
|
|
26
|
+
cancel = 2
|
|
27
|
+
save = 4
|
|
28
|
+
discard = 8
|
|
29
|
+
ignore = 16
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
|
|
18
|
+
def get_next_available_name(name_basis, names):
|
|
19
|
+
i = 1
|
|
20
|
+
while True:
|
|
21
|
+
next_name = f'{name_basis} {i}' if i > 1 else name_basis
|
|
22
|
+
if next_name not in names:
|
|
23
|
+
return next_name
|
|
24
|
+
i += 1
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from datetime import datetime, timedelta
|
|
18
|
+
from dateutil.parser import parse as parse_datetime
|
|
19
|
+
|
|
20
|
+
def isoformat_to_datetime(isoformat: str):
|
|
21
|
+
if isoformat is None:
|
|
22
|
+
return None
|
|
23
|
+
return parse_datetime(isoformat)
|
|
24
|
+
|
|
25
|
+
def round_timedelta(delta, resolution):
|
|
26
|
+
if resolution is None:
|
|
27
|
+
return delta
|
|
28
|
+
days = delta.days - (delta.days % resolution.days) if resolution.days else delta.days
|
|
29
|
+
seconds = delta.seconds - (delta.seconds % resolution.seconds) if resolution.seconds else delta.seconds
|
|
30
|
+
return timedelta(days=days, seconds=seconds)
|
|
31
|
+
|
|
32
|
+
def round_datetime(time, resolution):
|
|
33
|
+
if resolution is None:
|
|
34
|
+
return time
|
|
35
|
+
day = time.day - (time.day % resolution.days) if resolution.days else time.day
|
|
36
|
+
hour = time.hour - (time.hour % resolution.seconds // 3600) if resolution.seconds // 3600 else time.hour
|
|
37
|
+
minute = time.minute - (time.minute % ((resolution.seconds // 60) % 60)) if (resolution.seconds // 60) % 60 else time.minute
|
|
38
|
+
second = time.second - (time.second % resolution.seconds) if resolution.seconds else time.second
|
|
39
|
+
microsecond = time.microsecond - (time.microsecond % resolution.microseconds) if resolution.microseconds else time.microsecond
|
|
40
|
+
return datetime(year=time.year, month=time.month, day=day, hour=hour, minute=minute, second=second, microsecond=microsecond, tzinfo=time.tzinfo)
|
|
41
|
+
|
|
42
|
+
def iso_year_start(iso_year):
|
|
43
|
+
"The gregorian calendar date of the first day of the given ISO year"
|
|
44
|
+
fourth_jan = datetime(iso_year, 1, 4)
|
|
45
|
+
delta = timedelta(fourth_jan.isoweekday()-1)
|
|
46
|
+
return fourth_jan - delta
|
|
47
|
+
|
|
48
|
+
def iso_to_gregorian(iso_year, iso_week, iso_day):
|
|
49
|
+
"Gregorian calendar date for the given ISO year, week and day"
|
|
50
|
+
year_start = iso_year_start(iso_year)
|
|
51
|
+
return year_start + timedelta(days=iso_day-1, weeks=iso_week-1)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from datetime import datetime, timedelta
|
|
18
|
+
from .datetime import round_timedelta
|
|
19
|
+
|
|
20
|
+
import pandas as pd
|
|
21
|
+
|
|
22
|
+
from PyQt5.QtCore import QDateTime, QDate, QTime
|
|
23
|
+
|
|
24
|
+
def q_datetime_to_datetime(q_datetime: QDateTime):
|
|
25
|
+
if q_datetime is None or not q_datetime.isValid():
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
return datetime(
|
|
29
|
+
q_datetime.date().year(), q_datetime.date().month(), q_datetime.date().day(),
|
|
30
|
+
q_datetime.time().hour(), q_datetime.time().minute(), q_datetime.time().second(),
|
|
31
|
+
q_datetime.time().msec() * 1000
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def pd_timestamp_to_q_datetime(pd_timestamp: datetime):
|
|
36
|
+
if pd_timestamp is None or pd.isna(pd_timestamp):
|
|
37
|
+
return QDateTime()
|
|
38
|
+
return QDateTime(
|
|
39
|
+
QDate(pd_timestamp.year, pd_timestamp.month, pd_timestamp.day),
|
|
40
|
+
QTime(pd_timestamp.hour, pd_timestamp.minute, pd_timestamp.second, pd_timestamp.microsecond // 1000)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def step_qdatetime(control, fraction, interval, range_start, range_end):
|
|
44
|
+
date_from = q_datetime_to_datetime(control.dateFrom)
|
|
45
|
+
date_to = q_datetime_to_datetime(control.dateTo)
|
|
46
|
+
range_start = q_datetime_to_datetime(range_start)
|
|
47
|
+
range_end = q_datetime_to_datetime(range_end)
|
|
48
|
+
delta = round_timedelta((date_to - date_from) * fraction, interval)
|
|
49
|
+
date_from = max(date_from + delta, range_start)
|
|
50
|
+
date_to = min(date_to + delta, range_end)
|
|
51
|
+
if date_to - date_from > timedelta(0):
|
|
52
|
+
control.dateTo = QDateTime(date_to)
|
|
53
|
+
control.dateFrom = QDateTime(date_from)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
import inspect
|
|
18
|
+
|
|
19
|
+
class MissingDependency(Exception):
|
|
20
|
+
def __init__(self, object_type, dependency_name):
|
|
21
|
+
Exception.__init__(self, object_type, dependency_name)
|
|
22
|
+
|
|
23
|
+
class DependencyContainer:
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self._instances = {}
|
|
26
|
+
self._types = {}
|
|
27
|
+
|
|
28
|
+
def register_instance(self, name, value):
|
|
29
|
+
self._instances[name] = value
|
|
30
|
+
|
|
31
|
+
def register_type(self, type_for, base_type, result_type):
|
|
32
|
+
self._types.setdefault(type_for, {})[base_type] = result_type
|
|
33
|
+
|
|
34
|
+
def get_instance(self, name):
|
|
35
|
+
return self._instances[name]
|
|
36
|
+
|
|
37
|
+
def resolve(self, type_, extras=None, none_for_missing=False):
|
|
38
|
+
args = self._get_args(type_)
|
|
39
|
+
all_dependencies = {**self._instances, **(extras or {})}
|
|
40
|
+
for arg_name, arg in args.items():
|
|
41
|
+
if arg_name not in all_dependencies:
|
|
42
|
+
if arg.default != inspect.Parameter.empty:
|
|
43
|
+
all_dependencies[arg_name] = arg.default
|
|
44
|
+
elif none_for_missing:
|
|
45
|
+
all_dependencies[arg_name] = None
|
|
46
|
+
else:
|
|
47
|
+
raise MissingDependency(type_, arg_name)
|
|
48
|
+
return type_(**{a: all_dependencies[a] for a in args})
|
|
49
|
+
|
|
50
|
+
def resolve_for(self, type_for, base_type, extras=None):
|
|
51
|
+
type_to_resolve = self._types[type_for][base_type]
|
|
52
|
+
return self.resolve(type_to_resolve, extras)
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _get_args(type_):
|
|
56
|
+
return inspect.signature(type_).parameters
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
class InvalidDataError(Exception):
|
|
18
|
+
def __init__(self, name, error):
|
|
19
|
+
Exception.__init__(self, f'{name}: {error}')
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
class TaskCancelled(Exception):
|
|
18
|
+
pass
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from PyQt5.QtCore import QUrl
|
|
18
|
+
#pylint: enable=no-name-in-module
|
|
19
|
+
|
|
20
|
+
def sanitise_filename(maybe_qurl_filename):
|
|
21
|
+
return (
|
|
22
|
+
QUrl(maybe_qurl_filename).toLocalFile() if 'file://' in maybe_qurl_filename
|
|
23
|
+
else maybe_qurl_filename
|
|
24
|
+
)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from typing import Callable
|
|
18
|
+
from io import StringIO
|
|
19
|
+
import logging
|
|
20
|
+
from logging import handlers
|
|
21
|
+
from functools import wraps
|
|
22
|
+
from os import path, makedirs
|
|
23
|
+
from enum import Enum
|
|
24
|
+
import appdirs
|
|
25
|
+
|
|
26
|
+
class CustomLogLevel(Enum):
|
|
27
|
+
TRACE = 5
|
|
28
|
+
|
|
29
|
+
TRACE = CustomLogLevel.TRACE.value
|
|
30
|
+
|
|
31
|
+
LOG_DIR = None
|
|
32
|
+
LOG_FILE = None
|
|
33
|
+
|
|
34
|
+
def get_log_dir():
|
|
35
|
+
global LOG_DIR
|
|
36
|
+
if LOG_DIR is None:
|
|
37
|
+
LOG_DIR = path.join(path.dirname(appdirs.AppDirs('PyQtToolkit', roaming=True).user_data_dir), 'logs')
|
|
38
|
+
return LOG_DIR
|
|
39
|
+
|
|
40
|
+
def get_log_file(log_dir):
|
|
41
|
+
global LOG_FILE, LOG_DIR
|
|
42
|
+
if LOG_FILE is None:
|
|
43
|
+
LOG_FILE = 'app.log'
|
|
44
|
+
return path.join(LOG_DIR, LOG_FILE)
|
|
45
|
+
|
|
46
|
+
def configure_logging(log_level, console, log_filter=None, log_file=None):
|
|
47
|
+
global LOG_DIR, LOG_FILE
|
|
48
|
+
if log_file is not None:
|
|
49
|
+
log_file = path.abspath(path.normpath(log_file))
|
|
50
|
+
LOG_DIR = path.dirname(log_file)
|
|
51
|
+
LOG_FILE = path.basename(log_file)
|
|
52
|
+
|
|
53
|
+
logging.addLevelName(TRACE, 'TRACE')
|
|
54
|
+
|
|
55
|
+
numeric_level = getattr(logging, log_level.upper(), None)
|
|
56
|
+
if not isinstance(numeric_level, int):
|
|
57
|
+
enum_level = getattr(CustomLogLevel, log_level.upper(), None)
|
|
58
|
+
if isinstance(enum_level, CustomLogLevel):
|
|
59
|
+
numeric_level = enum_level.value
|
|
60
|
+
else:
|
|
61
|
+
numeric_level = logging.INFO
|
|
62
|
+
|
|
63
|
+
logging.getLogger().setLevel(numeric_level)
|
|
64
|
+
|
|
65
|
+
rollover_bytes = 100 * 1024
|
|
66
|
+
dir_name = get_log_dir()
|
|
67
|
+
if not path.isdir(dir_name):
|
|
68
|
+
makedirs(dir_name)
|
|
69
|
+
|
|
70
|
+
log_file = get_log_file(dir_name)
|
|
71
|
+
|
|
72
|
+
do_rollover = path.isfile(log_file)
|
|
73
|
+
|
|
74
|
+
file_handler = handlers.RotatingFileHandler(
|
|
75
|
+
get_log_file(dir_name),
|
|
76
|
+
mode='w',
|
|
77
|
+
maxBytes=rollover_bytes,
|
|
78
|
+
backupCount=10
|
|
79
|
+
)
|
|
80
|
+
if do_rollover:
|
|
81
|
+
file_handler.doRollover()
|
|
82
|
+
file_handler.setLevel(numeric_level)
|
|
83
|
+
if log_filter:
|
|
84
|
+
file_handler.addFilter(log_filter)
|
|
85
|
+
file_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
86
|
+
file_handler.setFormatter(file_formatter)
|
|
87
|
+
logging.getLogger().addHandler(file_handler)
|
|
88
|
+
|
|
89
|
+
if console:
|
|
90
|
+
console_handler = logging.StreamHandler()
|
|
91
|
+
console_handler.setLevel(numeric_level)
|
|
92
|
+
console_handler.setFormatter(file_formatter)
|
|
93
|
+
if log_filter:
|
|
94
|
+
console_handler.addFilter(log_filter)
|
|
95
|
+
logging.getLogger().addHandler(console_handler)
|
|
96
|
+
|
|
97
|
+
def log_debug(_f):
|
|
98
|
+
return logf(logging.DEBUG)
|
|
99
|
+
|
|
100
|
+
def logf(level, log_args=True):
|
|
101
|
+
def logf_decorator(f):
|
|
102
|
+
@wraps(f)
|
|
103
|
+
def _(*args, **kwargs):
|
|
104
|
+
logger = logging.getLogger(f.__name__)
|
|
105
|
+
if log_args:
|
|
106
|
+
logger.log(level, 'Entering, args: %s, kwargs: %s', args, kwargs)
|
|
107
|
+
else:
|
|
108
|
+
logger.log(level, f'Entering')
|
|
109
|
+
result = f(*args, **kwargs)
|
|
110
|
+
logger.log(level, 'Exiting')
|
|
111
|
+
return result
|
|
112
|
+
return _
|
|
113
|
+
return logf_decorator
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class LogStream:
|
|
117
|
+
def __init__(self, notify_update: Callable = None):
|
|
118
|
+
self._buffer = StringIO()
|
|
119
|
+
self._notify_update = notify_update
|
|
120
|
+
|
|
121
|
+
def __iadd__(self, other):
|
|
122
|
+
self.write(other)
|
|
123
|
+
return self
|
|
124
|
+
|
|
125
|
+
def write(self, message):
|
|
126
|
+
self._buffer.write(message)
|
|
127
|
+
if self._notify_update:
|
|
128
|
+
self._notify_update()
|
|
129
|
+
|
|
130
|
+
def flush(self):
|
|
131
|
+
self._buffer.flush()
|
|
132
|
+
|
|
133
|
+
def getvalue(self):
|
|
134
|
+
return self._buffer.getvalue()
|
|
135
|
+
|
|
136
|
+
def clear(self):
|
|
137
|
+
self._buffer.close()
|
|
138
|
+
self._buffer = StringIO()
|
|
139
|
+
if self._notify_update:
|
|
140
|
+
self._notify_update()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from .dict_metadata_manager import DictMetadataManager
|
|
18
|
+
from .id_generator import next_id
|
|
19
|
+
try:
|
|
20
|
+
from .dataframe_metadata_manager import DataframeMetadataManager
|
|
21
|
+
except ImportError:
|
|
22
|
+
import warnings
|
|
23
|
+
warnings.warn('Unable to import DataframeMetadataManager - please ensure pandas is installed', ImportWarning)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# pyqttoolkit
|
|
2
|
+
# Copyright (C) 2018-2019, Simmovation Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from copy import deepcopy
|
|
19
|
+
import json
|
|
20
|
+
|
|
21
|
+
import pandas as pd
|
|
22
|
+
|
|
23
|
+
from .metadata_manager_base import MetadataManagerBase
|
|
24
|
+
|
|
25
|
+
class DataframeMetadataManager(MetadataManagerBase):
|
|
26
|
+
def __init__(self, project_manager):
|
|
27
|
+
MetadataManagerBase.__init__(self, project_manager)
|
|
28
|
+
|
|
29
|
+
def get_property(self, id_, property_):
|
|
30
|
+
if not isinstance(property_, Enum):
|
|
31
|
+
raise TypeError('Property must be enum type')
|
|
32
|
+
property_type = type(property_)
|
|
33
|
+
property_type_key = self._get_property_type_key(property_type)
|
|
34
|
+
if property_type_key not in self.metadata:
|
|
35
|
+
return None
|
|
36
|
+
if id_ not in self.metadata[property_type_key].index:
|
|
37
|
+
return None
|
|
38
|
+
if property_.name not in self.metadata[property_type_key].columns:
|
|
39
|
+
return None
|
|
40
|
+
value_read = deepcopy(self.metadata[property_type_key].loc[id_, property_.name])
|
|
41
|
+
if isinstance(value_read, str) and value_read.startswith('json:'):
|
|
42
|
+
return json.loads(value_read.replace('json:', ''))
|
|
43
|
+
return value_read
|
|
44
|
+
|
|
45
|
+
def set_property(self, id_, property_, value):
|
|
46
|
+
if not isinstance(property_, Enum):
|
|
47
|
+
raise TypeError('Property must be enum type')
|
|
48
|
+
if id_ is None:
|
|
49
|
+
raise ValueError('Must specify an ID')
|
|
50
|
+
property_type = type(property_)
|
|
51
|
+
property_type_key = self._get_property_type_key(property_type)
|
|
52
|
+
if not property_type_key in self.metadata:
|
|
53
|
+
self.metadata[property_type_key] = pd.DataFrame(columns=self.columns(property_type))
|
|
54
|
+
if not id_ in self.metadata[property_type_key].index:
|
|
55
|
+
self.metadata[property_type_key].loc[id_, :] = None
|
|
56
|
+
if not property_.name in self.metadata[property_type_key].columns:
|
|
57
|
+
self.metadata[property_type_key].loc[:, property_.name] = None
|
|
58
|
+
self.metadata[property_type_key].loc[id_, property_.name] = self._get_value_to_store(value)
|
|
59
|
+
|
|
60
|
+
def get_properties(self, id_, property_type):
|
|
61
|
+
property_type_key = self._get_property_type_key(property_type)
|
|
62
|
+
if property_type_key not in self.metadata:
|
|
63
|
+
return None
|
|
64
|
+
if id_ not in self.metadata[property_type_key].index:
|
|
65
|
+
return None
|
|
66
|
+
return self.metadata[property_type_key].loc[id_].to_dict()
|
|
67
|
+
|
|
68
|
+
def add_properties(self, id_, property_type, properties=None):
|
|
69
|
+
if id_ is None:
|
|
70
|
+
raise ValueError('Must specify an ID')
|
|
71
|
+
property_type_key = self._get_property_type_key(property_type)
|
|
72
|
+
if property_type_key not in self.metadata:
|
|
73
|
+
self.metadata[property_type_key] = pd.DataFrame(columns=self.columns(property_type))
|
|
74
|
+
if id_ not in self.metadata[property_type_key].index:
|
|
75
|
+
self.metadata[property_type_key].loc[id_, :] = None
|
|
76
|
+
if properties:
|
|
77
|
+
for p, v in properties.items():
|
|
78
|
+
self.metadata[property_type_key].loc[id_, p] = self._get_value_to_store(v)
|
|
79
|
+
|
|
80
|
+
def delete_properties(self, id_, property_type):
|
|
81
|
+
if id_ is None:
|
|
82
|
+
raise ValueError('Must specify an ID')
|
|
83
|
+
metadata = self.metadata.get(self._get_property_type_key(property_type), {})
|
|
84
|
+
if id_ in metadata.index:
|
|
85
|
+
metadata.drop(id_, inplace=True)
|
|
86
|
+
|
|
87
|
+
def get_metadata(self, property_type):
|
|
88
|
+
property_type_key = self._get_property_type_key(property_type)
|
|
89
|
+
if property_type_key not in self.metadata:
|
|
90
|
+
return None
|
|
91
|
+
return self.metadata[property_type_key].T.to_dict()
|
|
92
|
+
|
|
93
|
+
def set_metadata(self, property_type, value):
|
|
94
|
+
property_type_key = self._get_property_type_key(property_type)
|
|
95
|
+
self.metadata[property_type_key] = pd.DataFrame(value).T
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def columns(property_type):
|
|
99
|
+
return [pt.name for pt in property_type]
|
|
100
|
+
|
|
101
|
+
def _get_value_to_store(self, value):
|
|
102
|
+
value_to_store = deepcopy(value)
|
|
103
|
+
if isinstance(value_to_store, dict) or isinstance(value_to_store, list):
|
|
104
|
+
value_to_store = 'json:' + json.dumps(value_to_store)
|
|
105
|
+
return value_to_store
|