fmtr.tools 1.1.1__py3-none-any.whl → 1.3.81__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 (67) hide show
  1. fmtr/tools/__init__.py +68 -52
  2. fmtr/tools/ai_tools/__init__.py +2 -2
  3. fmtr/tools/ai_tools/agentic_tools.py +151 -32
  4. fmtr/tools/ai_tools/inference_tools.py +2 -1
  5. fmtr/tools/api_tools.py +8 -5
  6. fmtr/tools/caching_tools.py +101 -3
  7. fmtr/tools/constants.py +33 -0
  8. fmtr/tools/context_tools.py +23 -0
  9. fmtr/tools/data_modelling_tools.py +227 -14
  10. fmtr/tools/database_tools/__init__.py +6 -0
  11. fmtr/tools/database_tools/document.py +51 -0
  12. fmtr/tools/datatype_tools.py +21 -1
  13. fmtr/tools/datetime_tools.py +12 -0
  14. fmtr/tools/debugging_tools.py +60 -0
  15. fmtr/tools/dns_tools/__init__.py +7 -0
  16. fmtr/tools/dns_tools/client.py +97 -0
  17. fmtr/tools/dns_tools/dm.py +257 -0
  18. fmtr/tools/dns_tools/proxy.py +66 -0
  19. fmtr/tools/dns_tools/server.py +138 -0
  20. fmtr/tools/docker_tools/__init__.py +6 -0
  21. fmtr/tools/entrypoints/__init__.py +0 -0
  22. fmtr/tools/entrypoints/cache_hfh.py +3 -0
  23. fmtr/tools/entrypoints/ep_test.py +2 -0
  24. fmtr/tools/entrypoints/install_yamlscript.py +8 -0
  25. fmtr/tools/{console_script_tools.py → entrypoints/remote_debug_test.py} +1 -6
  26. fmtr/tools/entrypoints/shell_debug.py +8 -0
  27. fmtr/tools/environment_tools.py +2 -2
  28. fmtr/tools/function_tools.py +77 -1
  29. fmtr/tools/google_api_tools.py +15 -4
  30. fmtr/tools/http_tools.py +26 -0
  31. fmtr/tools/inherit_tools.py +27 -0
  32. fmtr/tools/interface_tools/__init__.py +8 -0
  33. fmtr/tools/interface_tools/context.py +13 -0
  34. fmtr/tools/interface_tools/controls.py +354 -0
  35. fmtr/tools/interface_tools/interface_tools.py +189 -0
  36. fmtr/tools/iterator_tools.py +29 -0
  37. fmtr/tools/logging_tools.py +43 -16
  38. fmtr/tools/packaging_tools.py +14 -0
  39. fmtr/tools/path_tools/__init__.py +12 -0
  40. fmtr/tools/path_tools/app_path_tools.py +40 -0
  41. fmtr/tools/{path_tools.py → path_tools/path_tools.py} +156 -12
  42. fmtr/tools/path_tools/type_path_tools.py +3 -0
  43. fmtr/tools/pattern_tools.py +260 -0
  44. fmtr/tools/pdf_tools.py +39 -1
  45. fmtr/tools/settings_tools.py +23 -4
  46. fmtr/tools/setup_tools/__init__.py +8 -0
  47. fmtr/tools/setup_tools/setup_tools.py +447 -0
  48. fmtr/tools/string_tools.py +92 -13
  49. fmtr/tools/tabular_tools.py +61 -0
  50. fmtr/tools/tools.py +27 -2
  51. fmtr/tools/version +1 -1
  52. fmtr/tools/version_tools/__init__.py +12 -0
  53. fmtr/tools/version_tools/version_tools.py +51 -0
  54. fmtr/tools/webhook_tools.py +17 -0
  55. fmtr/tools/yaml_tools.py +66 -5
  56. {fmtr_tools-1.1.1.dist-info → fmtr_tools-1.3.81.dist-info}/METADATA +136 -54
  57. fmtr_tools-1.3.81.dist-info/RECORD +93 -0
  58. {fmtr_tools-1.1.1.dist-info → fmtr_tools-1.3.81.dist-info}/WHEEL +1 -1
  59. fmtr_tools-1.3.81.dist-info/entry_points.txt +6 -0
  60. fmtr_tools-1.3.81.dist-info/top_level.txt +1 -0
  61. fmtr/tools/docker_tools.py +0 -30
  62. fmtr/tools/interface_tools.py +0 -64
  63. fmtr/tools/version_tools.py +0 -62
  64. fmtr_tools-1.1.1.dist-info/RECORD +0 -65
  65. fmtr_tools-1.1.1.dist-info/entry_points.txt +0 -3
  66. fmtr_tools-1.1.1.dist-info/top_level.txt +0 -2
  67. {fmtr_tools-1.1.1.dist-info → fmtr_tools-1.3.81.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,27 @@
1
+ from typing import TypeVar, Generic
2
+
3
+ T = TypeVar("T")
4
+
5
+
6
+ class Inherit(Generic[T]):
7
+ """
8
+
9
+ Runtime inheritance. Acts like a wrapper around an instantiated base class of type T, and allows overriding methods in subclasses like regular inheritance.
10
+
11
+ """
12
+
13
+ def __init__(self, parent: T):
14
+ """
15
+
16
+ Set parent
17
+
18
+ """
19
+ object.__setattr__(self, "_parent", parent)
20
+
21
+ def __getattr__(self, name):
22
+ """
23
+
24
+ Since regular attribute access checks own methods first, we don't need to do anything fancy to fall back to the parent when not implemented.
25
+
26
+ """
27
+ return getattr(self._parent, name)
@@ -0,0 +1,8 @@
1
+ from fmtr.tools.import_tools import MissingExtraMockModule
2
+
3
+ try:
4
+ from fmtr.tools.interface_tools.interface_tools import Base, update, progress
5
+ from fmtr.tools.interface_tools import controls
6
+ from fmtr.tools.interface_tools.context import Context
7
+ except ModuleNotFoundError as exception:
8
+ Interface = update = progress = controls = MissingExtraMockModule('interface', exception)
@@ -0,0 +1,13 @@
1
+ from dataclasses import dataclass
2
+
3
+ from flet.core.page import Page
4
+
5
+
6
+ @dataclass
7
+ class Context:
8
+ """
9
+
10
+ Base context class
11
+
12
+ """
13
+ page: Page
@@ -0,0 +1,354 @@
1
+ import flet as ft
2
+ from dataclasses import dataclass
3
+ from flet.core.gesture_detector import TapEvent
4
+ from flet.core.page import Page
5
+ from flet.core.types import ColorValue, IconValue
6
+ from functools import cached_property
7
+ from typing import Optional
8
+
9
+ from fmtr.tools.logging_tools import logger
10
+
11
+
12
+ class SliderSteps(ft.Slider):
13
+ """
14
+
15
+ Slider control using step instead of divisions
16
+
17
+ """
18
+
19
+ def __init__(self, *args, min=10, max=100, step=10, **kwargs):
20
+ self.step = step
21
+ divisions = (max - min) // step
22
+ super().__init__(*args, min=min, max=max, divisions=divisions, **kwargs)
23
+
24
+
25
+ class Cell(ft.DataCell):
26
+ """
27
+
28
+ Context-aware, clickable data cell.
29
+
30
+ """
31
+
32
+ def __init__(self, series, column):
33
+ """
34
+
35
+ Store context
36
+
37
+ """
38
+ self.series = series
39
+ self.column = column
40
+ self.value = series[column]
41
+
42
+ super().__init__(self.gesture_detector)
43
+
44
+ @cached_property
45
+ def text(self):
46
+ """
47
+
48
+ Cell contents text
49
+
50
+ """
51
+
52
+ return ft.Text(str(self.value), color=self.color, bgcolor=self.bgcolor)
53
+
54
+ @property
55
+ def color(self):
56
+ """
57
+
58
+ Basic conditional formatting
59
+
60
+ """
61
+ if self.value is None:
62
+ return ft.Colors.GREY
63
+ else:
64
+ return None
65
+
66
+ @property
67
+ def bgcolor(self):
68
+ """
69
+
70
+ Basic conditional formatting
71
+
72
+ """
73
+ return None
74
+
75
+ @cached_property
76
+ def gesture_detector(self):
77
+ """
78
+
79
+ Make arbitrary content clickable
80
+
81
+ """
82
+ return ft.GestureDetector(content=self.text, on_tap=self.click_tap, on_double_tap=self.click_double_tap)
83
+
84
+ async def click_tap(self, event: TapEvent):
85
+ """
86
+
87
+ Default cell click behavior — override in subclass if needed
88
+
89
+ """
90
+ value = await self.click(event=event)
91
+ event.page.update()
92
+ return value
93
+
94
+ async def click(self, event: Optional[TapEvent] = None):
95
+ """
96
+
97
+ Default cell click behavior — override in subclass if needed
98
+
99
+ """
100
+ logger.info(f"Clicked {self.column=} {self.series.name=} {self.value=}")
101
+
102
+ async def click_double_tap(self, event: TapEvent):
103
+ """
104
+
105
+ Default cell click behavior — override in subclass if needed
106
+
107
+ """
108
+ value = await self.double_click(event=event)
109
+ event.page.update()
110
+ return value
111
+
112
+ async def double_click(self, event: Optional[TapEvent] = None):
113
+ """
114
+
115
+ Default cell double click behavior — override in subclass if needed
116
+
117
+ """
118
+ logger.info(f"Double-clicked {self.column=} {self.series.id=} {self.value=}")
119
+
120
+
121
+ class Row(ft.DataRow):
122
+ """
123
+
124
+ Instantiate a row from a series
125
+
126
+ """
127
+
128
+ TypesCells = {None: Cell}
129
+
130
+ def __init__(self, series):
131
+ self.series = series
132
+ super().__init__(self.cells_controls, color=self.row_color)
133
+
134
+ @cached_property
135
+ def cells_data(self) -> dict[str, list[Cell]]:
136
+ """
137
+
138
+ Cell controls lookup
139
+
140
+ """
141
+ data = {}
142
+ for col in self.series.index:
143
+ default = self.TypesCells[None]
144
+ TypeCell = self.TypesCells.get(col, default)
145
+ data.setdefault(col, []).append(TypeCell(self.series, col))
146
+ return data
147
+
148
+ @cached_property
149
+ def cells_controls(self) -> list[Cell]:
150
+ """
151
+
152
+ Flat list of controls
153
+
154
+ """
155
+ controls = []
156
+ for cells in self.cells_data.values():
157
+ for cell in cells:
158
+ controls.append(cell)
159
+ return controls
160
+
161
+ def __getitem__(self, item) -> list[Cell]:
162
+ """
163
+
164
+ Get cells by column name
165
+
166
+ """
167
+ return self.cells_data[item]
168
+
169
+ @property
170
+ def row_color(self):
171
+ """
172
+
173
+ Basic conditional formatting
174
+
175
+ """
176
+ return None
177
+
178
+
179
+ class Column(ft.DataColumn):
180
+ """
181
+
182
+ Column stub
183
+
184
+ """
185
+
186
+ def __init__(self, col_name: str):
187
+ """
188
+
189
+ Store context
190
+
191
+ """
192
+
193
+ self.col_name = col_name
194
+
195
+ super().__init__(label=self.text)
196
+
197
+ @cached_property
198
+ def text(self):
199
+ """
200
+
201
+ Cell contents text
202
+
203
+ """
204
+ return ft.Text(str(self.col_name), weight=self.weight)
205
+
206
+ @property
207
+ def weight(self):
208
+ """
209
+
210
+ Default bold headers
211
+
212
+ """
213
+ return ft.FontWeight.BOLD
214
+
215
+
216
+ class Table(ft.DataTable):
217
+ """
218
+
219
+ Dataframe with clickable cells
220
+
221
+ """
222
+
223
+ TypesRows = {None: Row}
224
+ TypesColumns = {None: Column}
225
+
226
+ def __init__(self, df): # todo move to submodule with tabular deps
227
+ """
228
+
229
+ Set columns/rows using relevant types
230
+
231
+ """
232
+ self.df = df
233
+ super().__init__(columns=self.columns_controls, rows=self.rows_controls)
234
+
235
+ @cached_property
236
+ def columns_data(self) -> dict[str, list[Column]]:
237
+ """
238
+
239
+ Columns controls lookup
240
+
241
+ """
242
+ data = {}
243
+ for col in self.df.columns:
244
+ default = self.TypesColumns[None]
245
+ TypeColumn = self.TypesColumns.get(col, default)
246
+ data.setdefault(col, []).append(TypeColumn(col))
247
+ return data
248
+
249
+ @cached_property
250
+ def columns_controls(self) -> list[Column]:
251
+ """
252
+
253
+ Flat list of controls
254
+
255
+ """
256
+ controls = []
257
+ for columns in self.columns_data.values():
258
+ for column in columns:
259
+ controls.append(column)
260
+ return controls
261
+
262
+ @cached_property
263
+ def rows_data(self) -> dict[str, list[Row]]:
264
+ """
265
+
266
+ Row controls lookup
267
+
268
+ """
269
+ data = {}
270
+ for index, row in self.df.iterrows():
271
+ default = self.TypesRows[None]
272
+ TypeRow = self.TypesRows.get(index, default)
273
+ data.setdefault(index, []).append(TypeRow(row))
274
+ return data
275
+
276
+ @cached_property
277
+ def rows_controls(self) -> list[Row]:
278
+ """
279
+
280
+ Flat list of controls
281
+
282
+ """
283
+ controls = []
284
+ for rows in self.rows_data.values():
285
+ for row in rows:
286
+ controls.append(row)
287
+ return controls
288
+
289
+ def __getitem__(self, item) -> list[Row]:
290
+ """
291
+
292
+ Get row by index
293
+
294
+ """
295
+
296
+ return self.rows_data[item]
297
+
298
+
299
+ @dataclass
300
+ class NotificationDatum:
301
+ """
302
+
303
+ Color and icon for notification bar
304
+
305
+ """
306
+ color: ColorValue
307
+ icon: IconValue
308
+
309
+
310
+ class NotificationBar(ft.SnackBar):
311
+ """
312
+
313
+ Combined logging and notification bar: infers the appropriate UI representation from the log level
314
+
315
+ """
316
+ DATA = {
317
+ logger.info: NotificationDatum(color=ft.Colors.BLUE, icon=ft.Icons.INFO),
318
+ logger.warning: NotificationDatum(color=ft.Colors.AMBER, icon=ft.Icons.WARNING),
319
+ logger.error: NotificationDatum(color=ft.Colors.RED, icon=ft.Icons.ERROR),
320
+ logger.debug: NotificationDatum(color=ft.Colors.GREY, icon=ft.Icons.BUG_REPORT),
321
+ logger.exception: NotificationDatum(color=ft.Colors.RED_ACCENT, icon=ft.Icons.REPORT),
322
+ }
323
+
324
+ def __init__(self, msg: str, method=logger.info):
325
+ """
326
+
327
+ Log the message immediately, otherwise configure notification bar icon/color.
328
+
329
+ """
330
+ self.msg = msg
331
+ self.method = method
332
+ self.method(msg)
333
+
334
+ icon = ft.Icon(self.data.icon, color=self.data.color)
335
+ text = ft.Text(self.msg)
336
+ content = ft.Row(controls=[icon, text])
337
+ super().__init__(content=content)
338
+
339
+ @cached_property
340
+ def data(self) -> NotificationDatum:
341
+ """
342
+
343
+ Fetching data using logging method.
344
+
345
+ """
346
+ return self.DATA[self.method]
347
+
348
+ def show(self, page: Page):
349
+ """
350
+
351
+ Show the notification on the relevant page.
352
+
353
+ """
354
+ page.open(self)
@@ -0,0 +1,189 @@
1
+ import os
2
+ from typing import TypeVar, Generic, Type
3
+
4
+ import flet as ft
5
+ from flet.core.control_event import ControlEvent
6
+ from flet.core.types import AppView
7
+ from flet.core.view import View
8
+
9
+ from fmtr.tools import environment_tools
10
+ from fmtr.tools.constants import Constants
11
+ from fmtr.tools.function_tools import MethodDecorator
12
+ from fmtr.tools.interface_tools.context import Context
13
+ from fmtr.tools.logging_tools import logger
14
+
15
+
16
+ class update(MethodDecorator):
17
+ """
18
+
19
+ Update the page after the decorated function is called.
20
+
21
+ """
22
+
23
+ def stop(self, instance, *args, **kwargs):
24
+ instance.page.update()
25
+
26
+
27
+ class progress(update):
28
+ """
29
+
30
+ Run the function while a progress indicator (e.g. spinner) is and within the object-defined context (e.g. logging span).
31
+
32
+ """
33
+
34
+ def start(self, instance, *args, **kwargs):
35
+ """
36
+
37
+ Make progress visible and update.
38
+
39
+ """
40
+ instance.progress.visible = True
41
+ instance.page.update()
42
+
43
+ def stop(self, instance, *args, **kwargs):
44
+ """
45
+
46
+ Make progress not visible and update.
47
+
48
+ """
49
+ instance.progress.visible = False
50
+ super().stop(instance)
51
+
52
+
53
+ T = TypeVar('T', bound=Context)
54
+
55
+
56
+ class Base(Generic[T], ft.Column):
57
+ """
58
+
59
+ Simple interface base class.
60
+
61
+ """
62
+ TITLE = 'Base Interface'
63
+ HOST = '0.0.0.0'
64
+ PORT = 8080
65
+ URL = Constants.FMTR_DEV_INTERFACE_URL if environment_tools.IS_DEV else None
66
+ APPVIEW = AppView.WEB_BROWSER
67
+ PATH_ASSETS = None
68
+ PATH_UPLOADS = None
69
+ SCROLL = ft.ScrollMode.AUTO
70
+
71
+ SECRET_KEY_KEY = 'FLET_SECRET_KEY'
72
+ ROUTE_ROOT = '/'
73
+
74
+ TypeContext: Type[T] = Context
75
+
76
+ @classmethod
77
+ async def new(cls, page: ft.Page):
78
+ """
79
+
80
+ Interface entry point/async constructor. Set relevant callbacks, and add instantiated self to page views.
81
+
82
+ Override this to work with `Context`, do async setup. Otherwise, override __init__ (which is regular Column __init__) for a simple interface.
83
+
84
+ """
85
+ page.scroll = cls.SCROLL
86
+ page.title = cls.TITLE
87
+ page.on_connect = cls.on_connect
88
+ page.on_disconnect = cls.on_disconnect
89
+ page.on_route_change = cls.route
90
+ page.on_view_pop = cls.pop
91
+ page.theme = cls.get_theme()
92
+
93
+ context = cls.TypeContext(page=page)
94
+ self = cls()
95
+ self.context = context
96
+
97
+ page.controls.append(self)
98
+ page.update()
99
+
100
+ return self
101
+
102
+ @classmethod
103
+ def route(cls, event: ft.RouteChangeEvent):
104
+ """
105
+
106
+ Overridable router.
107
+
108
+ """
109
+ logger.debug(f'Route change: {event=}')
110
+
111
+ @classmethod
112
+ def pop(cls, view: View, page: ft.Page):
113
+ """
114
+
115
+ Overridable view pop.
116
+
117
+ """
118
+ logger.debug(f'View popped: {page.route=} {len(page.views)=} {view=}')
119
+
120
+ @classmethod
121
+ def on_connect(cls, event: ControlEvent):
122
+ """
123
+
124
+ Log connections
125
+
126
+ """
127
+ page = event.control
128
+ logger.warning(f'Connect: {page.client_user_agent=} {page.platform.name=}')
129
+
130
+ @classmethod
131
+ def on_disconnect(cls, event: ControlEvent):
132
+ """
133
+
134
+ Log disconnections
135
+
136
+ """
137
+ page = event.control
138
+ logger.warning(f'Disconnect {page.client_user_agent=} {page.platform.name=}')
139
+
140
+
141
+ @classmethod
142
+ def get_theme(self):
143
+ """
144
+
145
+ Overridable theme definition
146
+
147
+ """
148
+ text_style = ft.TextStyle(size=20)
149
+ theme = ft.Theme(
150
+ text_theme=ft.TextTheme(body_large=text_style),
151
+ )
152
+ return theme
153
+
154
+ @classmethod
155
+ def launch(cls):
156
+ """
157
+
158
+ Launch via async constructor method
159
+
160
+ """
161
+
162
+ if cls.URL:
163
+ url = cls.URL
164
+ else:
165
+ url = f'http://{cls.HOST}:{cls.PORT}'
166
+
167
+ if not environment_tools.get(cls.SECRET_KEY_KEY, default=None):
168
+ os.environ["FLET_SECRET_KEY"] = os.urandom(12).hex()
169
+
170
+ logger.info(f"Launching {cls.TITLE} at {url}")
171
+ ft.app(cls.new, view=cls.APPVIEW, host=cls.HOST, port=cls.PORT, assets_dir=cls.PATH_ASSETS, upload_dir=cls.PATH_UPLOADS)
172
+
173
+
174
+ class Test(Base[Context]):
175
+ """
176
+
177
+ Simple test interface, showing typing example.
178
+
179
+ """
180
+ TypeContext: Type[Context] = Context
181
+
182
+ TITLE = 'Test Interface'
183
+
184
+ def __init__(self):
185
+ controls = [ft.Text(self.TITLE)]
186
+ super().__init__(controls=controls)
187
+
188
+ if __name__ == "__main__":
189
+ Test.launch()
@@ -1,6 +1,8 @@
1
1
  from itertools import chain, batched
2
2
  from typing import List, Dict, Any
3
3
 
4
+ from fmtr.tools.datatype_tools import is_none
5
+
4
6
 
5
7
  def enlist(value) -> List[Any]:
6
8
  """
@@ -52,3 +54,30 @@ def rebatch(batches, size: int):
52
54
 
53
55
  """
54
56
  return batched(chain.from_iterable(batches), size)
57
+
58
+
59
+ def strip_none(*items):
60
+ """
61
+
62
+ Remove nones from a list of arguments
63
+
64
+ """
65
+ return [item for item in items if not is_none(item)]
66
+
67
+
68
+ def dedupe(items):
69
+ """
70
+
71
+ Deduplicate a list of items, retaining order
72
+
73
+ """
74
+ return list(dict.fromkeys(items))
75
+
76
+
77
+ def get_class_lookup(*classes, name_function=lambda cls: cls.__name__):
78
+ """
79
+
80
+ Dictionary of class names to classes
81
+
82
+ """
83
+ return {name_function(cls): cls for cls in classes}