fmtr.tools 1.3.15__tar.gz → 1.3.17__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 (91) hide show
  1. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/PKG-INFO +46 -46
  2. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/dns_tools/dm.py +40 -7
  3. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/dns_tools/proxy.py +1 -1
  4. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/dns_tools/server.py +44 -35
  5. fmtr_tools-1.3.17/fmtr/tools/version +1 -0
  6. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr.tools.egg-info/PKG-INFO +46 -46
  7. fmtr_tools-1.3.15/fmtr/tools/version +0 -1
  8. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/LICENSE +0 -0
  9. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/README.md +0 -0
  10. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/__init__.py +0 -0
  11. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/ai_tools/__init__.py +0 -0
  12. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/ai_tools/agentic_tools.py +0 -0
  13. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/ai_tools/inference_tools.py +0 -0
  14. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/api_tools.py +0 -0
  15. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/async_tools.py +0 -0
  16. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/augmentation_tools.py +0 -0
  17. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/caching_tools.py +0 -0
  18. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/constants.py +0 -0
  19. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/data_modelling_tools.py +0 -0
  20. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/dataclass_tools.py +0 -0
  21. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/datatype_tools.py +0 -0
  22. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/debugging_tools.py +0 -0
  23. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/dns_tools/__init__.py +0 -0
  24. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/dns_tools/client.py +0 -0
  25. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/docker_tools.py +0 -0
  26. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/entrypoints/__init__.py +0 -0
  27. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/entrypoints/cache_hfh.py +0 -0
  28. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/entrypoints/ep_test.py +0 -0
  29. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/entrypoints/install_yamlscript.py +0 -0
  30. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/entrypoints/remote_debug_test.py +0 -0
  31. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/entrypoints/shell_debug.py +0 -0
  32. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/environment_tools.py +0 -0
  33. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/function_tools.py +0 -0
  34. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/google_api_tools.py +0 -0
  35. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/hash_tools.py +0 -0
  36. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/hfh_tools.py +0 -0
  37. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/html_tools.py +0 -0
  38. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/http_tools.py +0 -0
  39. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/import_tools.py +0 -0
  40. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/inspection_tools.py +0 -0
  41. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/interface_tools.py +0 -0
  42. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/iterator_tools.py +0 -0
  43. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/json_fix_tools.py +0 -0
  44. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/json_tools.py +0 -0
  45. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/logging_tools.py +0 -0
  46. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/merging_tools.py +0 -0
  47. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/metric_tools.py +0 -0
  48. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/name_tools.py +0 -0
  49. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/netrc_tools.py +0 -0
  50. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/openai_tools.py +0 -0
  51. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/packaging_tools.py +0 -0
  52. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/parallel_tools.py +0 -0
  53. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/path_tools/__init__.py +0 -0
  54. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/path_tools/app_path_tools.py +0 -0
  55. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/path_tools/path_tools.py +0 -0
  56. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/path_tools/type_path_tools.py +0 -0
  57. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/pattern_tools.py +0 -0
  58. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/pdf_tools.py +0 -0
  59. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/platform_tools.py +0 -0
  60. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/process_tools.py +0 -0
  61. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/profiling_tools.py +0 -0
  62. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/random_tools.py +0 -0
  63. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/semantic_tools.py +0 -0
  64. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/settings_tools.py +0 -0
  65. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/setup_tools/__init__.py +0 -0
  66. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/setup_tools/setup_tools.py +0 -0
  67. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/spaces_tools.py +0 -0
  68. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/string_tools.py +0 -0
  69. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/tabular_tools.py +0 -0
  70. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/tests/__init__.py +0 -0
  71. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/tests/conftest.py +0 -0
  72. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/tests/helpers.py +0 -0
  73. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/tests/test_datatype.py +0 -0
  74. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/tests/test_environment.py +0 -0
  75. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/tests/test_json.py +0 -0
  76. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/tests/test_path.py +0 -0
  77. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/tests/test_yaml.py +0 -0
  78. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/tokenization_tools.py +0 -0
  79. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/tools.py +0 -0
  80. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/unicode_tools.py +0 -0
  81. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/version_tools/__init__.py +0 -0
  82. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/version_tools/version_tools.py +0 -0
  83. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr/tools/yaml_tools.py +0 -0
  84. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr.tools.egg-info/SOURCES.txt +0 -0
  85. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr.tools.egg-info/dependency_links.txt +0 -0
  86. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr.tools.egg-info/entry_points.txt +0 -0
  87. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr.tools.egg-info/requires.txt +45 -45
  88. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/fmtr.tools.egg-info/top_level.txt +0 -0
  89. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/pyproject.toml +0 -0
  90. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/setup.cfg +0 -0
  91. {fmtr_tools-1.3.15 → fmtr_tools-1.3.17}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmtr.tools
