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.
Files changed (133) hide show
  1. pyqttoolkit/__init__.py +20 -0
  2. pyqttoolkit/_version.py +21 -0
  3. pyqttoolkit/application/__init__.py +18 -0
  4. pyqttoolkit/application/application.py +86 -0
  5. pyqttoolkit/application/garbage_collector.py +65 -0
  6. pyqttoolkit/colors/__init__.py +84 -0
  7. pyqttoolkit/config/__init__.py +20 -0
  8. pyqttoolkit/config/base.py +22 -0
  9. pyqttoolkit/config/composite.py +31 -0
  10. pyqttoolkit/config/default.py +27 -0
  11. pyqttoolkit/config/environment.py +35 -0
  12. pyqttoolkit/config/multi_json_file.py +93 -0
  13. pyqttoolkit/config/with_signal.py +30 -0
  14. pyqttoolkit/config/with_value_changed.py +31 -0
  15. pyqttoolkit/data/__init__.py +21 -0
  16. pyqttoolkit/data/bits.py +23 -0
  17. pyqttoolkit/data/cache.py +64 -0
  18. pyqttoolkit/data/interval.py +26 -0
  19. pyqttoolkit/data/message.py +29 -0
  20. pyqttoolkit/data/names.py +24 -0
  21. pyqttoolkit/datetime/__init__.py +16 -0
  22. pyqttoolkit/datetime/datetime.py +51 -0
  23. pyqttoolkit/datetime/qdatetime.py +53 -0
  24. pyqttoolkit/dependencies/__init__.py +56 -0
  25. pyqttoolkit/errors/__init__.py +19 -0
  26. pyqttoolkit/exceptions/__init__.py +18 -0
  27. pyqttoolkit/filesystem/__init__.py +24 -0
  28. pyqttoolkit/logs/__init__.py +140 -0
  29. pyqttoolkit/metadata/__init__.py +23 -0
  30. pyqttoolkit/metadata/dataframe_metadata_manager.py +105 -0
  31. pyqttoolkit/metadata/dict_metadata_manager.py +63 -0
  32. pyqttoolkit/metadata/id_generator.py +20 -0
  33. pyqttoolkit/metadata/metadata_manager_base.py +54 -0
  34. pyqttoolkit/models/__init__.py +26 -0
  35. pyqttoolkit/models/code_text.py +43 -0
  36. pyqttoolkit/models/dataframetable.py +135 -0
  37. pyqttoolkit/models/dictpropertytree.py +155 -0
  38. pyqttoolkit/models/dicttable.py +140 -0
  39. pyqttoolkit/models/json_text.py +52 -0
  40. pyqttoolkit/models/list_model.py +52 -0
  41. pyqttoolkit/models/module.py +26 -0
  42. pyqttoolkit/models/roles.py +23 -0
  43. pyqttoolkit/models/series_data.py +68 -0
  44. pyqttoolkit/models/span.py +36 -0
  45. pyqttoolkit/models/validation.py +27 -0
  46. pyqttoolkit/modules/__init__.py +18 -0
  47. pyqttoolkit/modules/events/__init__.py +19 -0
  48. pyqttoolkit/modules/events/add_signal.py +31 -0
  49. pyqttoolkit/modules/events/module_opening.py +21 -0
  50. pyqttoolkit/modules/events/rejectable.py +29 -0
  51. pyqttoolkit/modules/launcher_config.py +55 -0
  52. pyqttoolkit/modules/module.py +111 -0
  53. pyqttoolkit/properties/__init__.py +18 -0
  54. pyqttoolkit/properties/auto.py +221 -0
  55. pyqttoolkit/properties/throttled.py +77 -0
  56. pyqttoolkit/scripting/__init__.py +19 -0
  57. pyqttoolkit/scripting/script_context.py +36 -0
  58. pyqttoolkit/scripting/script_runner.py +132 -0
  59. pyqttoolkit/scripting/script_template.py +42 -0
  60. pyqttoolkit/services/__init__.py +32 -0
  61. pyqttoolkit/services/autosave.py +41 -0
  62. pyqttoolkit/services/email.py +25 -0
  63. pyqttoolkit/services/event_registry.py +52 -0
  64. pyqttoolkit/services/file_dialog.py +71 -0
  65. pyqttoolkit/services/hotkey_manager.py +137 -0
  66. pyqttoolkit/services/link_manager.py +135 -0
  67. pyqttoolkit/services/message_board.py +90 -0
  68. pyqttoolkit/services/module_service.py +154 -0
  69. pyqttoolkit/services/object_converter.py +45 -0
  70. pyqttoolkit/services/project_updater.py +149 -0
  71. pyqttoolkit/services/task_runner.py +281 -0
  72. pyqttoolkit/services/theme_manager.py +46 -0
  73. pyqttoolkit/services/tool_window_service.py +46 -0
  74. pyqttoolkit/viewmanagers/__init__.py +17 -0
  75. pyqttoolkit/viewmanagers/tool.py +23 -0
  76. pyqttoolkit/views/__init__.py +50 -0
  77. pyqttoolkit/views/bulk_value_selector.py +112 -0
  78. pyqttoolkit/views/check_box.py +36 -0
  79. pyqttoolkit/views/code/__init__.py +18 -0
  80. pyqttoolkit/views/code/code_edit.py +268 -0
  81. pyqttoolkit/views/code/python_syntax.py +226 -0
  82. pyqttoolkit/views/combo_box.py +101 -0
  83. pyqttoolkit/views/count_selector.py +59 -0
  84. pyqttoolkit/views/datetime.py +111 -0
  85. pyqttoolkit/views/datetime_range_selector.py +231 -0
  86. pyqttoolkit/views/default_text_line_edit.py +50 -0
  87. pyqttoolkit/views/delegates.py +117 -0
  88. pyqttoolkit/views/dtype_edit.py +117 -0
  89. pyqttoolkit/views/editable_combo_box.py +45 -0
  90. pyqttoolkit/views/file_selector.py +51 -0
  91. pyqttoolkit/views/icon.py +67 -0
  92. pyqttoolkit/views/icon_button.py +129 -0
  93. pyqttoolkit/views/json_edit.py +21 -0
  94. pyqttoolkit/views/labelled.py +29 -0
  95. pyqttoolkit/views/layout/__init__.py +21 -0
  96. pyqttoolkit/views/layout/collabsible.py +59 -0
  97. pyqttoolkit/views/layout/group_box.py +48 -0
  98. pyqttoolkit/views/layout/group_panel.py +188 -0
  99. pyqttoolkit/views/layout/lines.py +30 -0
  100. pyqttoolkit/views/layout/link.py +38 -0
  101. pyqttoolkit/views/line_edit.py +61 -0
  102. pyqttoolkit/views/linkable.py +49 -0
  103. pyqttoolkit/views/list_view.py +43 -0
  104. pyqttoolkit/views/main_window.py +91 -0
  105. pyqttoolkit/views/metadata_selector.py +84 -0
  106. pyqttoolkit/views/modulewindow.py +81 -0
  107. pyqttoolkit/views/plot/__init__.py +26 -0
  108. pyqttoolkit/views/plot/legend_control.py +121 -0
  109. pyqttoolkit/views/plot/matplotlib/__init__.py +23 -0
  110. pyqttoolkit/views/plot/matplotlib/base.py +1056 -0
  111. pyqttoolkit/views/plot/matplotlib/colorbar.py +75 -0
  112. pyqttoolkit/views/plot/matplotlib/colormap.py +48 -0
  113. pyqttoolkit/views/plot/matplotlib/font.py +55 -0
  114. pyqttoolkit/views/plot/plot_options.py +211 -0
  115. pyqttoolkit/views/plot/plot_toolbar_options.py +133 -0
  116. pyqttoolkit/views/plot/size.py +41 -0
  117. pyqttoolkit/views/plot/tool_type.py +26 -0
  118. pyqttoolkit/views/plot/toolbar.py +100 -0
  119. pyqttoolkit/views/popout.py +41 -0
  120. pyqttoolkit/views/popup.py +41 -0
  121. pyqttoolkit/views/property_tree.py +26 -0
  122. pyqttoolkit/views/shrunk_push_button.py +34 -0
  123. pyqttoolkit/views/steppable_combo_box.py +67 -0
  124. pyqttoolkit/views/styleable.py +37 -0
  125. pyqttoolkit/views/table.py +146 -0
  126. pyqttoolkit/views/text_edit.py +31 -0
  127. pyqttoolkit/views/tool_window.py +35 -0
  128. pyqttoolkit-0.9.1.dist-info/METADATA +69 -0
  129. pyqttoolkit-0.9.1.dist-info/RECORD +133 -0
  130. pyqttoolkit-0.9.1.dist-info/WHEEL +5 -0
  131. pyqttoolkit-0.9.1.dist-info/licenses/LICENSE.txt +674 -0
  132. pyqttoolkit-0.9.1.dist-info/licenses/LICENSE_SHORT +16 -0
  133. 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