fmtr.tools 1.3.2__py3-none-any.whl → 1.3.4__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.

Potentially problematic release.


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

@@ -41,7 +41,7 @@ class Proxy(server.Plain):
41
41
 
42
42
  request = exchange.request
43
43
 
44
- with logger.span(f'Handling request ID {request.message.id} for {request.name_text} from {exchange.client}...'):
44
+ with logger.span(f'Handling request {request.message.id=} {request.question=} {exchange.client=}...'):
45
45
 
46
46
  if not request.is_valid:
47
47
  raise ValueError(f'Only one question per request is supported. Got {len(request.question)} questions.')
@@ -51,7 +51,7 @@ class Proxy(server.Plain):
51
51
  if exchange.response.is_complete:
52
52
  return
53
53
 
54
- with logger.span(f'Making upstream request for {request.name_text}...'):
54
+ with logger.span(f'Making upstream request...'):
55
55
  self.client.resolve(exchange)
56
56
  if exchange.response.is_complete:
57
57
  return
@@ -61,8 +61,7 @@ class Proxy(server.Plain):
61
61
  if exchange.response.is_complete:
62
62
  return
63
63
 
64
- if exchange.response:
65
- return
66
-
67
64
  exchange.response.is_complete = True
68
- return
65
+
66
+ logger.info(f'Resolution complete {request.message.id=} {exchange.response.answer=}')
67
+ return
@@ -2,6 +2,7 @@ import socket
2
2
  from dataclasses import dataclass
3
3
 
4
4
  from fmtr.tools.dns_tools.dm import Exchange
5
+ from fmtr.tools.logging_tools import logger
5
6
 
6
7
 
7
8
  @dataclass
@@ -30,7 +31,7 @@ class Plain:
30
31
  """
31
32
  sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
32
33
  sock.bind((self.host, self.port))
33
- print(f"Listening on {self.host}:{self.port}")
34
+ logger.info(f'Listening on {self.host}:{self.port}')
34
35
  while True:
35
36
  data, (ip, port) = sock.recvfrom(512)
36
37
  exchange = Exchange.from_wire(data, ip=ip, port=port)
@@ -1,8 +1,9 @@
1
- import regex as re
2
1
  from dataclasses import dataclass, asdict
3
2
  from functools import cached_property
4
3
  from typing import List, Any
5
4
 
5
+ import regex as re
6
+
6
7
  from fmtr.tools.logging_tools import logger
7
8
  from fmtr.tools.string_tools import join
8
9
 
@@ -25,11 +26,6 @@ def alt(*patterns):
25
26
  pattern = MASK_GROUP.format(pattern=pattern)
26
27
  return pattern
27
28
 
28
-
29
-
30
-
31
-
32
-
33
29
  @dataclass
34
30
  class Key:
35
31
  RECORD_SEP = '␞'
@@ -93,17 +89,18 @@ class Item:
93
89
  Key-value pair
94
90
 
95
91
  """
96
- key: Key
97
- value: Key
92
+ source: Key
93
+ target: Key
98
94
 
99
95
  @dataclass
100
- class Mapper:
96
+ class Transformer:
101
97
  """
102
98
 
103
99
  Pattern-based, dictionary-like mapper.
104
- Compiles a single regex pattern from a list of rules, and determines which rule matched.
105
- It supports initialization from structured rule data, execution of a single lookup pass, and
106
- recursive lookups until a stable state is reached.
100
+ Compiles an complex set of rules into single regex pattern, and determines which rule matched.
101
+ Inputs are then transformed according to the matching rule.
102
+ Works like a pattern-based dictionary when is_recursive==False.
103
+ Works something like an FSA/transducer when is_recursive=True.
107
104
 
108
105
  """
109
106
  PREFIX_GROUP = '__'
@@ -111,22 +108,27 @@ class Mapper:
111
108
  default: Any = None
112
109
  is_recursive: bool = False
113
110
 