3
- Version: 1.3.15
3
+ Version: 1.3.17
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: flet-video; extra == "all"
130
- Requires-Dist: regex; extra == "all"
131
- Requires-Dist: dnspython[doh]; extra == "all"
132
- Requires-Dist: distributed; extra == "all"
133
- Requires-Dist: tinynetrc; extra == "all"
134
- Requires-Dist: pandas; extra == "all"
135
- Requires-Dist: contexttimer; extra == "all"
136
- Requires-Dist: peft; extra == "all"
137
- Requires-Dist: pydantic; extra == "all"
138
- Requires-Dist: google-auth; extra == "all"
139
- Requires-Dist: tokenizers; extra == "all"
140
- Requires-Dist: diskcache; extra == "all"
141
- Requires-Dist: cachetools; extra == "all"
142
- Requires-Dist: pymupdf; extra == "all"
143
- Requires-Dist: bokeh; extra == "all"
144
- Requires-Dist: tabulate; extra == "all"
145
- Requires-Dist: ollama; extra == "all"
146
129
  Requires-Dist: Unidecode; extra == "all"
147
- Requires-Dist: logfire; extra == "all"
148
- Requires-Dist: sre_yield; extra == "all"
149
- Requires-Dist: pyyaml; extra == "all"
150
- Requires-Dist: deepmerge; extra == "all"
151
- Requires-Dist: huggingface_hub; extra == "all"
152
- Requires-Dist: openai; extra == "all"
153
- Requires-Dist: sentence_transformers; extra == "all"
154
- Requires-Dist: openpyxl; extra == "all"
130
+ Requires-Dist: semver; extra == "all"
131
+ Requires-Dist: google-auth-httplib2; extra == "all"
132
+ Requires-Dist: setuptools; extra == "all"
133
+ Requires-Dist: dask[bag]; extra == "all"
155
134
  Requires-Dist: httpx_retries; extra == "all"
156
135
  Requires-Dist: flet-webview; extra == "all"
157
- Requires-Dist: pytest-cov; extra == "all"
136
+ Requires-Dist: docker; extra == "all"
137
+ Requires-Dist: bokeh; extra == "all"
138
+ Requires-Dist: filetype; extra == "all"
158
139
  Requires-Dist: torchvision; extra == "all"
159
- Requires-Dist: torchaudio; extra == "all"
160
140
  Requires-Dist: faker; extra == "all"
161
- Requires-Dist: logfire[httpx]; extra == "all"
162
- Requires-Dist: flet[all]; extra == "all"
163
- Requires-Dist: google-api-python-client; extra == "all"
164
- Requires-Dist: json_repair; extra == "all"
165
- Requires-Dist: google-auth-oauthlib; extra == "all"
141
+ Requires-Dist: uvicorn[standard]; extra == "all"
166
142
  Requires-Dist: fastapi; extra == "all"
167
- Requires-Dist: yamlscript; extra == "all"
168
- Requires-Dist: setuptools; extra == "all"
169
- Requires-Dist: semver; extra == "all"
170
- Requires-Dist: pymupdf4llm; extra == "all"
171
- Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
143
+ Requires-Dist: regex; extra == "all"
144
+ Requires-Dist: openai; extra == "all"
172
145
  Requires-Dist: appdirs; extra == "all"
173
- Requires-Dist: google-auth-httplib2; extra == "all"
174
- Requires-Dist: pydantic-settings; extra == "all"
175
- Requires-Dist: uvicorn[standard]; extra == "all"
176
146
  Requires-Dist: httpx; extra == "all"
