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

@@ -1,8 +1,7 @@
1
- from dataclasses import dataclass
2
- from functools import cached_property
3
-
4
1
  import dns as dnspython
2
+ from dataclasses import dataclass
5
3
  from dns import query
4
+ from functools import cached_property
6
5
  from httpx_retries import Retry, RetryTransport
7
6
 
8
7
  from fmtr.tools import http_tools as http
@@ -82,8 +81,14 @@ class HTTP:
82
81
 
83
82
  headers = self.HEADERS | dict(Host=self.host)
84
83
  url = self.url.format(host=self.ip)
85
- response_doh = self.CLIENT.post(url, headers=headers, content=exchange.query_last.to_wire())
86
- response_doh.raise_for_status()
87
- response = Response.from_http(response_doh)
88
84
 
89
- exchange.response.message.answer += response.message.answer
85
+ try:
86
+ response_doh = self.CLIENT.post(url, headers=headers, content=exchange.query_last.to_wire())
87
+ response_doh.raise_for_status()
88
+ response = Response.from_http(response_doh)
89
+ exchange.response.message.answer += response.message.answer
90
+
91
+ except Exception as exception:
92
+ exchange.response.message.set_rcode(dnspython.rcode.SERVFAIL)
93
+ exchange.response.is_complete = True
94
+ logger.exception(exception)
@@ -1,6 +1,8 @@
1
1
  import dns
2
2
  import httpx
3
3
  from dataclasses import dataclass
4
+ from dns import rcode as dnsrcode
5
+ from dns import reversename
4
6
  from dns.message import Message, QueryMessage
5
7
  from dns.rrset import RRset
6
8
  from functools import cached_property
@@ -36,6 +38,7 @@ class Response(BaseDNSData):
36
38
 
37
39
  http: Optional[httpx.Response] = None
38
40
  is_complete: bool = False
41
+ blocked_by: Optional[str] = None
39
42
 
40
43
  @classmethod
41
44
  def from_http(cls, response: httpx.Response) -> Self:
@@ -58,6 +61,14 @@ class Response(BaseDNSData):
58
61
  return None
59
62
  return self.message.answer[-1]
60
63
 
64
+ @property
65
+ def rcode(self) -> dnsrcode.Rcode:
66
+ return self.message.rcode()
67
+
68
+ @property
69
+ def rcode_text(self) -> str:
70
+ return dnsrcode.to_text(self.rcode)
71
+
61
72
  def __str__(self):
62
73
  """
63
74
 
@@ -133,14 +144,16 @@ class Exchange:
133
144
 
134
145
  request: Request
135
146
  response: Optional[Response] = None
147
+ is_internal: bool = False
148
+ client_name: Optional[str] = None
149
+
136
150
 
137
151
 
138
152
  @classmethod
139
- def from_wire(cls, wire: bytes, ip: str, port: int) -> Self:
153
+ def from_wire(cls, wire: bytes, **kwargs) -> Self:
140
154
  request = Request(wire)
141
155
  response = Response.from_message(request.get_response_template())
142
-
143
- return cls(request=request, response=response, ip=ip, port=port)
156
+ return cls(request=request, response=response, **kwargs)
144
157
 
145
158
  @cached_property
146
159
  def client(self):
@@ -193,3 +206,15 @@ class Exchange:
193
206
  """
194
207
  data = tuple(self.request.question.to_text().split())
195
208
  return data
209
+
210
+ @cached_property
211
+ def reverse(self) -> Self:
212
+ """
213
+
214
+ Create an Exchange for a reverse lookup of this Exchange's client IP.
215
+
216
+ """
217
+ name = reversename.from_address(self.ip)
218
+ query = dns.message.make_query(name, dns.rdatatype.PTR)
219
+ exchange = self.__class__.from_wire(query.to_wire(), ip=self.ip, port=self.port, is_internal=True)
220
+ return exchange
@@ -68,19 +68,51 @@ class Plain:
68
68
  exchange.response.message.id = exchange.request.message.id
69
69
  exchange.response.is_complete = True
70
70
 
71
- def handle(self, exchange: Exchange):
71
+ def get_span(self, exchange: Exchange):
72
72
  """
73
73
 
74
- Check validity of request, presence in cache and resolve.
74
+ Get handling span
75
75
 
76
76
  """
77
77
  request = exchange.request