111
+ def __post_init__(self):
112
+ with logger.span(f'Compiling expression {len(self.items)=}'):
113
+ rx = self.rx
114
+ logger.debug(f'Compiled successfully {rx.groups=}')
115
+
114
116
  @cached_property
115
- def pattern(self):
117
+ def pattern(self) -> str:
116
118
  """
117
119
 
118
- Provides a dynamically generated regex pattern based on the rules provided.
120
+ Dynamically generated regex pattern based on the rules provided.
119
121
 
120
122
  """
121
123
  patterns = [
122
- MASK_NAMED.format(key=f'{self.PREFIX_GROUP}{i}', pattern=item.key.pattern)
124
+ MASK_NAMED.format(key=f'{self.PREFIX_GROUP}{i}', pattern=item.source.pattern)
123
125
  for i, item in enumerate(self.items)
124
126
  ]
125
127
  pattern = alt(*patterns)
126
128
  return pattern
127
129
 
128
130
  @cached_property
129
- def rx(self):
131
+ def rx(self) -> re.Pattern:
130
132
  """
131
133
 
132
134
  Regex object.
@@ -134,24 +136,31 @@ class Mapper:
134
136
  """
135
137
  return re.compile(self.pattern)
136
138
 
137
- def get_default(self, key: Key):
139
+ def get_default(self, key: Key) -> Any:
140
+ """
141
+
142
+ Define what to return in case of no match
143
+
144
+ """
138
145
  if self.is_recursive:
139
146
  return key
140
147
  else:
141
148
  return self.default
142
149
 
143
- def get(self, key: Key) -> Key:
150
+ def get(self, key: Key) -> Key | Any:
144
151
  """
145
152
 
146
153
  Use recursive or single lookup pass, depending on whether recursive lookups have been specified.
147
154
 
148
155
  """
149
156
  if self.is_recursive:
150
- return self.get_recursive(key)
157
+ with logger.span(f'Transforming recursively {key=}...'):
158
+ return self.get_recursive(key)
151
159
  else:
152
- return self.get_one(key)
160
+ with logger.span(f'Transforming linearly {key=}...'):
161
+ return self.get_one(key)
153
162
 
154
- def get_one(self, key: Key):
163
+ def get_one(self, key: Key) -> Key | Any:
155
164
  """
156
165
 
157
166
  Single lookup pass.
@@ -163,7 +172,7 @@ class Mapper:
163
172
 
164
173
  if not match:
165
174
  value = self.get_default(key)
166
- logger.debug(f'No match for {key=}.')
175
+ logger.debug(f'No match for {key=}. Returning {self.get_default(key)=}')
167
176
  else:
168
177
 
169
178
  match_ids = {name: v for name, v in match.groupdict().items() if v}
@@ -179,19 +188,21 @@ class Mapper:
179
188
  rule_id = next(iter(rule_ids))
180
189
  rule = self.items[rule_id]
181
190
 
182
- if isinstance(rule.value, Key):
183
- value = rule.value.transform(match)
191
+ logger.debug(f'Matched using {rule_id=}: {rule.source=}')
192
+
193
+ if isinstance(rule.target, Key):
194
+ value = rule.target.transform(match)
184
195
  else:
185
- value = rule.value
196
+ value = rule.target
186
197
 
187
- logger.debug(f'Matched using {rule_id=}: {key=} → {value=}')
198
+ logger.debug(f'Transformed using {rule_id=}: {key=} → {value=}')
188
199
 
189
200
  return value
190
201
 
191
- def get_recursive(self, key: Key) -> Key:
202
+ def get_recursive(self, key: Key) -> Key | Any:
192
203
  """
193
204
 
194
- Lookup the provided text by continuously applying lookup rules until no changes are made
205
+ Lookup the provided key by continuously applying transforms until no changes are made
195
206
  or a circular loop is detected.
196
207
 