177
- Requires-Dist: docker; extra == "all"
178
- Requires-Dist: transformers[sentencepiece]; extra == "all"
179
- Requires-Dist: dask[bag]; extra == "all"
180
- Requires-Dist: filetype; extra == "all"
181
- Requires-Dist: logfire[fastapi]; extra == "all"
147
+ Requires-Dist: distributed; extra == "all"
148
+ Requires-Dist: google-api-python-client; extra == "all"
149
+ Requires-Dist: peft; extra == "all"
150
+ Requires-Dist: google-auth-oauthlib; extra == "all"
151
+ Requires-Dist: pyyaml; extra == "all"
152
+ Requires-Dist: json_repair; extra == "all"
153
+ Requires-Dist: deepmerge; extra == "all"
154
+ Requires-Dist: cachetools; extra == "all"
155
+ Requires-Dist: flet[all]; extra == "all"
156
+ Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
182
157
  Requires-Dist: html2text; extra == "all"
158
+ Requires-Dist: ollama; extra == "all"
159
+ Requires-Dist: pandas; extra == "all"
160
+ Requires-Dist: pymupdf4llm; extra == "all"
161
+ Requires-Dist: pydantic-settings; extra == "all"
162
+ Requires-Dist: flet-video; extra == "all"
163
+ Requires-Dist: sentence_transformers; extra == "all"
164
+ Requires-Dist: pymupdf; extra == "all"
165
+ Requires-Dist: logfire[httpx]; extra == "all"
166
+ Requires-Dist: huggingface_hub; extra == "all"
167
+ Requires-Dist: openpyxl; extra == "all"
168
+ Requires-Dist: contexttimer; extra == "all"
169
+ Requires-Dist: yamlscript; extra == "all"
170
+ Requires-Dist: pydantic; extra == "all"
171
+ Requires-Dist: sre_yield; extra == "all"
183
172
  Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
173
+ Requires-Dist: tokenizers; extra == "all"
174
+ Requires-Dist: torchaudio; extra == "all"
175
+ Requires-Dist: logfire[fastapi]; extra == "all"
176
+ Requires-Dist: pytest-cov; extra == "all"
177
+ Requires-Dist: tinynetrc; extra == "all"
178
+ Requires-Dist: tabulate; extra == "all"
179
+ Requires-Dist: diskcache; extra == "all"
180
+ Requires-Dist: transformers[sentencepiece]; extra == "all"
181
+ Requires-Dist: google-auth; extra == "all"
182
+ Requires-Dist: logfire; extra == "all"
183
+ Requires-Dist: dnspython[doh]; extra == "all"
184
184
  Dynamic: author
185
185
  Dynamic: author-email
186
186
  Dynamic: description
@@ -10,6 +10,19 @@ from typing import Self, Optional, List
10
10
 
11
11
  from fmtr.tools.string_tools import join
12
12
 
13
+ TTL_CODE_DEFAULTS = {
14
+ dnsrcode.NOERROR: 300, # Successful query
15
+ dnsrcode.FORMERR: 60, # Format error
16
+ dnsrcode.SERVFAIL: 10, # Server failure
17
+ dnsrcode.NXDOMAIN: 60 * 60, # Non-existent domain
18
+ dnsrcode.NOTIMP: 60, # Not implemented
19
+ dnsrcode.REFUSED: 60, # Refused
20
+ dnsrcode.YXDOMAIN: 600, # Name exists when it should not
21
+ dnsrcode.YXRRSET: 600, # RR Set exists when it should not
22
+ dnsrcode.NXRRSET: 300, # RR Set that should exist does not
23
+ dnsrcode.NOTAUTH: 60, # Not authorized
24
+ dnsrcode.NOTZONE: 60 # Name not contained in zone
25
+ }
13
26
 
14
27
  @dataclass
15
28
  class BaseDNSData:
@@ -68,6 +81,24 @@ class Response(BaseDNSData):
68
81
  def rcode_text(self) -> str:
69
82
  return dnsrcode.to_text(self.rcode)
70
83
 
84
+ @property
85
+ def ttl(self) -> int:
86
+ """
87
+
88
+ Get median TTL from answers, falling back to authority, then to error-code defaults.
89
+
90
+ """
91
+ answers = self.message.answer or self.message.authority
92
+ if answers:
93
+ ttls = [answer.ttl for answer in answers]
94
+ ttl = min(ttls)
95
+ return ttl
96
+
97
+ ttl = TTL_CODE_DEFAULTS.get(self.rcode, dnsrcode.NXDOMAIN)
98
+ return ttl
99
+
100
+
101
+
71
102
  def __str__(self):