78
+ span = logger.span(
79
+ f'Handling request {exchange.client_name=} {request.message.id=} {request.type_text} {request.name_text} {request.question=}...'
80
+ )
81
+ return span
82
+
83
+ def log_response(self, exchange: Exchange):
84
+ """
78
85
 
79
- if not request.is_valid:
80
- raise ValueError(f'Only one question per request is supported. Got {len(request.question)} questions.')
86
+ Log when resolution complete
81
87
 
82
- with logger.span(f'Handling request {request.message.id=} {request.type_text} {request.name_text} {request.question=} {exchange.ip=} {exchange.port=}...'):
88
+ """
89
+ request = exchange.request
90
+ response = exchange.response
83
91
 
92
+ logger.info(
93
+ f'Resolution complete {exchange.client_name=} {request.message.id=} {request.type_text} {request.name_text} {request.question=} {response.is_complete=} {response.rcode=} {response.rcode_text=} {response.answer=} {response.blocked_by=}...'
94
+ )
95
+
96
+
97
+
98
+ def handle(self, exchange: Exchange):
99
+ """
100
+
101
+ Check validity of request, reverse lookup client address, check presence in cache and resolve.
102
+
103
+ """
104
+
105
+ if not exchange.request.is_valid:
106
+ raise ValueError(f'Only one question per request is supported. Got {len(exchange.request.question)} questions.')
107
+
108
+ if not exchange.is_internal:
109
+ self.handle(exchange.reverse)
110
+ client_name = exchange.reverse.question_last.name.to_text()
111
+ if not exchange.reverse.response.answer:
112
+ logger.warning(f'Client name could not be resolved {client_name=}.')
113
+ exchange.client_name = client_name
114
+
115
+ with self.get_span(exchange):
84
116
  with logger.span(f'Checking cache...'):
85
117
  self.check_cache(exchange)
86
118
 
@@ -89,5 +121,6 @@ class Plain:
89
121
  exchange.response.is_complete = True
90
122
 
91
123
  self.cache[exchange.key] = exchange.response
92
- logger.info(f'Resolution complete {request.message.id=} {exchange.response.answer=}')
124
+ self.log_response(exchange)
125
+
93
126
  return exchange
fmtr/tools/version CHANGED
@@ -1 +1 @@
1
- 1.3.12
1
+ 1.3.14
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmtr.tools
3
- Version: 1.3.12
3
+ Version: 1.3.14
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
@@ -126,61 +126,61 @@ Requires-Dist: logfire[httpx]; extra == "http"
126
126
  Provides-Extra: setup
127
127
  Requires-Dist: setuptools; extra == "setup"
128
128
  Provides-Extra: all
129
- Requires-Dist: dask[bag]; extra == "all"
130
- Requires-Dist: deepmerge; extra == "all"
131
- Requires-Dist: dnspython[doh]; extra == "all"
132
- Requires-Dist: bokeh; extra == "all"
133
- Requires-Dist: distributed; extra == "all"
134
- Requires-Dist: flet[all]; extra == "all"
135
- Requires-Dist: fastapi; extra == "all"
136
- Requires-Dist: google-api-python-client; extra == "all"
137
- Requires-Dist: cachetools; extra == "all"
138
- Requires-Dist: diskcache; extra == "all"
139
- Requires-Dist: pyyaml; extra == "all"
140
- Requires-Dist: regex; extra == "all"
141
- Requires-Dist: tinynetrc; extra == "all"
142
- Requires-Dist: pydantic; extra == "all"
143
- Requires-Dist: flet-webview; extra == "all"
144
- Requires-Dist: pandas; extra == "all"
145
- Requires-Dist: torchvision; extra == "all"
146
129
  Requires-Dist: sentence_transformers; extra == "all"
147
- Requires-Dist: sre_yield; extra == "all"
148
- Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
149
- Requires-Dist: json_repair; extra == "all"
150
- Requires-Dist: google-auth; extra == "all"
151
- Requires-Dist: tabulate; extra == "all"
152
- Requires-Dist: html2text; extra == "all"
153
- Requires-Dist: transformers[sentencepiece]; extra == "all"
154
- Requires-Dist: httpx_retries; extra == "all"
155
- Requires-Dist: pymupdf4llm; extra == "all"
156
- Requires-Dist: httpx; extra == "all"
130
+ Requires-Dist: Unidecode; extra == "all"
131
+ Requires-Dist: peft; extra == "all"
132
+ Requires-Dist: tinynetrc; extra == "all"
157
133
  Requires-Dist: contexttimer; extra == "all"
158
134
  Requires-Dist: torchaudio; extra == "all"
159
- Requires-Dist: semver; extra == "all"
160
- Requires-Dist: ollama; extra == "all"
161
- Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
162
- Requires-Dist: huggingface_hub; extra == "all"
163
135
  Requires-Dist: uvicorn[standard]; extra == "all"
