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,20 @@
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
+ from ._version import get_versions
19
+ __version__ = get_versions()['version']
20
+ del get_versions
@@ -0,0 +1,21 @@
1
+
2
+ # This file was generated by 'versioneer.py' (0.18) from
3
+ # revision-control system data, or from the parent directory name of an
4
+ # unpacked source archive. Distribution tarballs contain a pre-generated copy
5
+ # of this file.
6
+
7
+ import json
8
+
9
+ version_json = '''
10
+ {
11
+ "date": "2026-10-20T09:25:30+0100",
12
+ "dirty": false,
13
+ "error": null,
14
+ "full-revisionid": "06c8bebf565d6ca91beb9a0fb7d8b171ebfd80fc",
15
+ "version": "0.9.1"
16
+ }
17
+ ''' # END VERSION_JSON
18
+
19
+
20
+ def get_versions():
21
+ return json.loads(version_json)
@@ -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
+ from .application import Application
18
+ from .garbage_collector import GarbageCollector
@@ -0,0 +1,86 @@
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 sys
18
+ import logging
19
+
20
+ from PyQt5.QtCore import pyqtSignal
21
+ from PyQt5.QtWidgets import QApplication, QWidget
22
+
23
+ from pyqttoolkit.services import MessageBoard, TaskRunner, ToolWindowService, EventRegistry, ObjectConverter
24
+ from pyqttoolkit.dependencies import DependencyContainer
25
+ from .garbage_collector import GarbageCollector
26
+
27
+ LOGGER = logging.getLogger(__name__)
28
+
29
+ class Application(QApplication):
30
+ def __init__(self, argv):
31
+ QApplication.__init__(self, argv)
32
+ self._dependency_container = DependencyContainer()
33
+ self._garbage_collector = GarbageCollector(self)
34
+
35
+ self._module_service = None
36
+ self._message_board = None
37
+ self._project_manager = None
38
+
39
+ self._handling_exception = False
40
+
41
+ sys.excepthook = self._handle_exception
42
+
43
+ mainWindowLoaded = pyqtSignal(QWidget)
44
+
45
+ def run(self, filename=None):
46
+ LOGGER.info('Running Application')
47
+ self._register_default_services()
48
+ self._register_modules()
49
+
50
+ if filename:
51
+ self._project_manager.load_project(filename)
52
+
53
+ self.mainWindowLoaded.emit(self._module_service.openModule('Base'))
54
+ return self.exec_()
55
+
56
+ def _handle_exception(self, ex_type, ex_value, traceback_obj):
57
+ LOGGER.error('Application Exception: %s, %s', ex_type, ex_value)
58
+ if self._handling_exception:
59
+ return
60
+ self._handling_exception = True
61
+ self._perform_exception_handling(ex_type, ex_value, traceback_obj)
62
+ self._handling_exception = False
63
+
64
+ def _perform_exception_handling(self, ex_type, ex_value, traceback_obj):
65
+ pass
66
+
67
+ def _register_default_services(self):
68
+ self._dependency_container.register_instance('application', self)
69
+ self._dependency_container.register_instance('dependency_container', self._dependency_container)
70
+ self._dependency_container.register_instance('task_runner', TaskRunner(self))
71
+ self._dependency_container.register_instance('message_board', MessageBoard(self))
72
+ self._dependency_container.register_instance('tool_window_service', ToolWindowService(self._dependency_container))
73
+ self._dependency_container.register_instance('event_registry', EventRegistry())
74
+ self._dependency_container.register_instance('object_converter', ObjectConverter())
75
+ self._dependency_container.register_instance('garbage_collector', self._garbage_collector)
76
+
77
+ def _register_modules(self):
78
+ modules = self._get_modules()
79
+ for module_type in [v for k, v in modules.__dict__.items() if k.endswith('Module')]:
80
+ module = self._dependency_container.resolve(module_type)
81
+ module.register(dependency_container=self._dependency_container)
82
+
83
+ self._module_service = self._dependency_container.get_instance('module_service')
84
+
85
+ self._message_board = self._dependency_container.get_instance('message_board')
86
+ self._project_manager = self._dependency_container.get_instance('project_manager')
@@ -0,0 +1,65 @@
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 gc
18
+ import logging
19
+
20
+ from PyQt5.QtCore import QObject, QTimer
21
+
22
+ from pyqttoolkit.logs import TRACE
23
+
24
+ LOGGER = logging.getLogger(__name__)
25
+
26
+ class GarbageCollector(QObject):
27
+ '''
28
+ Disable automatic garbage collection and instead collect manually
29
+ every INTERVAL milliseconds.
30
+
31
+ This is done to ensure that garbage collection only happens in the GUI
32
+ thread, as otherwise Qt can crash.
33
+ '''
34
+
35
+ def __init__(self, parent, interval=60000):
36
+ super().__init__(parent)
37
+ self._interval = interval
38
+ self._threshold = gc.get_threshold()
39
+ gc.disable()
40
+
41
+ if self._interval:
42
+ self._timer = QTimer(self)
43
+ self._timer.timeout.connect(self.check)
44
+ self._timer.start(self._interval)
45
+ else:
46
+ self._timer = None
47
+
48
+ def check(self, full=False):
49
+ l0, l1, l2 = gc.get_count()
50
+ LOGGER.log(TRACE, 'gc_check called: l0=%d, l1=%d, l2=%d', l0, l1, l2)
51
+ if full or l0 > self._threshold[0]:
52
+ num = gc.collect(0)
53
+ LOGGER.log(TRACE, 'Collecting gen 0, found: %d unreachable', num)
54
+ if full or l1 > self._threshold[1]:
55
+ num = gc.collect(1)
56
+ LOGGER.log(TRACE, 'Collecting gen 1, found: %d unreachable', num)
57
+ if full or l2 > self._threshold[2]:
58
+ num = gc.collect(2)
59
+ LOGGER.log(TRACE, 'Collecting gen 2, found: %d unreachable', num)
60
+
61
+ def debug_cycles(self):
62
+ gc.set_debug(gc.DEBUG_SAVEALL)
63
+ gc.collect()
64
+ for obj in gc.garbage:
65
+ LOGGER.log(TRACE, "gc: obj=%s, (%s), type=%s", obj, repr(obj), type(obj))
@@ -0,0 +1,84 @@
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
+
19
+ from itertools import chain
20
+ from math import ceil
21
+
22
+ def hex_to_rgb(hex_value):
23
+ hex_value = hex_value.lstrip('#')
24
+ return tuple(int(hex_value[i:i+2], 16) / 255 for i in [0, 2, 4])
25
+
26
+ def rgb_to_hex(rgb_value):
27
+ return '#' + ''.join('{:02x}'.format(int(v * 255)) for v in rgb_value)
28
+
29
+ def interpolate_rgb(a, b, n):
30
+ if n <= 0:
31
+ return [a, b]
32
+ step = tuple((b_val - a_val) / (n + 1) for a_val, b_val in zip(a, b))
33
+ return [tuple(a_val + step_val * i for a_val, step_val in zip(a, step)) for i in range(1, n+1)]
34
+
35
+ def expand_rgb(colors, n):
36
+ extras_per_gap = int(ceil((n - len(colors)) / (len(colors) - 1)))
37
+ if extras_per_gap <= 0:
38
+ return list(colors)
39
+ result = [*chain(
40
+ *([a, *interpolate_rgb(a, b, extras_per_gap)] for a, b in zip(colors, colors[1:]))
41
+ ), colors[-1]]
42
+ return result
43
+
44
+ class ColorFormat(Enum):
45
+ rgba_string_256 = 0
46
+ rgb_string_256 = 1
47
+ hex_string = 2
48
+ hexa_string = 3
49
+
50
+ def format_color(color, color_format=ColorFormat.rgba_string_256, opacity=None, lighten=None):
51
+ if len(color) == 4:
52
+ r, g, b, a = color
53
+ elif len(color) == 3:
54
+ r, g, b = color
55
+ a = 255
56
+ else:
57
+ raise ValueError(f'Invalid value for color: {color}')
58
+ a = a if opacity is None else opacity
59
+
60
+ if lighten is not None:
61
+ r, g, b = redistribute_rgb(lighten * (r or 100), lighten * (g or 100), lighten * (b or 100))
62
+
63
+ if color_format == ColorFormat.rgba_string_256:
64
+ return f'rgba({r},{g},{b},{a})'
65
+ if color_format == ColorFormat.rgb_string_256:
66
+ return f'rgb({r},{g},{b})'
67
+ if color_format == ColorFormat.hex_string:
68
+ return '#{:02x}{:02x}{:02x}'.format(*(int(c * 255) for c in (r, g, b)))
69
+ if color_format == ColorFormat.hexa_string:
70
+ return '#{:02x}{:02x}{:02x}{:02x}'.format(*(int(c * 255) for c in (a, r, g, b)))
71
+
72
+ raise ValueError(f'Invalid format: {color_format}')
73
+
74
+ def redistribute_rgb(r, g, b):
75
+ threshold = 255.999
76
+ m = max(r, g, b)
77
+ if m <= threshold:
78
+ return int(r), int(g), int(b)
79
+ total = r + g + b
80
+ if total >= 3 * threshold:
81
+ return int(threshold), int(threshold), int(threshold)
82
+ x = (3 * threshold - total) / (3 * m - total)
83
+ gray = threshold - x * m
84
+ return int(gray + x * r), int(gray + x * g), int(gray + x * b)
@@ -0,0 +1,20 @@
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 .default import DefaultApplicationConfiguration
18
+ from .multi_json_file import MultiJsonFileApplicationConfiguration
19
+ from .composite import CompositeApplicationConfiguration
20
+ from .environment import EnvironmentApplicationConfiguration
@@ -0,0 +1,22 @@
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 BaseApplicationConfiguration:
18
+ def get_value(self, key):
19
+ raise NotImplementedError()
20
+
21
+ def set_value(self, key, value):
22
+ raise NotImplementedError()
@@ -0,0 +1,31 @@
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 .base import BaseApplicationConfiguration
18
+
19
+ class CompositeApplicationConfiguration(BaseApplicationConfiguration):
20
+ def __init__(self, *inners):
21
+ self._inners = inners
22
+
23
+ def get_value(self, key):
24
+ for inner in self._inners:
25
+ value = inner.get_value(key)
26
+ if value is not None:
27
+ return value
28
+ return None
29
+
30
+ def set_value(self, key, value):
31
+ self._inners[0].set_value(key, value)
@@ -0,0 +1,27 @@
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 .base import BaseApplicationConfiguration
18
+
19
+ class DefaultApplicationConfiguration(BaseApplicationConfiguration):
20
+ def __init__(self, config):
21
+ self._configuration = config
22
+
23
+ def get_value(self, key):
24
+ return self._configuration.get(key)
25
+
26
+ def set_value(self, key, value):
27
+ self._configuration[key] = value
@@ -0,0 +1,35 @@
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 os
18
+ import json
19
+ from .base import BaseApplicationConfiguration
20
+
21
+ class EnvironmentApplicationConfiguration(BaseApplicationConfiguration):
22
+ def get_value(self, key):
23
+ value = os.getenv(self._format_key(key))
24
+ if value is None:
25
+ return value
26
+ try:
27
+ return json.loads(value)
28
+ except json.decoder.JSONDecodeError:
29
+ return value
30
+
31
+ def set_value(self, key, value):
32
+ os.environ[self._format_key(key)] = str(value)
33
+
34
+ def _format_key(self, key):
35
+ return key.replace('.', '_').upper()
@@ -0,0 +1,93 @@
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 os import path, makedirs
18
+ from json import load, dump, JSONDecodeError
19
+ import logging
20
+ import shutil
21
+
22
+ from .base import BaseApplicationConfiguration
23
+
24
+ LOGGER = logging.getLogger(__name__)
25
+
26
+ class MultiJsonFileApplicationConfiguration(BaseApplicationConfiguration):
27
+ def __init__(self, directory):
28
+ self._directory = directory
29
+
30
+ def get_value(self, key):
31
+ parts = key.split('.')
32
+ if len(parts) < 2:
33
+ raise ValueError('key')
34
+
35
+ filename = path.join(self._directory, f'{parts[0]}.json')
36
+ value_path = parts[1:]
37
+
38
+ if not path.exists(filename):
39
+ return None
40
+
41
+ try:
42
+ with open(filename) as fp:
43
+ values = load(fp)
44
+ except JSONDecodeError as err:
45
+ LOGGER.warning('Invalid json file, exception: %s', err)
46
+ self._backup_file(filename)
47
+ return None
48
+
49
+ result = values
50
+ for p in value_path:
51
+ result = result.get(p, {})
52
+
53
+ if result == {}:
54
+ return None
55
+
56
+ return result
57
+
58
+ def _backup_file(self, filename):
59
+ backup_path_root = path.join(path.dirname(filename), f'{path.basename(filename)}.json.bak')
60
+ attempt = 0
61
+ backup_path = backup_path_root
62
+ while path.isfile(backup_path):
63
+ attempt += 1
64
+ backup_path = backup_path_root + f'.{attempt}'
65
+ LOGGER.warning('Backing up config file %s to %s', filename, backup_path)
66
+ shutil.move(filename, backup_path)
67
+
68
+ def set_value(self, key, value):
69
+ parts = key.split('.')
70
+ if len(parts) < 2:
71
+ raise ValueError('key')
72
+
73
+ filename = path.join(self._directory, f'{parts[0]}.json')
74
+ if path.exists(filename):
75
+ try:
76
+ with open(filename) as fp:
77
+ current_config = load(fp)
78
+ except JSONDecodeError as err:
79
+ LOGGER.warning('Invalid json file, exception: %s', err)
80
+ current_config = {}
81
+ else:
82
+ current_config = {}
83
+
84
+ parent = current_config
85
+ for p in parts[1:-1]:
86
+ parent = current_config.setdefault(p, {})
87
+ parent[parts[-1]] = value
88
+
89
+ if not path.isdir(path.dirname(filename)):
90
+ makedirs(path.dirname(filename))
91
+
92
+ with open(filename, 'w') as fp:
93
+ dump(current_config, fp, indent=4, sort_keys=True)
@@ -0,0 +1,30 @@
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 pyqtSignal, QObject
18
+
19
+ class WithValueChangedConfiguration(QObject):
20
+ def __init__(self, inner):
21
+ self._inner = inner
22
+
23
+ onValueChanged = pyqtSignal(str, object)
24
+
25
+ def get_value(self, key):
26
+ return self._inner.get_value(key)
27
+
28
+ def set_value(self, key, value):
29
+ self._inner.set_value(key, value)
30
+ self.onValueChanged(key, value)
@@ -0,0 +1,31 @@
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 pyqtSignal, QObject
18
+
19
+ class WithValueChangedConfiguration(QObject):
20
+ def __init__(self, parent, inner):
21
+ super().__init__(parent)
22
+ self._inner = inner
23
+
24
+ onValueChanged = pyqtSignal(str, object)
25
+
26
+ def get_value(self, key):
27
+ return self._inner.get_value(key)
28
+
29
+ def set_value(self, key, value):
30
+ self._inner.set_value(key, value)
31
+ self.onValueChanged.emit(key, value)
@@ -0,0 +1,21 @@
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 .cache import Cache
18
+ from .interval import calculate_interval
19
+ from .bits import get_next_available_bit
20
+ from .names import get_next_available_name
21
+ from .message import MessageType, MessageResponse
@@ -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
+ def get_next_available_bit(bits):
18
+ bit = 1
19
+ for _ in range(63):
20
+ if not bit in bits:
21
+ return bit
22
+ bit <<= 1
23
+ return 0