72
103
  """
73
104
 
@@ -148,6 +179,9 @@ class Exchange:
148
179
  client_name: Optional[str] = None
149
180
  is_complete: bool = False
150
181
 
182
+ @property
183
+ def addr(self):
184
+ return self.ip, self.port
151
185
 
152
186
  @classmethod
153
187
  def from_wire(cls, wire: bytes, **kwargs) -> Self:
@@ -163,26 +197,25 @@ class Exchange:
163
197
  def question_last(self) -> RRset:
164
198
  """
165
199
 
166
- Contrive an RRset representing the latest/current question. This can be the original question - or a hybrid one if we've injected our own answers into the exchange.
200
+ Create an RRset surrogate representing the latest/current question. This can be the original question - or a hybrid one if we've injected our own answers into the Exchange.
167
201
 
168
202
  """
169
203
  if self.answers_pre:
170
204
  rrset = self.answers_pre[-1]
171
- ty = self.request.type
205
+ rdtype = self.request.type
172
206
  ttl = self.request.question.ttl
173
207
  rdclass = self.request.question.rdclass
174
208
  name = next(iter(rrset.items.keys())).to_text()
175
-
176
- rrset_contrived = dns.rrset.from_text(
209
+ rrset_surrogate = dns.rrset.from_text(
177
210
  name=name,
178
211
  ttl=ttl,
179
- rdtype=ty,
212
+ rdtype=rdtype,
180
213
  rdclass=rdclass,
181
214
  )
182
215
 
183
- return rrset_contrived
216
+ return rrset_surrogate
184
217
  else:
185
- return self.request.question # Solves the issue of digging out the name.
218
+ return self.request.question
186
219
 
187
220
  @property
188
221
  def query_last(self) -> QueryMessage:
@@ -39,7 +39,7 @@ class Proxy(server.Plain):
39
39
  """
40
40
  exchange.is_complete = True
41
41
 
42
- def resolve(self, exchange: Exchange) -> Exchange:
42
+ async def resolve(self, exchange: Exchange) -> Exchange:
43
43
  """
44
44
 
45
45
  Resolve a request, processing each stage, initial question, upstream response etc.
@@ -1,7 +1,9 @@
1
- import socket
2
- from dataclasses import dataclass
1
+ import asyncio
2
+ import dns.rcode
3
+ from dataclasses import dataclass, field
3
4
  from datetime import timedelta
4
5
  from functools import cached_property
6
+ from typing import Optional
5
7
 
6
8
  from fmtr.tools import caching_tools as caching
7
9
  from fmtr.tools.dns_tools.dm import Exchange
@@ -9,59 +11,61 @@ from fmtr.tools.logging_tools import logger
9
11
 
10
12
 
11
13
  @dataclass(kw_only=True, eq=False)
12
- class Plain:
14
+ class Plain(asyncio.DatagramProtocol):
13
15
  """
14
16
 
15
- Base for starting a plain DNS server
16
-
17
+ Async base class for a plain DNS server using asyncio DatagramProtocol.
17
18
  """
18
19
 
19
20
  host: str
20
21
  port: int
22
+ transport: Optional[asyncio.DatagramTransport] = field(default=None, init=False)
21
23
 
22
24
  @cached_property
23
- def sock(self):
24
- return socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
25
+ def loop(self):
26
+ return asyncio.get_event_loop()
27
+
25
28
 
26
29
  @cached_property
27
30
  def cache(self):
28
31
  """
29
32
 
30
33
  Overridable cache.
31
-
32
34
  """
33
35
  cache = caching.TLRU(maxsize=1_024, ttu_static=timedelta(hours=1), desc='DNS Request')
34
36
  return cache
35
37
 
36
- def start(self):
37
- """
38
+ def connection_made(self, transport: asyncio.DatagramTransport):
39
+ self.transport = transport
40
+ logger.info(f'Listening on {self.host}:{self.port}')
38
41
 