164
- Requires-Dist: peft; extra == "all"
136
+ Requires-Dist: openpyxl; extra == "all"
165
137
  Requires-Dist: pydantic-settings; extra == "all"
166
- Requires-Dist: appdirs; extra == "all"
167
- Requires-Dist: flet-video; extra == "all"
168
- Requires-Dist: Unidecode; extra == "all"
169
- Requires-Dist: docker; extra == "all"
170
- Requires-Dist: filetype; extra == "all"
138
+ Requires-Dist: sre_yield; extra == "all"
139
+ Requires-Dist: distributed; extra == "all"
140
+ Requires-Dist: semver; extra == "all"
171
141
  Requires-Dist: tokenizers; extra == "all"
142
+ Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
143
+ Requires-Dist: fastapi; extra == "all"
144
+ Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
145
+ Requires-Dist: httpx_retries; extra == "all"
146
+ Requires-Dist: flet[all]; extra == "all"
147
+ Requires-Dist: pymupdf4llm; extra == "all"
148
+ Requires-Dist: filetype; extra == "all"
172
149
  Requires-Dist: pytest-cov; extra == "all"
173
- Requires-Dist: faker; extra == "all"
174
- Requires-Dist: openai; extra == "all"
175
- Requires-Dist: openpyxl; extra == "all"
150
+ Requires-Dist: flet-webview; extra == "all"
176
151
  Requires-Dist: pymupdf; extra == "all"
177
- Requires-Dist: logfire[httpx]; extra == "all"
152
+ Requires-Dist: json_repair; extra == "all"
153
+ Requires-Dist: openai; extra == "all"
154
+ Requires-Dist: google-auth; extra == "all"
155
+ Requires-Dist: huggingface_hub; extra == "all"
156
+ Requires-Dist: ollama; extra == "all"
157
+ Requires-Dist: faker; extra == "all"
158
+ Requires-Dist: dnspython[doh]; extra == "all"
178
159
  Requires-Dist: logfire[fastapi]; extra == "all"
179
- Requires-Dist: logfire; extra == "all"
180
- Requires-Dist: google-auth-httplib2; extra == "all"
181
- Requires-Dist: yamlscript; extra == "all"
160
+ Requires-Dist: httpx; extra == "all"
161
+ Requires-Dist: google-api-python-client; extra == "all"
162
+ Requires-Dist: docker; extra == "all"
182
163
  Requires-Dist: google-auth-oauthlib; extra == "all"
164
+ Requires-Dist: torchvision; extra == "all"
165
+ Requires-Dist: regex; extra == "all"
166
+ Requires-Dist: dask[bag]; extra == "all"
167
+ Requires-Dist: appdirs; extra == "all"
168
+ Requires-Dist: html2text; extra == "all"
169
+ Requires-Dist: deepmerge; extra == "all"
170
+ Requires-Dist: logfire[httpx]; extra == "all"
171
+ Requires-Dist: pandas; extra == "all"
172
+ Requires-Dist: flet-video; extra == "all"
173
+ Requires-Dist: tabulate; extra == "all"
174
+ Requires-Dist: google-auth-httplib2; extra == "all"
175
+ Requires-Dist: bokeh; extra == "all"
176
+ Requires-Dist: logfire; extra == "all"
177
+ Requires-Dist: cachetools; extra == "all"
183
178
  Requires-Dist: setuptools; extra == "all"
179
+ Requires-Dist: yamlscript; extra == "all"
180
+ Requires-Dist: pydantic; extra == "all"
181
+ Requires-Dist: transformers[sentencepiece]; extra == "all"
182
+ Requires-Dist: pyyaml; extra == "all"
183
+ Requires-Dist: diskcache; extra == "all"
184
184
  Dynamic: author
185
185
  Dynamic: author-email
186
186
  Dynamic: description
@@ -44,16 +44,16 @@ 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=vx8ZDie10HtXdoZhV-7t3WTQ7a7wkHXtTJNV9hcNSew,6
47
+ fmtr/tools/version,sha256=GG4QPlY_pdWNhfbn575pBPL9Qt2zJ_Kt1m89Ii4ieks,6
48
48
  fmtr/tools/yaml_tools.py,sha256=Bhhyd6GQVKO72Lp8ky7bAUjIB_65Hdh0Q45SKIEe6S8,1901
49
49
  fmtr/tools/ai_tools/__init__.py,sha256=JZrLuOFNV1A3wvJgonxOgz_4WS-7MfCuowGWA5uYCjs,372
50
50
  fmtr/tools/ai_tools/agentic_tools.py,sha256=acSEPFS-aguDXanWGs3fAAlRyJSYPZW7L-Kb2qDLm-I,4300