197
208
  """
@@ -201,29 +212,28 @@ class Mapper:
201
212
  def get_history_str():
202
213
  return join(history, sep=' → ')
203
214
 
204
- with logger.span(f'Matching {key=}...'):
205
- while True:
206
- if previous in history:
207
- history.append(previous)
208
- msg = f'Loop detected on node "{previous}": {get_history_str()}'
209
- raise RewriteCircularLoopError(msg)
210
-
215
+ while True:
216
+ if previous in history:
211
217
  history.append(previous)
218
+ msg = f'Loop detected on node "{previous}": {get_history_str()}'
219
+ raise RewriteCircularLoopError(msg)
212
220
 
213
- new = previous
214
-
215
- new = self.get_one(new)
221
+ history.append(previous)
222
+ new = previous
223
+ new = self.get_one(new)
224
+ if new == previous:
225
+ break
226
+ previous = new
216
227
 
217
- if new == previous:
218
- break
219
-
220
- previous = new
228
+ if not isinstance(new, Key):
229
+ history.append(previous)
230
+ break
221
231
 
222
232
  if len(history) == 1:
223
- history_str = 'No matching performed.'
233
+ history_str = 'No transforms performed.'
224
234
  else:
225
235
  history_str = get_history_str()
226
- logger.debug(f'Finished matching: {history_str}')
236
+ logger.debug(f'Finished transforming: {history_str}')
227
237
 
228
238
  return previous
229
239
 
fmtr/tools/version CHANGED
@@ -1 +1 @@
1
- 1.3.2
1
+ 1.3.4
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmtr.tools
3
- Version: 1.3.2
3
+ Version: 1.3.4
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
@@ -102,7 +102,7 @@ Requires-Dist: pymupdf; extra == "pdf"
102
102
  Requires-Dist: pydantic; extra == "pdf"
103
103
  Requires-Dist: pymupdf4llm; extra == "pdf"
104
104
  Provides-Extra: debug
105
- Requires-Dist: pydevd-pycharm; extra == "debug"
105
+ Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "debug"
106
106
  Provides-Extra: sets
107
107
  Requires-Dist: pydantic-settings; extra == "sets"
108
108
  Requires-Dist: pydantic; extra == "sets"
@@ -130,60 +130,60 @@ Requires-Dist: logfire[httpx]; extra == "http"
130
130
  Provides-Extra: setup
131
131
  Requires-Dist: setuptools; extra == "setup"
132
132
  Provides-Extra: all
133
- Requires-Dist: google-auth-oauthlib; extra == "all"
134
- Requires-Dist: dask[bag]; extra == "all"
135
- Requires-Dist: tabulate; extra == "all"
136
- Requires-Dist: flet[all]; extra == "all"
137
- Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
138
- Requires-Dist: tinynetrc; extra == "all"
139
- Requires-Dist: html2text; extra == "all"
140
- Requires-Dist: deepmerge; extra == "all"
141
- Requires-Dist: pytest-cov; extra == "all"
142
- Requires-Dist: logfire[fastapi]; extra == "all"
143
- Requires-Dist: dnspython[doh]; extra == "all"
144
- Requires-Dist: setuptools; extra == "all"
145
- Requires-Dist: peft; extra == "all"
146
- Requires-Dist: filetype; extra == "all"
147
- Requires-Dist: flet-webview; extra == "all"
133
+ Requires-Dist: appdirs; extra == "all"
148
134
  Requires-Dist: flet-video; extra == "all"
135
+ Requires-Dist: openai; extra == "all"
136
+ Requires-Dist: pydantic; extra == "all"
137
+ Requires-Dist: fastapi; extra == "all"
138
+ Requires-Dist: faker; extra == "all"
139
+ Requires-Dist: regex; extra == "all"
140
+ Requires-Dist: google-auth-httplib2; extra == "all"
149
141
  Requires-Dist: tokenizers; extra == "all"
150
142
  Requires-Dist: sre_yield; extra == "all"
151
- Requires-Dist: semver; extra == "all"
143
+ Requires-Dist: peft; extra == "all"
144
+ Requires-Dist: pydantic-settings; extra == "all"
145
+ Requires-Dist: flet[all]; extra == "all"
146
+ Requires-Dist: torchaudio; extra == "all"
147
+ Requires-Dist: google-api-python-client; extra == "all"
148
+ Requires-Dist: bokeh; extra == "all"
149
+ Requires-Dist: huggingface_hub; extra == "all"
150
+ Requires-Dist: pymupdf; extra == "all"
152
151
  Requires-Dist: httpx; extra == "all"
153
- Requires-Dist: google-auth; extra == "all"
154
- Requires-Dist: openpyxl; extra == "all"
155
- Requires-Dist: pymupdf4llm; extra == "all"
156
- Requires-Dist: appdirs; extra == "all"
157
152
  Requires-Dist: pyyaml; extra == "all"
158
- Requires-Dist: uvicorn[standard]; extra == "all"
159
- Requires-Dist: httpx_retries; extra == "all"
160
- Requires-Dist: pydantic; extra == "all"
161
- Requires-Dist: sentence_transformers; extra == "all"
162
153
  Requires-Dist: ollama; extra == "all"
163
- Requires-Dist: distributed; extra == "all"
164
- Requires-Dist: huggingface_hub; extra == "all"
165
- Requires-Dist: logfire[httpx]; extra == "all"
166
- Requires-Dist: torchaudio; extra == "all"
154
+ Requires-Dist: openpyxl; extra == "all"
155
+ Requires-Dist: tinynetrc; extra == "all"
156
+ Requires-Dist: logfire[fastapi]; extra == "all"
157
+ Requires-Dist: httpx_retries; extra == "all"
167
158
  Requires-Dist: docker; extra == "all"
168
- Requires-Dist: openai; extra == "all"
169
- Requires-Dist: yamlscript; extra == "all"
170
- Requires-Dist: google-auth-httplib2; extra == "all"
171
- Requires-Dist: Unidecode; extra == "all"
159
+ Requires-Dist: json_repair; extra == "all"
160
+ Requires-Dist: tabulate; extra == "all"
161
+ Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
162
+ Requires-Dist: uvicorn[standard]; extra == "all"
163
+ Requires-Dist: filetype; extra == "all"
164
+ Requires-Dist: flet-webview; extra == "all"
165
+ Requires-Dist: pymupdf4llm; extra == "all"
166
+ Requires-Dist: html2text; extra == "all"
172
167
  Requires-Dist: transformers[sentencepiece]; extra == "all"
168
+ Requires-Dist: setuptools; extra == "all"
173
169
  Requires-Dist: pandas; extra == "all"
174
- Requires-Dist: bokeh; extra == "all"
175
- Requires-Dist: diskcache; extra == "all"
176
- Requires-Dist: json_repair; extra == "all"
170
+ Requires-Dist: dnspython[doh]; extra == "all"
171
+ Requires-Dist: google-auth; extra == "all"
177
172
  Requires-Dist: logfire; extra == "all"
178
- Requires-Dist: regex; extra == "all"
179
- Requires-Dist: fastapi; extra == "all"
180
- Requires-Dist: contexttimer; extra == "all"
181
- Requires-Dist: pydevd-pycharm; extra == "all"
182
- Requires-Dist: faker; extra == "all"
183
- Requires-Dist: pymupdf; extra == "all"
184
- Requires-Dist: google-api-python-client; extra == "all"
173
+ Requires-Dist: pytest-cov; extra == "all"
174
+ Requires-Dist: distributed; extra == "all"
175
+ Requires-Dist: sentence_transformers; extra == "all"
176
+ Requires-Dist: google-auth-oauthlib; extra == "all"
177
+ Requires-Dist: diskcache; extra == "all"
178
+ Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
179
+ Requires-Dist: yamlscript; extra == "all"
185
180
  Requires-Dist: torchvision; extra == "all"
186
- Requires-Dist: pydantic-settings; extra == "all"
181
+ Requires-Dist: Unidecode; extra == "all"
182
+ Requires-Dist: contexttimer; extra == "all"
183
+ Requires-Dist: dask[bag]; extra == "all"
184
+ Requires-Dist: semver; extra == "all"
185
+ Requires-Dist: deepmerge; extra == "all"
186
+ Requires-Dist: logfire[httpx]; extra == "all"
187
187
  Dynamic: author
188
188
  Dynamic: author-email
189
189
  Dynamic: description
@@ -30,7 +30,7 @@ fmtr/tools/netrc_tools.py,sha256=PpNpz_mWlQi6VHGromKwFfTyLpHUXsd4LY6-OKLCbeI,376
30
30
  fmtr/tools/openai_tools.py,sha256=6SUgejgzUzmlKKct2_ePXntvMegu3FJgfk9x7aqtqYc,742
31
31
  fmtr/tools/packaging_tools.py,sha256=FlgOTnDRHZWQL2iR-wucTsyGEHRE-MlddKL30MPmUqE,253
32
32
  fmtr/tools/parallel_tools.py,sha256=QEb_gN1StkxsqYaH4HSjiJX8Y3gpb2uKNsOzG4uFpaM,3071
33
- fmtr/tools/pattern_tools.py,sha256=GzdhKt-nIHuKCGI9y3aGUaD5k9thbqbA9UXJoPPIcIY,5328
33
+ fmtr/tools/pattern_tools.py,sha256=EYKtBPnoBwTpvTPZWLYGrRFIGnSg2R6Tv2VmxvoJDog,5959
34
34
  fmtr/tools/pdf_tools.py,sha256=xvv9B84uAF81rFJRnXhSsxYuP42vY9ZdPVFrSMVe8G8,4069
35
35
  fmtr/tools/platform_tools.py,sha256=7p69CmAHe_sF68Fx9uVhns1k5EewTHTWgUYzkl6ZQKA,308
36
36
  fmtr/tools/process_tools.py,sha256=Ysh5Dk2QFBhXQerArjKdt7xZd3JrN5Ho02AaOjH0Nnw,1425
@@ -44,7 +44,7 @@ fmtr/tools/tabular_tools.py,sha256=tpIpZzYku1HcJrHZJL6BC39LmN3WUWVhFbK2N7nDVmE,1
44
44
  fmtr/tools/tokenization_tools.py,sha256=me-IBzSLyNYejLybwjO9CNB6Mj2NYfKPaOVThXyaGNg,4268
45
45
  fmtr/tools/tools.py,sha256=CAsApa1YwVdNE6H66Vjivs_mXYvOas3rh7fPELAnTpk,795
46
46
  fmtr/tools/unicode_tools.py,sha256=yS_9wpu8ogNoiIL7s1G_8bETFFO_YQlo4LNPv1NLDeY,52
47
- fmtr/tools/version,sha256=exPSrztkeQgMZ8VTDFGwb8f-z8T_NbtvtmOtm7IiqvA,5
47
+ fmtr/tools/version,sha256=2Nc8Vw6HAjDWCtPBpzMVxh4-YdAUne_NZiu-z5HiatM,5
48
48
  fmtr/tools/version_tools.py,sha256=yNs_CGqWpqE4jbK9wsPIi14peJVXYbhIcMqHAFOw3yE,1480
49
49
  fmtr/tools/yaml_tools.py,sha256=9kuYChqJelWQIjGlSnK4iDdOWWH06P0gp9jIcRrC3UI,1903
50
50
  fmtr/tools/ai_tools/__init__.py,sha256=JZrLuOFNV1A3wvJgonxOgz_4WS-7MfCuowGWA5uYCjs,372
@@ -53,8 +53,8 @@ fmtr/tools/ai_tools/inference_tools.py,sha256=2UP2gXEyOJUjyyV6zmFIYmIxUsh1rXkRH0
53
53
  fmtr/tools/dns_tools/__init__.py,sha256=PjD3Og6D5yvDVpKmsUsrnSpz_rjXpl4zBtvMqm8xKWU,237
54
54
  fmtr/tools/dns_tools/client.py,sha256=c2vzWBDZSxijwL1KvWUoDGc8wqk_KTAFxCr0P1rNjy8,2367
55
55
  fmtr/tools/dns_tools/dm.py,sha256=KWQeeZhsDyrKoXzAD5zEOoHH3aiD4uKRqwnD8fFP1nI,3725
56
- fmtr/tools/dns_tools/proxy.py,sha256=0TULVnD3FlZNAsNINy8bcbimTNvmkwvzXeMnOkdUqvw,1879
57
- fmtr/tools/dns_tools/server.py,sha256=0RLsyVH8C-15PAJgqMNqxbHTPbTWkq_thh8nnS1clAg,892
56
+ fmtr/tools/dns_tools/proxy.py,sha256=L0utKw8D1JXtAW9IfXcBdNMb-AyPFgW5XGiPnWuk7D8,1878
57
+ fmtr/tools/dns_tools/server.py,sha256=t38TmSpra3FRoMk351eHzOOPlSuaCyjxbvPwX9o15ak,942
58
58
  fmtr/tools/entrypoints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
59
  fmtr/tools/entrypoints/cache_hfh.py,sha256=fQNs4J9twQuZH_Yj98-oOvEX7-LrSUP3kO8nzw2HrHs,60
60
60
  fmtr/tools/entrypoints/ep_test.py,sha256=B8HfWISfSgw_xVX475CbJGh_QnpOe9MH65H8qGjTWbY,46
@@ -74,9 +74,9 @@ fmtr/tools/tests/test_environment.py,sha256=iHaiMQfECYZPkPKwfuIZV9uHuWe3aE-p_dN_
74
74
  fmtr/tools/tests/test_json.py,sha256=IeSP4ziPvRcmS8kq7k9tHonC9rN5YYq9GSNT2ul6Msk,287
75
75
  fmtr/tools/tests/test_path.py,sha256=AkZQa6_8BQ-VaCyL_J-iKmdf2ZaM-xFYR37Kun3k4_g,2188
76
76
  fmtr/tools/tests/test_yaml.py,sha256=jc0TwwKu9eC0LvFGNMERdgBue591xwLxYXFbtsRwXVM,287
77
- fmtr_tools-1.3.2.dist-info/licenses/LICENSE,sha256=FW9aa6vVN5IjRQWLT43hs4_koYSmpcbIovlKeAJ0_cI,10757
78
- fmtr_tools-1.3.2.dist-info/METADATA,sha256=KlWOZL7_0eJCCM3bZtnujinv89YPLmtU4LcDg7JZx9I,16030
79
- fmtr_tools-1.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
80
- fmtr_tools-1.3.2.dist-info/entry_points.txt,sha256=fSQrDGNctdQXbUxpMWYVfVQ0mhZMDyaEDG3D3a0zOSc,278
81
- fmtr_tools-1.3.2.dist-info/top_level.txt,sha256=LXem9xCgNOD72tE2gRKESdiQTL902mfFkwWb6-dlwEE,5
82
- fmtr_tools-1.3.2.dist-info/RECORD,,
77
+ fmtr_tools-1.3.4.dist-info/licenses/LICENSE,sha256=FW9aa6vVN5IjRQWLT43hs4_koYSmpcbIovlKeAJ0_cI,10757
78
+ fmtr_tools-1.3.4.dist-info/METADATA,sha256=TywtfCfmbCtpH1vG7v7cyJ6V07_sXNfNuUu14uIrTHE,16060
79
+ fmtr_tools-1.3.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
80
+ fmtr_tools-1.3.4.dist-info/entry_points.txt,sha256=fSQrDGNctdQXbUxpMWYVfVQ0mhZMDyaEDG3D3a0zOSc,278
81
+ fmtr_tools-1.3.4.dist-info/top_level.txt,sha256=LXem9xCgNOD72tE2gRKESdiQTL902mfFkwWb6-dlwEE,5
82
+ fmtr_tools-1.3.4.dist-info/RECORD,,