39
- Listen and resolve via overridden resolve method.
42
+ def datagram_received(self, data: bytes, addr):
43
+ ip, port = addr
44
+ exchange = Exchange.from_wire(data, ip=ip, port=port)
45
+ asyncio.create_task(self.handle(exchange))
40
46
 
47
+ async def start(self):
41
48
  """
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
49
 
51
- def resolve(self, exchange: Exchange) -> Exchange:
50
+ Start the async UDP server.
52
51
  """
53
52
 
54
- Defined in subclasses
53
+ logger.info(f'Starting async DNS server on {self.host}:{self.port}...')
54
+ await self.loop.create_datagram_endpoint(
55
+ lambda: self,
56
+ local_addr=(self.host, self.port)
57
+ )
58
+ await asyncio.Future() # Prevent exit by blocking forever
55
59
 
60
+ async def resolve(self, exchange: Exchange) -> Exchange:
56
61
  """
57
- raise NotImplemented
58
62
 
59
- def check_cache(self, exchange: Exchange):
60
- """
61
-
62
- Check cache, patch in in new ID and mark complete
63
+ To be defined in subclasses.
63
64
 
64
65
  """
66
+ raise NotImplementedError
67
+
68
+ def check_cache(self, exchange: Exchange):
65
69
  if exchange.key in self.cache:
66
70
  logger.info(f'Request found in cache.')
67
71
  exchange.response = self.cache[exchange.key]
@@ -88,25 +92,30 @@ class Plain:
88
92
  """
89
93
  request = exchange.request
90
94
  response = exchange.response
91
-
92
95
  logger.info(
93
96
  f'Resolution complete {exchange.client_name=} {request.message.id=} {request.type_text} {request.name_text} {request.question=} {exchange.is_complete=} {response.rcode=} {response.rcode_text=} {response.answer=} {response.blocked_by=}...'
94
97
  )
95
98
 
99
+ def log_dns_errors(self, exchange: Exchange):
100
+ """
96
101
 
102
+ Warn about any errors
97
103
 
98
- def handle(self, exchange: Exchange):
99
104
  """
105
+ if exchange.response.rcode != dns.rcode.NOERROR:
106
+ logger.warning(f'Error {exchange.response.rcode_text=}')
100
107
 
101
- Check validity of request, reverse lookup client address, check presence in cache and resolve.
102
-
108
+ async def handle(self, exchange: Exchange):
103
109
  """
104
110
 
111
+ Warn about any errors
112
+
113
+ """
105
114
  if not exchange.request.is_valid:
106
115
  raise ValueError(f'Only one question per request is supported. Got {len(exchange.request.question)} questions.')
107
116
 
108
117
  if not exchange.is_internal:
109
- self.handle(exchange.reverse)
118
+ await self.handle(exchange.reverse)
110
119
  client_name = exchange.reverse.question_last.name.to_text()
111
120
  if not exchange.reverse.response.answer:
112
121
  logger.warning(f'Client name could not be resolved {client_name=}.')
@@ -117,10 +126,10 @@ class Plain:
117
126
  self.check_cache(exchange)
118
127
 
119
128
  if not exchange.is_complete:
120
- exchange = self.resolve(exchange)
121
- exchange.is_complete = True
129
+ exchange = await self.resolve(exchange)
130
+ self.cache[exchange.key] = exchange.response
122
131
 
123
- self.cache[exchange.key] = exchange.response
132
+ self.log_dns_errors(exchange)
124
133
  self.log_response(exchange)
125
134
 
126
- return exchange
135
+ self.transport.sendto(exchange.response.message.to_wire(), exchange.addr)
@@ -0,0 +1 @@
1
+ 1.3.17
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmtr.tools
3
- Version: 1.3.15
3
+ Version: 1.3.17
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: flet-video; extra == "all"
130
- Requires-Dist: regex; extra == "all"
131
- Requires-Dist: dnspython[doh]; extra == "all"
132
- Requires-Dist: distributed; extra == "all"
133
- Requires-Dist: tinynetrc; extra == "all"
134
- Requires-Dist: pandas; extra == "all"
135
- Requires-Dist: contexttimer; extra == "all"
136
- Requires-Dist: peft; extra == "all"
137
- Requires-Dist: pydantic; extra == "all"
138
- Requires-Dist: google-auth; extra == "all"
139
- Requires-Dist: tokenizers; extra == "all"
140
- Requires-Dist: diskcache; extra == "all"
141
- Requires-Dist: cachetools; extra == "all"
142
- Requires-Dist: pymupdf; extra == "all"
143
- Requires-Dist: bokeh; extra == "all"
144
- Requires-Dist: tabulate; extra == "all"
145
- Requires-Dist: ollama; extra == "all"
146
129
  Requires-Dist: Unidecode; extra == "all"
