fmtr.tools 1.3.7__tar.gz → 1.3.9__tar.gz

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.

Potentially problematic release.


This version of fmtr.tools might be problematic. Click here for more details.

Files changed (92) hide show
  1. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/PKG-INFO +44 -42
  2. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/caching_tools.py +101 -3
  3. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/constants.py +4 -0
  4. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/dns_tools/dm.py +41 -5
  5. fmtr_tools-1.3.9/fmtr/tools/dns_tools/proxy.py +57 -0
  6. fmtr_tools-1.3.9/fmtr/tools/dns_tools/server.py +94 -0
  7. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/logging_tools.py +1 -1
  8. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/path_tools/path_tools.py +10 -0
  9. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/pattern_tools.py +5 -4
  10. fmtr_tools-1.3.9/fmtr/tools/version +1 -0
  11. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr.tools.egg-info/PKG-INFO +44 -42
  12. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr.tools.egg-info/requires.txt +43 -41
  13. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/setup.py +1 -1
  14. fmtr_tools-1.3.7/fmtr/tools/dns_tools/proxy.py +0 -67
  15. fmtr_tools-1.3.7/fmtr/tools/dns_tools/server.py +0 -39
  16. fmtr_tools-1.3.7/fmtr/tools/version +0 -1
  17. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/LICENSE +0 -0
  18. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/README.md +0 -0
  19. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/__init__.py +0 -0
  20. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/ai_tools/__init__.py +0 -0
  21. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/ai_tools/agentic_tools.py +0 -0
  22. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/ai_tools/inference_tools.py +0 -0
  23. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/api_tools.py +0 -0
  24. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/async_tools.py +0 -0
  25. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/augmentation_tools.py +0 -0
  26. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/data_modelling_tools.py +0 -0
  27. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/dataclass_tools.py +0 -0
  28. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/datatype_tools.py +0 -0
  29. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/debugging_tools.py +0 -0
  30. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/dns_tools/__init__.py +0 -0
  31. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/dns_tools/client.py +0 -0
  32. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/docker_tools.py +0 -0
  33. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/entrypoints/__init__.py +0 -0
  34. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/entrypoints/cache_hfh.py +0 -0
  35. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/entrypoints/ep_test.py +0 -0
  36. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/entrypoints/install_yamlscript.py +0 -0
  37. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/entrypoints/remote_debug_test.py +0 -0
  38. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/entrypoints/shell_debug.py +0 -0
  39. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/environment_tools.py +0 -0
  40. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/function_tools.py +0 -0
  41. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/google_api_tools.py +0 -0
  42. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/hash_tools.py +0 -0
  43. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/hfh_tools.py +0 -0
  44. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/html_tools.py +0 -0
  45. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/http_tools.py +0 -0
  46. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/import_tools.py +0 -0
  47. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/inspection_tools.py +0 -0
  48. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/interface_tools.py +0 -0
  49. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/iterator_tools.py +0 -0
  50. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/json_fix_tools.py +0 -0
  51. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/json_tools.py +0 -0
  52. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/merging_tools.py +0 -0
  53. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/metric_tools.py +0 -0
  54. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/name_tools.py +0 -0
  55. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/netrc_tools.py +0 -0
  56. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/openai_tools.py +0 -0
  57. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/packaging_tools.py +0 -0
  58. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/parallel_tools.py +0 -0
  59. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/path_tools/__init__.py +0 -0
  60. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/path_tools/app_path_tools.py +0 -0
  61. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/path_tools/type_path_tools.py +0 -0
  62. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/pdf_tools.py +0 -0
  63. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/platform_tools.py +0 -0
  64. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/process_tools.py +0 -0
  65. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/profiling_tools.py +0 -0
  66. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/random_tools.py +0 -0
  67. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/semantic_tools.py +0 -0
  68. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/settings_tools.py +0 -0
  69. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/setup_tools/__init__.py +0 -0
  70. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/setup_tools/setup_tools.py +0 -0
  71. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/spaces_tools.py +0 -0
  72. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/string_tools.py +0 -0
  73. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/tabular_tools.py +0 -0
  74. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/tests/__init__.py +0 -0
  75. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/tests/conftest.py +0 -0
  76. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/tests/helpers.py +0 -0
  77. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/tests/test_datatype.py +0 -0
  78. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/tests/test_environment.py +0 -0
  79. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/tests/test_json.py +0 -0
  80. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/tests/test_path.py +0 -0
  81. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/tests/test_yaml.py +0 -0
  82. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/tokenization_tools.py +0 -0
  83. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/tools.py +0 -0
  84. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/unicode_tools.py +0 -0
  85. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/version_tools.py +0 -0
  86. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr/tools/yaml_tools.py +0 -0
  87. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr.tools.egg-info/SOURCES.txt +0 -0
  88. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr.tools.egg-info/dependency_links.txt +0 -0
  89. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr.tools.egg-info/entry_points.txt +0 -0
  90. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/fmtr.tools.egg-info/top_level.txt +0 -0
  91. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/pyproject.toml +0 -0
  92. {fmtr_tools-1.3.7 → fmtr_tools-1.3.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmtr.tools
3
- Version: 1.3.7
3
+ Version: 1.3.9
4
4
  Summary: Collection of high-level tools to simplify everyday development tasks, with a focus on AI/ML
5
5
  Home-page: https://github.com/fmtr/fmtr.tools
6
6
  Author: Frontmatter
@@ -97,6 +97,7 @@ Requires-Dist: google-auth-httplib2; extra == "google-api"
97
97
  Requires-Dist: google-api-python-client; extra == "google-api"
98
98
  Provides-Extra: caching
99
99
  Requires-Dist: diskcache; extra == "caching"
100
+ Requires-Dist: cachetools; extra == "caching"
100
101
  Provides-Extra: pdf
101
102
  Requires-Dist: pymupdf; extra == "pdf"
102
103
  Requires-Dist: pydantic; extra == "pdf"
@@ -130,60 +131,61 @@ Requires-Dist: logfire[httpx]; extra == "http"
130
131
  Provides-Extra: setup
131
132
  Requires-Dist: setuptools; extra == "setup"
132
133
  Provides-Extra: all
133
- Requires-Dist: dnspython[doh]; extra == "all"
134
- Requires-Dist: pymupdf4llm; extra == "all"
135
- Requires-Dist: regex; extra == "all"
136
134
  Requires-Dist: uvicorn[standard]; extra == "all"
137
- Requires-Dist: tokenizers; extra == "all"
138
- Requires-Dist: filetype; extra == "all"
139
- Requires-Dist: flet[all]; extra == "all"
140
- Requires-Dist: Unidecode; extra == "all"
141
- Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
142
- Requires-Dist: logfire[httpx]; extra == "all"
143
- Requires-Dist: bokeh; extra == "all"
144
- Requires-Dist: fastapi; extra == "all"
145
- Requires-Dist: pytest-cov; extra == "all"
146
135
  Requires-Dist: json_repair; extra == "all"
147
- Requires-Dist: semver; extra == "all"
148
- Requires-Dist: google-auth-oauthlib; extra == "all"
149
- Requires-Dist: google-auth-httplib2; extra == "all"
150
- Requires-Dist: html2text; extra == "all"
151
- Requires-Dist: sre_yield; extra == "all"
152
- Requires-Dist: pymupdf; extra == "all"
153
- Requires-Dist: flet-video; extra == "all"
154
- Requires-Dist: transformers[sentencepiece]; extra == "all"
155
- Requires-Dist: pydantic-settings; extra == "all"
156
- Requires-Dist: ollama; extra == "all"
157
- Requires-Dist: distributed; extra == "all"
158
136
  Requires-Dist: httpx; extra == "all"
159
- Requires-Dist: google-auth; extra == "all"
137
+ Requires-Dist: openai; extra == "all"
160
138
  Requires-Dist: openpyxl; extra == "all"
161
- Requires-Dist: logfire[fastapi]; extra == "all"
139
+ Requires-Dist: flet[all]; extra == "all"
140
+ Requires-Dist: regex; extra == "all"
162
141
  Requires-Dist: tabulate; extra == "all"
163
142
  Requires-Dist: tinynetrc; extra == "all"
164
- Requires-Dist: torchvision; extra == "all"
165
- Requires-Dist: sentence_transformers; extra == "all"
166
- Requires-Dist: pyyaml; extra == "all"
167
- Requires-Dist: diskcache; extra == "all"
168
- Requires-Dist: faker; extra == "all"
169
- Requires-Dist: docker; extra == "all"
143
+ Requires-Dist: filetype; extra == "all"
144
+ Requires-Dist: google-api-python-client; extra == "all"
170
145
  Requires-Dist: yamlscript; extra == "all"
146
+ Requires-Dist: tokenizers; extra == "all"
147
+ Requires-Dist: deepmerge; extra == "all"
148
+ Requires-Dist: sre_yield; extra == "all"
149
+ Requires-Dist: diskcache; extra == "all"
150
+ Requires-Dist: transformers[sentencepiece]; extra == "all"
151
+ Requires-Dist: google-auth-oauthlib; extra == "all"
152
+ Requires-Dist: Unidecode; extra == "all"
153
+ Requires-Dist: sentence_transformers; extra == "all"
154
+ Requires-Dist: logfire; extra == "all"
155
+ Requires-Dist: logfire[fastapi]; extra == "all"
171
156
  Requires-Dist: pydantic; extra == "all"
172
- Requires-Dist: contexttimer; extra == "all"
157
+ Requires-Dist: semver; extra == "all"
158
+ Requires-Dist: dnspython[doh]; extra == "all"
173
159
  Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
174
- Requires-Dist: appdirs; extra == "all"
175
- Requires-Dist: flet-webview; extra == "all"
176
- Requires-Dist: dask[bag]; extra == "all"
177
- Requires-Dist: openai; extra == "all"
160
+ Requires-Dist: google-auth-httplib2; extra == "all"
161
+ Requires-Dist: pymupdf4llm; extra == "all"
162
+ Requires-Dist: html2text; extra == "all"
163
+ Requires-Dist: google-auth; extra == "all"
164
+ Requires-Dist: pydantic-settings; extra == "all"
178
165
  Requires-Dist: huggingface_hub; extra == "all"
179
- Requires-Dist: torchaudio; extra == "all"
166
+ Requires-Dist: pyyaml; extra == "all"
167
+ Requires-Dist: torchvision; extra == "all"
168
+ Requires-Dist: pymupdf; extra == "all"
169
+ Requires-Dist: fastapi; extra == "all"
170
+ Requires-Dist: logfire[httpx]; extra == "all"
171
+ Requires-Dist: contexttimer; extra == "all"
180
172
  Requires-Dist: httpx_retries; extra == "all"
173
+ Requires-Dist: dask[bag]; extra == "all"
174
+ Requires-Dist: peft; extra == "all"
175
+ Requires-Dist: distributed; extra == "all"
176
+ Requires-Dist: docker; extra == "all"
177
+ Requires-Dist: faker; extra == "all"
178
+ Requires-Dist: cachetools; extra == "all"
179
+ Requires-Dist: torchaudio; extra == "all"
181
180
  Requires-Dist: setuptools; extra == "all"
182
- Requires-Dist: logfire; extra == "all"
183
- Requires-Dist: deepmerge; extra == "all"
181
+ Requires-Dist: pytest-cov; extra == "all"
182
+ Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
183
+ Requires-Dist: flet-video; extra == "all"
184
+ Requires-Dist: flet-webview; extra == "all"
185
+ Requires-Dist: appdirs; extra == "all"
184
186
  Requires-Dist: pandas; extra == "all"
185
- Requires-Dist: google-api-python-client; extra == "all"
186
- Requires-Dist: peft; extra == "all"
187
+ Requires-Dist: bokeh; extra == "all"
188
+ Requires-Dist: ollama; extra == "all"
187
189
  Dynamic: author
188
190
  Dynamic: author-email
189
191
  Dynamic: description
@@ -1,6 +1,10 @@
1
+ import cachetools
2
+ from datetime import timedelta, datetime
1
3
  from diskcache import Cache
2
4
 
3
- from fmtr.tools import logger, Path
5
+ from fmtr.tools.constants import Constants
6
+ from fmtr.tools.logging_tools import logger
7
+ from fmtr.tools.path_tools.path_tools import Path
4
8
 
5
9
 
6
10
  class Dump(dict):
@@ -31,9 +35,9 @@ class Disk(Cache):
31
35
  if not path.parent.exists():
32
36
  raise FileNotFoundError(f"Directory {path.parent=} does not exist")
33
37
  if path and not path.exists():
34
- logger.warning(f'Cache does not exist. Will be created. "{path=}"...')
38
+ logger.warning(f'Cache does not exist. Will be created. {str(path)=}...')
35
39
 
36
- logger.info(f'Initializing Disk Cache at path "{path=}"...')
40
+ logger.info(f'Initializing Disk Cache {str(path)=}...')
37
41
 
38
42
  super().__init__(directory=str(path / self.ROOT_KEY), **settings)
39
43
 
@@ -95,7 +99,101 @@ class Disk(Cache):
95
99
  return self.dump()
96
100
 
97
101
 
102
+ class TLRU(cachetools.TLRUCache):
103
+ """
104
+
105
+ Subclass to include logging and simplify global TTU
106
+
107
+ """
108
+ MASK_MAPPING = '{key} ' + Constants.ARROW + ' {value}'
109
+
110
+ def __init__(self, maxsize=1_024, timer=datetime.now, getsizeof=None, ttu_static=None, desc=None):
111
+ """
112
+
113
+ Add overridable TTU method
114
+
115
+ """
116
+ super().__init__(maxsize=maxsize, ttu=self.get_ttu, timer=timer, getsizeof=getsizeof)
117
+ self.ttu_static = ttu_static
118
+ self.desc = desc
119
+
120
+ @property
121
+ def cache_desc(self):
122
+ """
123
+
124
+ Friendly description of cache
125
+
126
+ """
127
+ desc = self.desc or self.__class__.__name__
128
+ return desc
129
+
130
+ def get_ttu(self, _key, value, now) -> float | timedelta:
131
+ """
132
+
133
+ Default implementation just adds on the static TTU
134
+
135
+ """
136
+ return now + self.ttu_static
137
+
138
+ def expire(self, time=None):
139
+ """
140
+
141
+ Log expiry
142
+
143
+ """
144
+ items = super().expire(time)
145
+ if not items:
146
+ return items
147
+
148
+ with logger.span(f'{self.desc} cache expiry {len(items)=}...'):
149
+ for key, value in items:
150
+ logger.debug(self.MASK_MAPPING.format(key=key, value=value))
151
+
152
+ return items
153
+
154
+ def popitem(self):
155
+ """
156
+
157
+ Log eviction
158
+
159
+ """
160
+ key, value = super().popitem()
161
+ logger.debug(f'{self.desc} cache eviction: {self.MASK_MAPPING.format(key=key, value=value)}')
162
+ return key, value
163
+
164
+ def dump(self):
165
+ """
166
+
167
+ Dump contents
168
+
169
+ """
170
+ data = Dump(self.items())
171
+ return data
172
+
173
+ @property
174
+ def data(self):
175
+ """
176
+
177
+ Dump as property
178
+
179
+ """
180
+ return self.dump()
181
+
182
+
183
+
98
184
  if __name__ == '__main__':
185
+ sec10 = timedelta(seconds=10)
186
+ c = TLRU(ttu_static=sec10, maxsize=2, desc='Test Data')
187
+ c['test'] = 'val'
188
+ c['test2'] = 'val2'
189
+ c['test3'] = 'val3'
190
+ c
191
+
192
+
193
+
194
+
195
+
196
+
99
197
  path_tmp_cache = Path.cwd().parent.parent / 'data' / 'cache'
100
198
  tc = Disk(path_tmp_cache)
101
199
 
@@ -14,6 +14,9 @@ class Constants:
14
14
  DATETIME_NOW_STR = DATETIME_NOW.strftime(DATETIME_FILENAME_FORMAT)
15
15
  SERIALIZATION_INDENT = 4
16
16
 
17
+ ARROW = '→'
18
+ ARROW_SEP = f' {ARROW} '
19
+
17
20
  FMTR_LOG_LEVEL_KEY = 'FMTR_LOG_LEVEL'
18
21
  FMTR_OBS_API_KEY_KEY = 'FMTR_OBS_API_KEY'
19
22
  FMTR_OBS_HOST = 'obs.sv.fmtr.dev'
@@ -31,6 +34,7 @@ class Constants:
31
34
  FILENAME_CONFIG = 'settings.yaml'
32
35
  DIR_NAME_REPO = 'repo'
33
36
  DIR_NAME_DATA = 'data'
37
+ DIR_NAME_CACHE = 'cache'
34
38
  DIR_NAME_ARTIFACT = 'artifact'
35
39
  DIR_NAME_SOURCE = 'source'
36
40
  FILENAME_VERSION = 'version'
@@ -1,11 +1,12 @@
1
- from dataclasses import dataclass
2
- from functools import cached_property
3
- from typing import Self, Optional
4
-
5
1
  import dns
6
2
  import httpx
3
+ from dataclasses import dataclass
7
4
  from dns.message import Message, QueryMessage
8
5
  from dns.rrset import RRset
6
+ from functools import cached_property
7
+ from typing import Self, Optional
8
+
9
+ from fmtr.tools.string_tools import join
9
10
 
10
11
 
11
12
  @dataclass
@@ -25,7 +26,6 @@ class BaseDNSData:
25
26
  def from_message(cls, message: Message) -> Self:
26
27
  return cls(message.to_wire())
27
28
 
28
-
29
29
  @dataclass
30
30
  class Response(BaseDNSData):
31
31
  """
@@ -39,15 +39,41 @@ class Response(BaseDNSData):
39
39
 
40
40
  @classmethod
41
41
  def from_http(cls, response: httpx.Response) -> Self:
42
+ """
43
+
44
+ Initialise from an HTTP response.
45
+
46
+ """
42
47
  self = cls(response.content, http=response)
43
48
  return self
44
49
 
45
50
  @property
46
51
  def answer(self) -> Optional[RRset]:
52
+ """
53
+
54
+ Get the latest answer, if one exists.
55
+
56
+ """
47
57
  if not self.message.answer:
48
58
  return None
49
59
  return self.message.answer[-1]
50
60
 
61
+ def __str__(self):
62
+ """
63
+
64
+ Put answer and ID text in string representation.
65
+
66
+ """
67
+ answer = self.answer
68
+
69
+ if answer:
70
+ answer = join(answer.to_text().splitlines(), sep=', ')
71
+
72
+ string = join([answer, self.message.flags], sep=', ')
73
+ string = f'{self.__class__.__name__}({string})'
74
+ return string
75
+
76
+
51
77
 
52
78
  @dataclass
53
79
  class Request(BaseDNSData):
@@ -157,3 +183,13 @@ class Exchange:
157
183
  question_last = self.question_last
158
184
  query = dns.message.make_query(qname=question_last.name, rdclass=question_last.rdclass, rdtype=question_last.rdtype)
159
185
  return query
186
+
187
+ @property
188
+ def key(self):
189
+ """
190
+
191
+ Hashable key for caching
192
+
193
+ """
194
+ data = tuple(self.request.question.to_text().split())
195
+ return data
@@ -0,0 +1,57 @@
1
+ from dataclasses import dataclass
2
+
3
+ from fmtr.tools.dns_tools import server, client
4
+ from fmtr.tools.dns_tools.dm import Exchange
5
+ from fmtr.tools.logging_tools import logger
6
+
7
+
8
+ @dataclass(kw_only=True, eq=False)
9
+ class Proxy(server.Plain):
10
+ """
11
+
12
+ Base for a DNS Proxy server (plain server) TODO: Allow subclassing of any server type.
13
+
14
+ """
15
+
16
+ client: client.HTTP
17
+
18
+ def process_question(self, exchange: Exchange):
19
+ """
20
+
21
+ Modify exchange based on initial question.
22
+
23
+ """
24
+ return
25
+
26
+ def process_upstream(self, exchange: Exchange):
27
+ """
28
+
29
+ Modify exchange after upstream response.
30
+
31
+ """
32
+ return
33
+
34
+ def resolve(self, exchange: Exchange) -> Exchange:
35
+ """
36
+
37
+ Resolve a request, processing each stage, initial question, upstream response etc.
38
+ Subclasses can override the relevant processing methods to implement custom behaviour.
39
+
40
+ """
41
+
42
+ with logger.span(f'Processing question...'):
43
+ self.process_question(exchange)
44
+ if exchange.response.is_complete:
45
+ return exchange
46
+
47
+ with logger.span(f'Making upstream request...'):
48
+ self.client.resolve(exchange)
49
+ if exchange.response.is_complete:
50
+ return exchange
51
+
52
+ with logger.span(f'Processing upstream response...'):
53
+ self.process_upstream(exchange)
54
+ if exchange.response.is_complete:
55
+ return exchange
56
+
57
+ return exchange
@@ -0,0 +1,94 @@
1
+ import socket
2
+ from dataclasses import dataclass
3
+ from datetime import timedelta
4
+ from functools import cached_property
5
+
6
+ from fmtr.tools import caching_tools as caching
7
+ from fmtr.tools.dns_tools.dm import Exchange
8
+ from fmtr.tools.logging_tools import logger
9
+
10
+
11
+ @dataclass(kw_only=True, eq=False)
12
+ class Plain:
13
+ """
14
+
15
+ Base for starting a plain DNS server
16
+
17
+ """
18
+
19
+ host: str
20
+ port: int
21
+
22
+ @cached_property
23
+ def sock(self):
24
+ return socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
25
+
26
+ @cached_property
27
+ def cache(self):
28
+ """
29
+
30
+ Overridable cache.
31
+
32
+ """
33
+ cache = caching.TLRU(maxsize=1_024, ttu_static=timedelta(hours=1), desc='DNS Request')
34
+ return cache
35
+
36
+ def start(self):
37
+ """
38
+
39
+ Listen and resolve via overridden resolve method.
40
+
41
+ """
42
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
43
+ sock.bind((self.host, self.port))
44
+ logger.info(f'Listening on {self.host}:{self.port}')
45
+ while True:
46
+ data, (ip, port) = sock.recvfrom(512)
47
+ exchange = Exchange.from_wire(data, ip=ip, port=port)
48
+ self.handle(exchange)
49
+ sock.sendto(exchange.response.message.to_wire(), (ip, port))
50
+
51
+ def resolve(self, exchange: Exchange) -> Exchange:
52
+ """
53
+
54
+ Defined in subclasses
55
+
56
+ """
57
+ raise NotImplemented
58
+
59
+ def check_cache(self, exchange: Exchange):
60
+ """
61
+
62
+ Check cache, patch in in new ID and mark complete
63
+
64
+ """
65
+ if exchange.key in self.cache:
66
+ logger.info(f'Request found in cache.')
67
+ exchange.response = self.cache[exchange.key]
68
+ exchange.response.message.id = exchange.request.message.id
69
+ exchange.response.is_complete = True
70
+
71
+ def handle(self, exchange: Exchange):
72
+ """
73
+
74
+ Check validity of request, presence in cache and resolve.
75
+
76
+ """
77
+ request = exchange.request
78
+
79
+ if not request.is_valid:
80
+ raise ValueError(f'Only one question per request is supported. Got {len(request.question)} questions.')
81
+
82
+ with logger.span(f'Handling request {request.message.id=} {request.question=} {exchange.client=}...'):
83
+
84
+ with logger.span(f'Checking cache...'):
85
+ self.check_cache(exchange)
86
+
87
+ if not exchange.response.is_complete:
88
+ exchange = self.resolve(exchange)
89
+ exchange.response.is_complete = True
90
+
91
+ self.cache[exchange.key] = exchange.response
92
+
93
+ logger.info(f'Resolution complete {request.message.id=} {exchange.response.answer=}')
94
+ return exchange
@@ -52,7 +52,7 @@ def get_logger(name, version=None, host=Constants.FMTR_OBS_HOST, key=None, org=C
52
52
  lev_name_otel = logfire._internal.constants.NUMBER_TO_LEVEL[lev_num_otel]
53
53
 
54
54
  console_opts = logfire.ConsoleOptions(
55
- colors='always' if environment_tools.IS_DEBUG else 'auto',
55
+ colors='always',
56
56
  min_log_level=lev_name_otel,
57
57
  )
58
58
 
@@ -305,6 +305,16 @@ class PackagePaths(FromCallerMixin):
305
305
 
306
306
  return self.dev / Constants.DIR_NAME_REPO / self.name_ns / self.dir_name_data
307
307
 
308
+ @property
309
+ def cache(self) -> Path:
310
+ """
311
+
312
+ Path of cache directory.
313
+
314
+ """
315
+
316
+ return self.data / Constants.DIR_NAME_CACHE
317
+
308
318
  @property
309
319
  def artifact(self) -> Path:
310
320
  """
@@ -1,9 +1,9 @@
1
+ import regex as re
1
2
  from dataclasses import dataclass, asdict
2
3
  from functools import cached_property
3
4
  from typing import List, Any
4
5
 
5
- import regex as re
6
-
6
+ from fmtr.tools import Constants
7
7
  from fmtr.tools.logging_tools import logger
8
8
  from fmtr.tools.string_tools import join
9
9
 
@@ -92,7 +92,8 @@ class Item:
92
92
  source: Key
93
93
  target: Key
94
94
 
95
- @dataclass
95
+
96
+ @dataclass(kw_only=True)
96
97
  class Transformer:
97
98
  """
98
99
 
@@ -210,7 +211,7 @@ class Transformer:
210
211
  previous = key
211
212
 
212
213
  def get_history_str():
213
- return join(history, sep=' → ')
214
+ return join(history, sep=Constants.ARROW_SEP)
214
215
 
215
216
  while True:
216
217
  if previous in history:
@@ -0,0 +1 @@
1
+ 1.3.9
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmtr.tools
3
- Version: 1.3.7
3
+ Version: 1.3.9
4
4
  Summary: Collection of high-level tools to simplify everyday development tasks, with a focus on AI/ML
5
5
  Home-page: https://github.com/fmtr/fmtr.tools
6
6
  Author: Frontmatter
@@ -97,6 +97,7 @@ Requires-Dist: google-auth-httplib2; extra == "google-api"
97
97
  Requires-Dist: google-api-python-client; extra == "google-api"
98
98
  Provides-Extra: caching
99
99
  Requires-Dist: diskcache; extra == "caching"
100
+ Requires-Dist: cachetools; extra == "caching"
100
101
  Provides-Extra: pdf
101
102
  Requires-Dist: pymupdf; extra == "pdf"
102
103
  Requires-Dist: pydantic; extra == "pdf"
@@ -130,60 +131,61 @@ Requires-Dist: logfire[httpx]; extra == "http"
130
131
  Provides-Extra: setup
131
132
  Requires-Dist: setuptools; extra == "setup"
132
133
  Provides-Extra: all
133
- Requires-Dist: dnspython[doh]; extra == "all"
134
- Requires-Dist: pymupdf4llm; extra == "all"
135
- Requires-Dist: regex; extra == "all"
136
134
  Requires-Dist: uvicorn[standard]; extra == "all"
137
- Requires-Dist: tokenizers; extra == "all"
138
- Requires-Dist: filetype; extra == "all"
139
- Requires-Dist: flet[all]; extra == "all"
140
- Requires-Dist: Unidecode; extra == "all"
141
- Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
142
- Requires-Dist: logfire[httpx]; extra == "all"
143
- Requires-Dist: bokeh; extra == "all"
144
- Requires-Dist: fastapi; extra == "all"
145
- Requires-Dist: pytest-cov; extra == "all"
146
135
  Requires-Dist: json_repair; extra == "all"
147
- Requires-Dist: semver; extra == "all"
148
- Requires-Dist: google-auth-oauthlib; extra == "all"
149
- Requires-Dist: google-auth-httplib2; extra == "all"
150
- Requires-Dist: html2text; extra == "all"
151
- Requires-Dist: sre_yield; extra == "all"
152
- Requires-Dist: pymupdf; extra == "all"
153
- Requires-Dist: flet-video; extra == "all"
154
- Requires-Dist: transformers[sentencepiece]; extra == "all"
155
- Requires-Dist: pydantic-settings; extra == "all"
156
- Requires-Dist: ollama; extra == "all"
157
- Requires-Dist: distributed; extra == "all"
158
136
  Requires-Dist: httpx; extra == "all"
159
- Requires-Dist: google-auth; extra == "all"
137
+ Requires-Dist: openai; extra == "all"
160
138
  Requires-Dist: openpyxl; extra == "all"
161
- Requires-Dist: logfire[fastapi]; extra == "all"
139
+ Requires-Dist: flet[all]; extra == "all"
140
+ Requires-Dist: regex; extra == "all"
162
141
  Requires-Dist: tabulate; extra == "all"
163
142
  Requires-Dist: tinynetrc; extra == "all"
164
- Requires-Dist: torchvision; extra == "all"
165
- Requires-Dist: sentence_transformers; extra == "all"
166
- Requires-Dist: pyyaml; extra == "all"
167
- Requires-Dist: diskcache; extra == "all"
168
- Requires-Dist: faker; extra == "all"
169
- Requires-Dist: docker; extra == "all"
143
+ Requires-Dist: filetype; extra == "all"
144
+ Requires-Dist: google-api-python-client; extra == "all"
170
145
  Requires-Dist: yamlscript; extra == "all"
146
+ Requires-Dist: tokenizers; extra == "all"
147
+ Requires-Dist: deepmerge; extra == "all"
148
+ Requires-Dist: sre_yield; extra == "all"
149
+ Requires-Dist: diskcache; extra == "all"
150
+ Requires-Dist: transformers[sentencepiece]; extra == "all"
151
+ Requires-Dist: google-auth-oauthlib; extra == "all"
152
+ Requires-Dist: Unidecode; extra == "all"
153
+ Requires-Dist: sentence_transformers; extra == "all"
154
+ Requires-Dist: logfire; extra == "all"
155
+ Requires-Dist: logfire[fastapi]; extra == "all"
171
156
  Requires-Dist: pydantic; extra == "all"
172
- Requires-Dist: contexttimer; extra == "all"
157
+ Requires-Dist: semver; extra == "all"
158
+ Requires-Dist: dnspython[doh]; extra == "all"
173
159
  Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
174
- Requires-Dist: appdirs; extra == "all"
175
- Requires-Dist: flet-webview; extra == "all"
176
- Requires-Dist: dask[bag]; extra == "all"
177
- Requires-Dist: openai; extra == "all"
160
+ Requires-Dist: google-auth-httplib2; extra == "all"
161
+ Requires-Dist: pymupdf4llm; extra == "all"
162
+ Requires-Dist: html2text; extra == "all"
163
+ Requires-Dist: google-auth; extra == "all"
164
+ Requires-Dist: pydantic-settings; extra == "all"
178
165
  Requires-Dist: huggingface_hub; extra == "all"
179
- Requires-Dist: torchaudio; extra == "all"
166
+ Requires-Dist: pyyaml; extra == "all"
167
+ Requires-Dist: torchvision; extra == "all"
168
+ Requires-Dist: pymupdf; extra == "all"
169
+ Requires-Dist: fastapi; extra == "all"
170
+ Requires-Dist: logfire[httpx]; extra == "all"
171
+ Requires-Dist: contexttimer; extra == "all"
180
172
  Requires-Dist: httpx_retries; extra == "all"
173
+ Requires-Dist: dask[bag]; extra == "all"
174
+ Requires-Dist: peft; extra == "all"
175
+ Requires-Dist: distributed; extra == "all"
176
+ Requires-Dist: docker; extra == "all"
177
+ Requires-Dist: faker; extra == "all"
178
+ Requires-Dist: cachetools; extra == "all"
179
+ Requires-Dist: torchaudio; extra == "all"
181
180
  Requires-Dist: setuptools; extra == "all"
182
- Requires-Dist: logfire; extra == "all"
183
- Requires-Dist: deepmerge; extra == "all"
181
+ Requires-Dist: pytest-cov; extra == "all"
182
+ Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
183
+ Requires-Dist: flet-video; extra == "all"
184
+ Requires-Dist: flet-webview; extra == "all"
185
+ Requires-Dist: appdirs; extra == "all"
184
186
  Requires-Dist: pandas; extra == "all"
185
- Requires-Dist: google-api-python-client; extra == "all"
186
- Requires-Dist: peft; extra == "all"
187
+ Requires-Dist: bokeh; extra == "all"
188
+ Requires-Dist: ollama; extra == "all"
187
189
  Dynamic: author
188
190
  Dynamic: author-email
189
191
  Dynamic: description
@@ -15,60 +15,61 @@ pydantic-ai[logfire,openai]
15
15
  ollama
16
16
 
17
17
  [all]
18
- dnspython[doh]
19
- pymupdf4llm
20
- regex
21
18
  uvicorn[standard]
22
- tokenizers
23
- filetype
24
- flet[all]
25
- Unidecode
26
- pydantic-ai[logfire,openai]
27
- logfire[httpx]
28
- bokeh
29
- fastapi
30
- pytest-cov
31
19
  json_repair
32
- semver
33
- google-auth-oauthlib
34
- google-auth-httplib2
35
- html2text
36
- sre_yield
37
- pymupdf
38
- flet-video
39
- transformers[sentencepiece]
40
- pydantic-settings
41
- ollama
42
- distributed
43
20
  httpx
44
- google-auth
21
+ openai
45
22
  openpyxl
46
- logfire[fastapi]
23
+ flet[all]
24
+ regex
47
25
  tabulate
48
26
  tinynetrc
49
- torchvision
50
- sentence_transformers
51
- pyyaml
52
- diskcache
53
- faker
54
- docker
27
+ filetype
28
+ google-api-python-client
55
29
  yamlscript
30
+ tokenizers
31
+ deepmerge
32
+ sre_yield
33
+ diskcache
34
+ transformers[sentencepiece]
35
+ google-auth-oauthlib
36
+ Unidecode
37
+ sentence_transformers
38
+ logfire
39
+ logfire[fastapi]
56
40
  pydantic
57
- contexttimer
41
+ semver
42
+ dnspython[doh]
58
43
  pydevd-pycharm~=251.25410.159
59
- appdirs
60
- flet-webview
61
- dask[bag]
62
- openai
44
+ google-auth-httplib2
45
+ pymupdf4llm
46
+ html2text
47
+ google-auth
48
+ pydantic-settings
63
49
  huggingface_hub
64
- torchaudio
50
+ pyyaml
51
+ torchvision
52
+ pymupdf
53
+ fastapi
54
+ logfire[httpx]
55
+ contexttimer
65
56
  httpx_retries
57
+ dask[bag]
58
+ peft
59
+ distributed
60
+ docker
61
+ faker
62
+ cachetools
63
+ torchaudio
66
64
  setuptools
67
- logfire
68
- deepmerge
65
+ pytest-cov
66
+ pydantic-ai[logfire,openai]
67
+ flet-video
68
+ flet-webview
69
+ appdirs
69
70
  pandas
70
- google-api-python-client
71
- peft
71
+ bokeh
72
+ ollama
72
73
 
73
74
  [api]
74
75
  fastapi
@@ -84,6 +85,7 @@ sre_yield
84
85
 
85
86
  [caching]
86
87
  diskcache
88
+ cachetools
87
89
 
88
90
  [debug]
89
91
  pydevd-pycharm~=251.25410.159
@@ -29,7 +29,7 @@ DEPENDENCIES = {
29
29
  'html': ['html2text'],
30
30
  'interface': ['flet[all]', 'flet-video', 'flet-webview', 'dm'],
31
31
  'google.api': ['google-auth', 'google-auth-oauthlib', 'google-auth-httplib2', 'google-api-python-client'],
32
- 'caching': ['diskcache'],
32
+ 'caching': ['diskcache', 'cachetools'],
33
33
  'pdf': ['pymupdf', 'dm', 'pymupdf4llm'],
34
34
  'debug': ['pydevd-pycharm~=251.25410.159'],
35
35
  'sets': ['pydantic-settings', 'dm', 'yaml'],
@@ -1,67 +0,0 @@
1
- from dataclasses import dataclass
2
-
3
- from fmtr.tools import logger
4
- from fmtr.tools.dns_tools import server, client
5
- from fmtr.tools.dns_tools.dm import Exchange
6
-
7
-
8
- @dataclass
9
- class Proxy(server.Plain):
10
- """
11
-
12
- Base for a DNS Proxy server (plain server) TODO: Allow subclassing of any server type.
13
-
14
- """
15
-
16
- client: client.HTTP
17
-
18
- def process_question(self, exchange: Exchange):
19
- """
20
-
21
- Modify exchange based on initial question.
22
-
23
- """
24
- return
25
-
26
- def process_upstream(self, exchange: Exchange):
27
- """
28
-
29
- Modify exchange after upstream response.
30
-
31
- """
32
- return
33
-
34
- def resolve(self, exchange: Exchange):
35
- """
36
-
37
- Resolve a request, processing each stage, initial question, upstream response etc.
38
- Subclasses can override the relevant processing methods to implement custom behaviour.
39
-
40
- """
41
-
42
- request = exchange.request
43
-
44
- with logger.span(f'Handling request {request.message.id=} {request.question=} {exchange.client=}...'):
45
-
46
- if not request.is_valid:
47
- raise ValueError(f'Only one question per request is supported. Got {len(request.question)} questions.')
48
-
49
- with logger.span(f'Processing question...'):
50
- self.process_question(exchange)
51
- if exchange.response.is_complete:
52
- return
53
-
54
- with logger.span(f'Making upstream request...'):
55
- self.client.resolve(exchange)
56
- if exchange.response.is_complete:
57
- return
58
-
59
- with logger.span(f'Processing upstream response...'):
60
- self.process_upstream(exchange)
61
- if exchange.response.is_complete:
62
- return
63
-
64
- exchange.response.is_complete = True
65
-
66
- logger.info(f'Resolution complete {request.message.id=} {exchange.response.answer=}')
67
- return
@@ -1,39 +0,0 @@
1
- import socket
2
- from dataclasses import dataclass
3
-
4
- from fmtr.tools.dns_tools.dm import Exchange
5
- from fmtr.tools.logging_tools import logger
6
-
7
-
8
- @dataclass
9
- class Plain:
10
- """
11
-
12
- Base for starting a plain DNS server
13
-
14
- """
15
-
16
- host: str
17
- port: int
18
-
19
- def __post_init__(self):
20
-
21
- self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
22
-
23
- def resolve(self, exchange: Exchange):
24
- raise NotImplemented
25
-
26
- def start(self):
27
- """
28
-
29
- Listen and resolve via overridden resolve method.
30
-
31
- """
32
- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
33
- sock.bind((self.host, self.port))
34
- logger.info(f'Listening on {self.host}:{self.port}')
35
- while True:
36
- data, (ip, port) = sock.recvfrom(512)
37
- exchange = Exchange.from_wire(data, ip=ip, port=port)
38
- self.resolve(exchange)
39
- sock.sendto(exchange.response.message.to_wire(), (ip, port))
@@ -1 +0,0 @@
1
- 1.3.7
File without changes
File without changes
File without changes
File without changes