51
51
  fmtr/tools/ai_tools/inference_tools.py,sha256=2UP2gXEyOJUjyyV6zmFIYmIxUsh1rXkRH0IbFvr2bRs,11908
52
52
  fmtr/tools/dns_tools/__init__.py,sha256=PjD3Og6D5yvDVpKmsUsrnSpz_rjXpl4zBtvMqm8xKWU,237
53
- fmtr/tools/dns_tools/client.py,sha256=c2vzWBDZSxijwL1KvWUoDGc8wqk_KTAFxCr0P1rNjy8,2367
54
- fmtr/tools/dns_tools/dm.py,sha256=QqEYebamLWMzr1KM1x4P4dUvPZL_zvymZrNj6ghw0rg,4445
53
+ fmtr/tools/dns_tools/client.py,sha256=zAPJbQZIuNJPTA-HDBtrVZHvHRP_7QYWLgH7D73g5LU,2598
54
+ fmtr/tools/dns_tools/dm.py,sha256=_G3ELId69SDMm0YciWQCW9-cTwt1l6cMVCt5-3T9V58,5177
55
55
  fmtr/tools/dns_tools/proxy.py,sha256=b3TdSwRO7IwcNjrWg1e8jVQb-YxJhT377rdVkeDc8_I,1466
56
- fmtr/tools/dns_tools/server.py,sha256=opVruRsiQisWHxOM-UjWUsuOmm5CSiMGRjD2yvoP8aw,2683
56
+ fmtr/tools/dns_tools/server.py,sha256=yL76rbYesdr1kHqLis5q1bxc3nzq68Utx-_PWA3Vu9U,3656
57
57
  fmtr/tools/entrypoints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
58
  fmtr/tools/entrypoints/cache_hfh.py,sha256=fQNs4J9twQuZH_Yj98-oOvEX7-LrSUP3kO8nzw2HrHs,60
59
59
  fmtr/tools/entrypoints/ep_test.py,sha256=B8HfWISfSgw_xVX475CbJGh_QnpOe9MH65H8qGjTWbY,46
@@ -76,9 +76,9 @@ fmtr/tools/tests/test_path.py,sha256=AkZQa6_8BQ-VaCyL_J-iKmdf2ZaM-xFYR37Kun3k4_g
76
76
  fmtr/tools/tests/test_yaml.py,sha256=jc0TwwKu9eC0LvFGNMERdgBue591xwLxYXFbtsRwXVM,287
77
77
  fmtr/tools/version_tools/__init__.py,sha256=pg4iLtmIr5HtyEW_j0fMFoIdzqi_w9xH8-grQaXLB28,318
78
78
  fmtr/tools/version_tools/version_tools.py,sha256=Hcc6yferZS1hHbugRTdiHhSNmXEEG0hjCiTTXKna-YY,1127
79
- fmtr_tools-1.3.12.dist-info/licenses/LICENSE,sha256=FW9aa6vVN5IjRQWLT43hs4_koYSmpcbIovlKeAJ0_cI,10757
80
- fmtr_tools-1.3.12.dist-info/METADATA,sha256=BLA4cVtgZiKy1vuKtFSaYIldrbm7iVx99Dq0hq4i8hY,15938
81
- fmtr_tools-1.3.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
82
- fmtr_tools-1.3.12.dist-info/entry_points.txt,sha256=h-r__Xh5njtFqreMLg6cGuTFS4Qh-QqJPU1HB-_BS-Q,357
83
- fmtr_tools-1.3.12.dist-info/top_level.txt,sha256=LXem9xCgNOD72tE2gRKESdiQTL902mfFkwWb6-dlwEE,5
84
- fmtr_tools-1.3.12.dist-info/RECORD,,
79
+ fmtr_tools-1.3.14.dist-info/licenses/LICENSE,sha256=FW9aa6vVN5IjRQWLT43hs4_koYSmpcbIovlKeAJ0_cI,10757
80
+ fmtr_tools-1.3.14.dist-info/METADATA,sha256=Yj9yReion57e01vzQDF85BNGZVVLXHOUxfv8KQWYj1o,15938
81
+ fmtr_tools-1.3.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
82
+ fmtr_tools-1.3.14.dist-info/entry_points.txt,sha256=h-r__Xh5njtFqreMLg6cGuTFS4Qh-QqJPU1HB-_BS-Q,357
83
+ fmtr_tools-1.3.14.dist-info/top_level.txt,sha256=LXem9xCgNOD72tE2gRKESdiQTL902mfFkwWb6-dlwEE,5
84
+ fmtr_tools-1.3.14.dist-info/RECORD,,