147
- Requires-Dist: logfire; extra == "all"
148
- Requires-Dist: sre_yield; extra == "all"
149
- Requires-Dist: pyyaml; extra == "all"
150
- Requires-Dist: deepmerge; extra == "all"
151
- Requires-Dist: huggingface_hub; extra == "all"
152
- Requires-Dist: openai; extra == "all"
153
- Requires-Dist: sentence_transformers; extra == "all"
154
- Requires-Dist: openpyxl; extra == "all"
130
+ Requires-Dist: semver; extra == "all"
131
+ Requires-Dist: google-auth-httplib2; extra == "all"
132
+ Requires-Dist: setuptools; extra == "all"
133
+ Requires-Dist: dask[bag]; extra == "all"
155
134
  Requires-Dist: httpx_retries; extra == "all"
156
135
  Requires-Dist: flet-webview; extra == "all"
157
- Requires-Dist: pytest-cov; extra == "all"
136
+ Requires-Dist: docker; extra == "all"
137
+ Requires-Dist: bokeh; extra == "all"
138
+ Requires-Dist: filetype; extra == "all"
158
139
  Requires-Dist: torchvision; extra == "all"
159
- Requires-Dist: torchaudio; extra == "all"
160
140
  Requires-Dist: faker; extra == "all"
161
- Requires-Dist: logfire[httpx]; extra == "all"
162
- Requires-Dist: flet[all]; extra == "all"
163
- Requires-Dist: google-api-python-client; extra == "all"
164
- Requires-Dist: json_repair; extra == "all"
165
- Requires-Dist: google-auth-oauthlib; extra == "all"
141
+ Requires-Dist: uvicorn[standard]; extra == "all"
166
142
  Requires-Dist: fastapi; extra == "all"
167
- Requires-Dist: yamlscript; extra == "all"
168
- Requires-Dist: setuptools; extra == "all"
169
- Requires-Dist: semver; extra == "all"
170
- Requires-Dist: pymupdf4llm; extra == "all"
171
- Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
143
+ Requires-Dist: regex; extra == "all"
144
+ Requires-Dist: openai; extra == "all"
172
145
  Requires-Dist: appdirs; extra == "all"
173
- Requires-Dist: google-auth-httplib2; extra == "all"
174
- Requires-Dist: pydantic-settings; extra == "all"
175
- Requires-Dist: uvicorn[standard]; extra == "all"
176
146
  Requires-Dist: httpx; extra == "all"
177
- Requires-Dist: docker; extra == "all"
178
- Requires-Dist: transformers[sentencepiece]; extra == "all"
179
- Requires-Dist: dask[bag]; extra == "all"
180
- Requires-Dist: filetype; extra == "all"
181
- Requires-Dist: logfire[fastapi]; extra == "all"
147
+ Requires-Dist: distributed; extra == "all"
148
+ Requires-Dist: google-api-python-client; extra == "all"
149
+ Requires-Dist: peft; extra == "all"
150
+ Requires-Dist: google-auth-oauthlib; extra == "all"
151
+ Requires-Dist: pyyaml; extra == "all"
152
+ Requires-Dist: json_repair; extra == "all"
153
+ Requires-Dist: deepmerge; extra == "all"
154
+ Requires-Dist: cachetools; extra == "all"
155
+ Requires-Dist: flet[all]; extra == "all"
156
+ Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
182
157
  Requires-Dist: html2text; extra == "all"
158
+ Requires-Dist: ollama; extra == "all"
159
+ Requires-Dist: pandas; extra == "all"
160
+ Requires-Dist: pymupdf4llm; extra == "all"
161
+ Requires-Dist: pydantic-settings; extra == "all"
162
+ Requires-Dist: flet-video; extra == "all"
163
+ Requires-Dist: sentence_transformers; extra == "all"
164
+ Requires-Dist: pymupdf; extra == "all"
165
+ Requires-Dist: logfire[httpx]; extra == "all"
166
+ Requires-Dist: huggingface_hub; extra == "all"
167
+ Requires-Dist: openpyxl; extra == "all"
168
+ Requires-Dist: contexttimer; extra == "all"
169
+ Requires-Dist: yamlscript; extra == "all"
170
+ Requires-Dist: pydantic; extra == "all"
171
+ Requires-Dist: sre_yield; extra == "all"
183
172
  Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
173
+ Requires-Dist: tokenizers; extra == "all"
174
+ Requires-Dist: torchaudio; extra == "all"
175
+ Requires-Dist: logfire[fastapi]; extra == "all"
176
+ Requires-Dist: pytest-cov; extra == "all"
177
+ Requires-Dist: tinynetrc; extra == "all"
178
+ Requires-Dist: tabulate; extra == "all"
179
+ Requires-Dist: diskcache; extra == "all"
180
+ Requires-Dist: transformers[sentencepiece]; extra == "all"
181
+ Requires-Dist: google-auth; extra == "all"
182
+ Requires-Dist: logfire; extra == "all"
183
+ Requires-Dist: dnspython[doh]; extra == "all"
184
184
  Dynamic: author
185
185
  Dynamic: author-email
186
186
  Dynamic: description
@@ -1 +0,0 @@
1
- 1.3.15
File without changes
File without changes
@@ -14,61 +14,61 @@ pydantic-ai[logfire,openai]
14
14
  ollama
15
15
 
16
16
  [all]
17
- flet-video
18
- regex
19
- dnspython[doh]
20
- distributed
21
- tinynetrc
22
- pandas
23
- contexttimer
24
- peft
25
- pydantic
26
- google-auth
27
- tokenizers
28
- diskcache
29
- cachetools
30
- pymupdf
31
- bokeh
32
- tabulate
33
- ollama
34
17
  Unidecode
35
- logfire
36
- sre_yield
37
- pyyaml
38
- deepmerge
39
- huggingface_hub
40
- openai
41
- sentence_transformers
42
- openpyxl
18
+ semver
19
+ google-auth-httplib2
20
+ setuptools
21
+ dask[bag]
43
22
  httpx_retries
44
23
  flet-webview
45
- pytest-cov
24
+ docker
25
+ bokeh
26
+ filetype
46
27
  torchvision
47
- torchaudio
48
28
  faker
49
- logfire[httpx]
50
- flet[all]
51
- google-api-python-client
52
- json_repair
53
- google-auth-oauthlib
29
+ uvicorn[standard]
54
30
  fastapi
55
- yamlscript
56
- setuptools
57
- semver
58
- pymupdf4llm
59
- pydevd-pycharm~=251.25410.159
31
+ regex
32
+ openai
60
33
  appdirs
61
- google-auth-httplib2
62
- pydantic-settings
63
- uvicorn[standard]
64
34
  httpx
65
- docker
66
- transformers[sentencepiece]
67
- dask[bag]
68
- filetype
69
- logfire[fastapi]
35
+ distributed
36
+ google-api-python-client
37
+ peft
38
+ google-auth-oauthlib
39
+ pyyaml
40
+ json_repair
41
+ deepmerge
42
+ cachetools
43
+ flet[all]
44
+ pydevd-pycharm~=251.25410.159
70
45
  html2text
46
+ ollama
47
+ pandas
48
+ pymupdf4llm
49
+ pydantic-settings
50
+ flet-video
51
+ sentence_transformers
52
+ pymupdf
53
+ logfire[httpx]
54
+ huggingface_hub
55
+ openpyxl
56
+ contexttimer
57
+ yamlscript
58
+ pydantic
59
+ sre_yield
71
60
  pydantic-ai[logfire,openai]
61
+ tokenizers
62
+ torchaudio
63
+ logfire[fastapi]
64
+ pytest-cov
65
+ tinynetrc
66
+ tabulate
67
+ diskcache
68
+ transformers[sentencepiece]
69
+ google-auth
70
+ logfire
71
+ dnspython[doh]
72
72
 
73
73
  [api]
74
74
  fastapi
File without changes
File without changes
File without changes