fmtr.tools 1.2.1__tar.gz → 1.2.3__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 (86) hide show
  1. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/PKG-INFO +47 -42
  2. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/ai_tools/agentic_tools.py +28 -4
  3. fmtr_tools-1.2.3/fmtr/tools/dns_tools.py +221 -0
  4. fmtr_tools-1.2.3/fmtr/tools/version +1 -0
  5. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr.tools.egg-info/PKG-INFO +47 -42
  6. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr.tools.egg-info/requires.txt +46 -41
  7. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/setup.py +1 -1
  8. fmtr_tools-1.2.1/fmtr/tools/dns_tools.py +0 -57
  9. fmtr_tools-1.2.1/fmtr/tools/version +0 -1
  10. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/LICENSE +0 -0
  11. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/README.md +0 -0
  12. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/__init__.py +0 -0
  13. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/ai_tools/__init__.py +0 -0
  14. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/ai_tools/inference_tools.py +0 -0
  15. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/api_tools.py +0 -0
  16. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/async_tools.py +0 -0
  17. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/augmentation_tools.py +0 -0
  18. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/caching_tools.py +0 -0
  19. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/constants.py +0 -0
  20. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/data_modelling_tools.py +0 -0
  21. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/dataclass_tools.py +0 -0
  22. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/datatype_tools.py +0 -0
  23. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/debugging_tools.py +0 -0
  24. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/docker_tools.py +0 -0
  25. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/entrypoints/__init__.py +0 -0
  26. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/entrypoints/cache_hfh.py +0 -0
  27. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/entrypoints/ep_test.py +0 -0
  28. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/entrypoints/remote_debug_test.py +0 -0
  29. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/entrypoints/shell_debug.py +0 -0
  30. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/environment_tools.py +0 -0
  31. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/function_tools.py +0 -0
  32. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/google_api_tools.py +0 -0
  33. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/hash_tools.py +0 -0
  34. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/hfh_tools.py +0 -0
  35. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/html_tools.py +0 -0
  36. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/http_tools.py +0 -0
  37. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/import_tools.py +0 -0
  38. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/inspection_tools.py +0 -0
  39. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/interface_tools.py +0 -0
  40. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/iterator_tools.py +0 -0
  41. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/json_fix_tools.py +0 -0
  42. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/json_tools.py +0 -0
  43. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/logging_tools.py +0 -0
  44. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/merging_tools.py +0 -0
  45. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/metric_tools.py +0 -0
  46. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/name_tools.py +0 -0
  47. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/netrc_tools.py +0 -0
  48. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/openai_tools.py +0 -0
  49. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/packaging_tools.py +0 -0
  50. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/parallel_tools.py +0 -0
  51. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/path_tools/__init__.py +0 -0
  52. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/path_tools/app_path_tools.py +0 -0
  53. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/path_tools/path_tools.py +0 -0
  54. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/path_tools/type_path_tools.py +0 -0
  55. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/pattern_tools.py +0 -0
  56. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/pdf_tools.py +0 -0
  57. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/platform_tools.py +0 -0
  58. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/process_tools.py +0 -0
  59. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/profiling_tools.py +0 -0
  60. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/random_tools.py +0 -0
  61. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/semantic_tools.py +0 -0
  62. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/settings_tools.py +0 -0
  63. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/setup_tools/__init__.py +0 -0
  64. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/setup_tools/setup_tools.py +0 -0
  65. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/spaces_tools.py +0 -0
  66. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/string_tools.py +0 -0
  67. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tabular_tools.py +0 -0
  68. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/__init__.py +0 -0
  69. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/conftest.py +0 -0
  70. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/helpers.py +0 -0
  71. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/test_datatype.py +0 -0
  72. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/test_environment.py +0 -0
  73. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/test_json.py +0 -0
  74. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/test_path.py +0 -0
  75. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/test_yaml.py +0 -0
  76. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tokenization_tools.py +0 -0
  77. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tools.py +0 -0
  78. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/unicode_tools.py +0 -0
  79. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/version_tools.py +0 -0
  80. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/yaml_tools.py +0 -0
  81. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr.tools.egg-info/SOURCES.txt +0 -0
  82. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr.tools.egg-info/dependency_links.txt +0 -0
  83. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr.tools.egg-info/entry_points.txt +0 -0
  84. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr.tools.egg-info/top_level.txt +0 -0
  85. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/pyproject.toml +0 -0
  86. {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmtr.tools
3
- Version: 1.2.1
3
+ Version: 1.2.3
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/tools
6
6
  Author: Frontmatter
@@ -112,6 +112,11 @@ Provides-Extra: path-type
112
112
  Requires-Dist: filetype; extra == "path-type"
113
113
  Provides-Extra: dns
114
114
  Requires-Dist: dnspython[doh]; extra == "dns"
115
+ Requires-Dist: httpx; extra == "dns"
116
+ Requires-Dist: httpx_retries; extra == "dns"
117
+ Requires-Dist: logfire; extra == "dns"
118
+ Requires-Dist: semver; extra == "dns"
119
+ Requires-Dist: logfire[httpx]; extra == "dns"
115
120
  Provides-Extra: patterns
116
121
  Requires-Dist: regex; extra == "patterns"
117
122
  Provides-Extra: http
@@ -123,60 +128,60 @@ Requires-Dist: logfire[httpx]; extra == "http"
123
128
  Provides-Extra: setup
124
129
  Requires-Dist: setuptools; extra == "setup"
125
130
  Provides-Extra: all
126
- Requires-Dist: docker; extra == "all"
127
- Requires-Dist: huggingface_hub; extra == "all"
131
+ Requires-Dist: logfire; extra == "all"
132
+ Requires-Dist: tokenizers; extra == "all"
133
+ Requires-Dist: appdirs; extra == "all"
134
+ Requires-Dist: pydantic-settings; extra == "all"
128
135
  Requires-Dist: filetype; extra == "all"
129
- Requires-Dist: httpx; extra == "all"
130
- Requires-Dist: torchvision; extra == "all"
136
+ Requires-Dist: pymupdf; extra == "all"
137
+ Requires-Dist: json_repair; extra == "all"
138
+ Requires-Dist: sentence_transformers; extra == "all"
131
139
  Requires-Dist: fastapi; extra == "all"
132
- Requires-Dist: google-auth-oauthlib; extra == "all"
133
- Requires-Dist: tinynetrc; extra == "all"
134
- Requires-Dist: html2text; extra == "all"
140
+ Requires-Dist: tabulate; extra == "all"
135
141
  Requires-Dist: distributed; extra == "all"
136
142
  Requires-Dist: faker; extra == "all"
137
- Requires-Dist: openpyxl; extra == "all"
138
- Requires-Dist: pyyaml; extra == "all"
139
- Requires-Dist: transformers[sentencepiece]; extra == "all"
140
- Requires-Dist: flet-video; extra == "all"
141
- Requires-Dist: flet[all]; extra == "all"
142
- Requires-Dist: logfire[fastapi]; extra == "all"
143
- Requires-Dist: ollama; extra == "all"
144
- Requires-Dist: Unidecode; extra == "all"
145
- Requires-Dist: google-api-python-client; extra == "all"
146
- Requires-Dist: diskcache; extra == "all"
147
- Requires-Dist: pymupdf; extra == "all"
148
- Requires-Dist: torchaudio; extra == "all"
149
- Requires-Dist: setuptools; extra == "all"
150
- Requires-Dist: pytest-cov; extra == "all"
151
- Requires-Dist: google-auth-httplib2; extra == "all"
152
143
  Requires-Dist: semver; extra == "all"
153
- Requires-Dist: yamlscript; extra == "all"
154
144
  Requires-Dist: uvicorn[standard]; extra == "all"
155
- Requires-Dist: pydantic-settings; extra == "all"
145
+ Requires-Dist: html2text; extra == "all"
146
+ Requires-Dist: google-auth-httplib2; extra == "all"
147
+ Requires-Dist: bokeh; extra == "all"
156
148
  Requires-Dist: flet-webview; extra == "all"
157
- Requires-Dist: tokenizers; extra == "all"
149
+ Requires-Dist: logfire[httpx]; extra == "all"
150
+ Requires-Dist: torchaudio; extra == "all"
151
+ Requires-Dist: Unidecode; extra == "all"
152
+ Requires-Dist: flet-video; extra == "all"
153
+ Requires-Dist: google-auth; extra == "all"
154
+ Requires-Dist: flet[all]; extra == "all"
158
155
  Requires-Dist: openai; extra == "all"
156
+ Requires-Dist: pymupdf4llm; extra == "all"
159
157
  Requires-Dist: dnspython[doh]; extra == "all"
160
- Requires-Dist: bokeh; extra == "all"
158
+ Requires-Dist: deepmerge; extra == "all"
161
159
  Requires-Dist: contexttimer; extra == "all"
162
- Requires-Dist: appdirs; extra == "all"
163
- Requires-Dist: dask[bag]; extra == "all"
164
- Requires-Dist: pydevd-pycharm; extra == "all"
165
- Requires-Dist: sentence_transformers; extra == "all"
166
- Requires-Dist: pymupdf4llm; extra == "all"
167
- Requires-Dist: pandas; extra == "all"
160
+ Requires-Dist: openpyxl; extra == "all"
161
+ Requires-Dist: tinynetrc; extra == "all"
162
+ Requires-Dist: ollama; extra == "all"
163
+ Requires-Dist: huggingface_hub; extra == "all"
164
+ Requires-Dist: google-auth-oauthlib; extra == "all"
165
+ Requires-Dist: httpx_retries; extra == "all"
166
+ Requires-Dist: docker; extra == "all"
167
+ Requires-Dist: setuptools; extra == "all"
168
168
  Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
169
+ Requires-Dist: google-api-python-client; extra == "all"
170
+ Requires-Dist: torchvision; extra == "all"
171
+ Requires-Dist: logfire[fastapi]; extra == "all"
172
+ Requires-Dist: pandas; extra == "all"
169
173
  Requires-Dist: regex; extra == "all"
170
- Requires-Dist: logfire[httpx]; extra == "all"
171
- Requires-Dist: pydantic; extra == "all"
172
- Requires-Dist: peft; extra == "all"
173
- Requires-Dist: google-auth; extra == "all"
174
- Requires-Dist: httpx_retries; extra == "all"
175
- Requires-Dist: tabulate; extra == "all"
176
- Requires-Dist: json_repair; extra == "all"
174
+ Requires-Dist: httpx; extra == "all"
177
175
  Requires-Dist: sre_yield; extra == "all"
178
- Requires-Dist: deepmerge; extra == "all"
179
- Requires-Dist: logfire; extra == "all"
176
+ Requires-Dist: pyyaml; extra == "all"
177
+ Requires-Dist: peft; extra == "all"
178
+ Requires-Dist: pydantic; extra == "all"
179
+ Requires-Dist: pytest-cov; extra == "all"
180
+ Requires-Dist: yamlscript; extra == "all"
181
+ Requires-Dist: transformers[sentencepiece]; extra == "all"
182
+ Requires-Dist: diskcache; extra == "all"
183
+ Requires-Dist: dask[bag]; extra == "all"
184
+ Requires-Dist: pydevd-pycharm; extra == "all"
180
185
  Dynamic: author
181
186
  Dynamic: author-email
182
187
  Dynamic: description
@@ -1,17 +1,31 @@
1
- from typing import List, Optional
2
-
3
1
  import pydantic_ai
4
- from pydantic_ai import RunContext
2
+ from pydantic_ai import RunContext, ModelRetry
5
3
  from pydantic_ai.agent import AgentRunResult, Agent
6
4
  from pydantic_ai.models.openai import OpenAIModel
7
5
  from pydantic_ai.providers.openai import OpenAIProvider
6
+ from typing import List, Optional, Any
8
7
 
9
8
  from fmtr.tools import environment_tools as env
10
9
  from fmtr.tools.constants import Constants
10
+ from fmtr.tools.logging_tools import logger
11
11
  from fmtr.tools.string_tools import truncate_mid
12
12
 
13
13
  pydantic_ai.Agent.instrument_all()
14
14
 
15
+
16
+ class Validator:
17
+ """
18
+
19
+ Subclassable validator
20
+
21
+ """
22
+
23
+ async def validate(self, ctx: RunContext[Any], output: Any) -> List[str]:
24
+ raise NotImplementedError()
25
+
26
+
27
+
28
+
15
29
  class Task:
16
30
  """
17
31
 
@@ -31,6 +45,7 @@ class Task:
31
45
  DEPS_TYPE = str
32
46
  RESULT_TYPE = str
33
47
  RESULT_RETRIES = 5
48
+ VALIDATORS: List[Validator] = []
34
49
 
35
50
  def __init__(self, *args, **kwargs):
36
51
  """
@@ -77,9 +92,18 @@ class Task:
77
92
  async def validate(self, ctx: RunContext[DEPS_TYPE], output: RESULT_TYPE) -> RESULT_TYPE:
78
93
  """
79
94
 
80
- Dummy validator
95
+ Aggregate any validation failures and combine them into a single ModelRetry exception
81
96
 
82
97
  """
98
+ msgs = []
99
+ for validator in self.VALIDATORS:
100
+ msgs += validator.validate(ctx, output)
101
+
102
+ if msgs:
103
+ msg = '. '.join(msgs)
104
+ logger.warning(msg)
105
+ raise ModelRetry(msg)
106
+
83
107
  return output
84
108
 
85
109
  def get_prompt(self, deps: Optional[DEPS_TYPE]) -> Optional[str]:
@@ -0,0 +1,221 @@
1
+ import dns
2
+ import httpx
3
+ import socket
4
+ from dataclasses import dataclass
5
+ from dns.message import Message
6
+ from functools import cached_property
7
+ from httpx_retries import Retry, RetryTransport
8
+ from typing import Optional, Self
9
+
10
+ from fmtr.tools import Client, logger
11
+
12
+ RETRY_STRATEGY = Retry(
13
+ total=2, # initial + 1 retry
14
+ allowed_methods={"GET", "POST"},
15
+ status_forcelist={502, 503, 504},
16
+ retry_on_exceptions=None, # defaults to httpx.TransportError etc.
17
+ backoff_factor=0.25, # short backoff (e.g. 0.25s, 0.5s)
18
+ max_backoff_wait=0.75, # max total delay before giving up
19
+ backoff_jitter=0.1, # small jitter to avoid retry bursts
20
+ respect_retry_after_header=False, # DoH resolvers probably won't set this
21
+ )
22
+
23
+
24
+ class HTTPClientDoH(Client):
25
+ """
26
+
27
+ Base HTTP client for DoH-appropriate retry strategy.
28
+
29
+ """
30
+ TRANSPORT = RetryTransport(retry=RETRY_STRATEGY)
31
+
32
+
33
+ @dataclass
34
+ class BaseDNSData:
35
+ """
36
+
37
+ DNS response object.
38
+
39
+ """
40
+ wire: bytes
41
+
42
+ @cached_property
43
+ def message(self) -> Message:
44
+ return dns.message.from_wire(self.wire)
45
+
46
+ @classmethod
47
+ def from_message(cls, message: Message) -> Self:
48
+ return cls(message.to_wire())
49
+
50
+
51
+ @dataclass
52
+ class Response(BaseDNSData):
53
+ """
54
+
55
+ DNS response object.
56
+
57
+ """
58
+
59
+ http: Optional[httpx.Response] = None
60
+
61
+ @classmethod
62
+ def from_http(cls, response: httpx.Response) -> Self:
63
+ self = cls(response.content, http=response)
64
+ return self
65
+
66
+
67
+ @dataclass
68
+ class Request(BaseDNSData):
69
+ """
70
+
71
+ DNS request object.
72
+
73
+ """
74
+ wire: bytes
75
+
76
+ @cached_property
77
+ def question(self):
78
+ return self.message.question[0]
79
+
80
+ @cached_property
81
+ def is_valid(self):
82
+ return len(self.message.question) != 0
83
+
84
+ @cached_property
85
+ def type(self):
86
+ return self.question.rdtype
87
+
88
+ @cached_property
89
+ def type_text(self):
90
+ return dns.rdatatype.to_text(self.type)
91
+
92
+ @cached_property
93
+ def name(self):
94
+ return self.question.name
95
+
96
+ @cached_property
97
+ def name_text(self):
98
+ return self.name.to_text()
99
+
100
+ @cached_property
101
+ def blackhole(self) -> Response:
102
+ blackhole = dns.message.make_response(self.message)
103
+ blackhole.flags |= dns.flags.RA
104
+ blackhole.set_rcode(dns.rcode.NXDOMAIN)
105
+ response = Response.from_message(blackhole)
106
+ return response
107
+
108
+
109
+ @dataclass
110
+ class Exchange:
111
+ """
112
+
113
+ Entire DNS exchange for a DNS Proxy: request -> upstream response -> response
114
+
115
+ """
116
+ ip: str
117
+ port: int
118
+
119
+ request: Request
120
+ response: Optional[Response] = None
121
+ response_upstream: Optional[Response] = None
122
+
123
+ @classmethod
124
+ def from_wire(cls, wire: bytes, ip: str, port: int) -> Self:
125
+ request = Request(wire)
126
+ return cls(request=request, ip=ip, port=port)
127
+
128
+ @cached_property
129
+ def client(self):
130
+ return f'{self.ip}:{self.port}'
131
+
132
+
133
+ class BasePlain:
134
+ """
135
+
136
+ Base for starting a plain DNS server
137
+
138
+ """
139
+
140
+ def __init__(self, host, port):
141
+ self.host = host
142
+ self.port = port
143
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
144
+
145
+ def resolve(self, exchange: Exchange):
146
+ raise NotImplemented
147
+
148
+ def start(self):
149
+ """
150
+
151
+ Listen and resolve via overridden resolve method.
152
+
153
+ """
154
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
155
+ sock.bind((self.host, self.port))
156
+ print(f"Listening on {self.host}:{self.port}")
157
+ while True:
158
+ data, (ip, port) = sock.recvfrom(512)
159
+ exchange = Exchange.from_wire(data, ip=ip, port=port)
160
+ self.resolve(exchange)
161
+ sock.sendto(exchange.response.wire, (ip, port))
162
+
163
+
164
+ class BaseDoHProxy(BasePlain):
165
+ """
166
+
167
+ Base for a DNS Proxy server
168
+
169
+ """
170
+
171
+ URL = None
172
+ HEADERS = {"Content-Type": "application/dns-message"}
173
+ client = HTTPClientDoH()
174
+
175
+ def process_question(self, exchange: Exchange):
176
+ return
177
+
178
+ def process_upstream(self, exchange: Exchange):
179
+ return
180
+
181
+ def from_upstream(self, exchange: Exchange) -> Exchange:
182
+
183
+ request = exchange.request
184
+ response_doh = self.client.post(self.URL, headers=self.HEADERS, content=request.wire)
185
+ response_doh.raise_for_status()
186
+ response = Response.from_http(response_doh)
187
+ exchange.response_upstream = response
188
+
189
+ return exchange
190
+
191
+ def resolve(self, exchange: Exchange):
192
+ """
193
+
194
+ Resolve a request, processing each stage, initial question, upstream response etc.
195
+ Subclasses can override the relevant processing methods to implement custom behaviour.
196
+
197
+ """
198
+
199
+ request = exchange.request
200
+
201
+ with logger.span(f'Handling request for {request.name_text} from {exchange.client}...'):
202
+
203
+ if not request.is_valid:
204
+ raise ValueError(f'Only one question per request is supported. Got {len(request.question)} questions.')
205
+
206
+ with logger.span(f'Processing question...'):
207
+ self.process_question(exchange)
208
+ if exchange.response:
209
+ return
210
+
211
+ with logger.span(f'Making upstream request for {request.name_text}...'):
212
+ self.from_upstream(exchange)
213
+
214
+ with logger.span(f'Processing upstream response...'):
215
+ self.process_upstream(exchange)
216
+
217
+ if exchange.response:
218
+ return
219
+
220
+ exchange.response = exchange.response_upstream
221
+ return
@@ -0,0 +1 @@
1
+ 1.2.3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmtr.tools
3
- Version: 1.2.1
3
+ Version: 1.2.3
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/tools
6
6
  Author: Frontmatter
@@ -112,6 +112,11 @@ Provides-Extra: path-type
112
112
  Requires-Dist: filetype; extra == "path-type"
113
113
  Provides-Extra: dns
114
114
  Requires-Dist: dnspython[doh]; extra == "dns"
115
+ Requires-Dist: httpx; extra == "dns"
116
+ Requires-Dist: httpx_retries; extra == "dns"
117
+ Requires-Dist: logfire; extra == "dns"
118
+ Requires-Dist: semver; extra == "dns"
119
+ Requires-Dist: logfire[httpx]; extra == "dns"
115
120
  Provides-Extra: patterns
116
121
  Requires-Dist: regex; extra == "patterns"
117
122
  Provides-Extra: http
@@ -123,60 +128,60 @@ Requires-Dist: logfire[httpx]; extra == "http"
123
128
  Provides-Extra: setup
124
129
  Requires-Dist: setuptools; extra == "setup"
125
130
  Provides-Extra: all
126
- Requires-Dist: docker; extra == "all"
127
- Requires-Dist: huggingface_hub; extra == "all"
131
+ Requires-Dist: logfire; extra == "all"
132
+ Requires-Dist: tokenizers; extra == "all"
133
+ Requires-Dist: appdirs; extra == "all"
134
+ Requires-Dist: pydantic-settings; extra == "all"
128
135
  Requires-Dist: filetype; extra == "all"
129
- Requires-Dist: httpx; extra == "all"
130
- Requires-Dist: torchvision; extra == "all"
136
+ Requires-Dist: pymupdf; extra == "all"
137
+ Requires-Dist: json_repair; extra == "all"
138
+ Requires-Dist: sentence_transformers; extra == "all"
131
139
  Requires-Dist: fastapi; extra == "all"
132
- Requires-Dist: google-auth-oauthlib; extra == "all"
133
- Requires-Dist: tinynetrc; extra == "all"
134
- Requires-Dist: html2text; extra == "all"
140
+ Requires-Dist: tabulate; extra == "all"
135
141
  Requires-Dist: distributed; extra == "all"
136
142
  Requires-Dist: faker; extra == "all"
137
- Requires-Dist: openpyxl; extra == "all"
138
- Requires-Dist: pyyaml; extra == "all"
139
- Requires-Dist: transformers[sentencepiece]; extra == "all"
140
- Requires-Dist: flet-video; extra == "all"
141
- Requires-Dist: flet[all]; extra == "all"
142
- Requires-Dist: logfire[fastapi]; extra == "all"
143
- Requires-Dist: ollama; extra == "all"
144
- Requires-Dist: Unidecode; extra == "all"
145
- Requires-Dist: google-api-python-client; extra == "all"
146
- Requires-Dist: diskcache; extra == "all"
147
- Requires-Dist: pymupdf; extra == "all"
148
- Requires-Dist: torchaudio; extra == "all"
149
- Requires-Dist: setuptools; extra == "all"
150
- Requires-Dist: pytest-cov; extra == "all"
151
- Requires-Dist: google-auth-httplib2; extra == "all"
152
143
  Requires-Dist: semver; extra == "all"
153
- Requires-Dist: yamlscript; extra == "all"
154
144
  Requires-Dist: uvicorn[standard]; extra == "all"
155
- Requires-Dist: pydantic-settings; extra == "all"
145
+ Requires-Dist: html2text; extra == "all"
146
+ Requires-Dist: google-auth-httplib2; extra == "all"
147
+ Requires-Dist: bokeh; extra == "all"
156
148
  Requires-Dist: flet-webview; extra == "all"
157
- Requires-Dist: tokenizers; extra == "all"
149
+ Requires-Dist: logfire[httpx]; extra == "all"
150
+ Requires-Dist: torchaudio; extra == "all"
151
+ Requires-Dist: Unidecode; extra == "all"
152
+ Requires-Dist: flet-video; extra == "all"
153
+ Requires-Dist: google-auth; extra == "all"
154
+ Requires-Dist: flet[all]; extra == "all"
158
155
  Requires-Dist: openai; extra == "all"
156
+ Requires-Dist: pymupdf4llm; extra == "all"
159
157
  Requires-Dist: dnspython[doh]; extra == "all"
160
- Requires-Dist: bokeh; extra == "all"
158
+ Requires-Dist: deepmerge; extra == "all"
161
159
  Requires-Dist: contexttimer; extra == "all"
162
- Requires-Dist: appdirs; extra == "all"
163
- Requires-Dist: dask[bag]; extra == "all"
164
- Requires-Dist: pydevd-pycharm; extra == "all"
165
- Requires-Dist: sentence_transformers; extra == "all"
166
- Requires-Dist: pymupdf4llm; extra == "all"
167
- Requires-Dist: pandas; extra == "all"
160
+ Requires-Dist: openpyxl; extra == "all"
161
+ Requires-Dist: tinynetrc; extra == "all"
162
+ Requires-Dist: ollama; extra == "all"
163
+ Requires-Dist: huggingface_hub; extra == "all"
164
+ Requires-Dist: google-auth-oauthlib; extra == "all"
165
+ Requires-Dist: httpx_retries; extra == "all"
166
+ Requires-Dist: docker; extra == "all"
167
+ Requires-Dist: setuptools; extra == "all"
168
168
  Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
169
+ Requires-Dist: google-api-python-client; extra == "all"
170
+ Requires-Dist: torchvision; extra == "all"
171
+ Requires-Dist: logfire[fastapi]; extra == "all"
172
+ Requires-Dist: pandas; extra == "all"
169
173
  Requires-Dist: regex; extra == "all"
170
- Requires-Dist: logfire[httpx]; extra == "all"
171
- Requires-Dist: pydantic; extra == "all"
172
- Requires-Dist: peft; extra == "all"
173
- Requires-Dist: google-auth; extra == "all"
174
- Requires-Dist: httpx_retries; extra == "all"
175
- Requires-Dist: tabulate; extra == "all"
176
- Requires-Dist: json_repair; extra == "all"
174
+ Requires-Dist: httpx; extra == "all"
177
175
  Requires-Dist: sre_yield; extra == "all"
178
- Requires-Dist: deepmerge; extra == "all"
179
- Requires-Dist: logfire; extra == "all"
176
+ Requires-Dist: pyyaml; extra == "all"
177
+ Requires-Dist: peft; extra == "all"
178
+ Requires-Dist: pydantic; extra == "all"
179
+ Requires-Dist: pytest-cov; extra == "all"
180
+ Requires-Dist: yamlscript; extra == "all"
181
+ Requires-Dist: transformers[sentencepiece]; extra == "all"
182
+ Requires-Dist: diskcache; extra == "all"
183
+ Requires-Dist: dask[bag]; extra == "all"
184
+ Requires-Dist: pydevd-pycharm; extra == "all"
180
185
  Dynamic: author
181
186
  Dynamic: author-email
182
187
  Dynamic: description
@@ -15,60 +15,60 @@ pydantic-ai[logfire,openai]
15
15
  ollama
16
16
 
17
17
  [all]
18
- docker
19
- huggingface_hub
18
+ logfire
19
+ tokenizers
20
+ appdirs
21
+ pydantic-settings
20
22
  filetype
21
- httpx
22
- torchvision
23
+ pymupdf
24
+ json_repair
25
+ sentence_transformers
23
26
  fastapi
24
- google-auth-oauthlib
25
- tinynetrc
26
- html2text
27
+ tabulate
27
28
  distributed
28
29
  faker
29
- openpyxl
30
- pyyaml
31
- transformers[sentencepiece]
32
- flet-video
33
- flet[all]
34
- logfire[fastapi]
35
- ollama
36
- Unidecode
37
- google-api-python-client
38
- diskcache
39
- pymupdf
40
- torchaudio
41
- setuptools
42
- pytest-cov
43
- google-auth-httplib2
44
30
  semver
45
- yamlscript
46
31
  uvicorn[standard]
47
- pydantic-settings
32
+ html2text
33
+ google-auth-httplib2
34
+ bokeh
48
35
  flet-webview
49
- tokenizers
36
+ logfire[httpx]
37
+ torchaudio
38
+ Unidecode
39
+ flet-video
40
+ google-auth
41
+ flet[all]
50
42
  openai
43
+ pymupdf4llm
51
44
  dnspython[doh]
52
- bokeh
45
+ deepmerge
53
46
  contexttimer
54
- appdirs
55
- dask[bag]
56
- pydevd-pycharm
57
- sentence_transformers
58
- pymupdf4llm
59
- pandas
47
+ openpyxl
48
+ tinynetrc
49
+ ollama
50
+ huggingface_hub
51
+ google-auth-oauthlib
52
+ httpx_retries
53
+ docker
54
+ setuptools
60
55
  pydantic-ai[logfire,openai]
56
+ google-api-python-client
57
+ torchvision
58
+ logfire[fastapi]
59
+ pandas
61
60
  regex
62
- logfire[httpx]
63
- pydantic
64
- peft
65
- google-auth
66
- httpx_retries
67
- tabulate
68
- json_repair
61
+ httpx
69
62
  sre_yield
70
- deepmerge
71
- logfire
63
+ pyyaml
64
+ peft
65
+ pydantic
66
+ pytest-cov
67
+ yamlscript
68
+ transformers[sentencepiece]
69
+ diskcache
70
+ dask[bag]
71
+ pydevd-pycharm
72
72
 
73
73
  [api]
74
74
  fastapi
@@ -93,6 +93,11 @@ pydantic
93
93
 
94
94
  [dns]
95
95
  dnspython[doh]
96
+ httpx
97
+ httpx_retries
98
+ logfire
99
+ semver
100
+ logfire[httpx]
96
101
 
97
102
  [docker.api]
98
103
  docker
@@ -35,7 +35,7 @@ DEPENDENCIES = {
35
35
  'sets': ['pydantic-settings', 'dm'],
36
36
  'path.app': ['appdirs'],
37
37
  'path.type': ['filetype'],
38
- 'dns': ['dnspython[doh]'],
38
+ 'dns': ['dnspython[doh]', 'http'],
39
39
  'patterns': ['regex'],
40
40
  'http': ['httpx', 'httpx_retries', 'logging', 'logfire[httpx]'],
41
41
  'setup': ['setuptools']
@@ -1,57 +0,0 @@
1
- import dns
2
- import httpx
3
- import socket
4
- from dns.message import Message, QueryMessage
5
-
6
-
7
- class Server:
8
-
9
- def __init__(self, host, port):
10
- self.host = host
11
- self.port = port
12
- self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
13
-
14
- def resolve(self, message: Message) -> QueryMessage:
15
- raise NotImplemented
16
-
17
- def start(self):
18
- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
19
- sock.bind((self.host, self.port))
20
- print(f"Listening on {self.host}:{self.port}")
21
- while True:
22
- data, addr = sock.recvfrom(512)
23
- question = dns.message.from_wire(data)
24
- answer = self.resolve(question)
25
- sock.sendto(answer.to_wire(), addr)
26
-
27
-
28
- class Proxy(Server):
29
- url = "https://cloudflare-dns.com/dns-query"
30
- headers = {"accept": "application/dns-json"}
31
-
32
- def resolve(self, question: Message) -> Message:
33
- qname = question.question[0].name.to_text()
34
- qtype = dns.rdatatype.to_text(question.question[0].rdtype)
35
- params = {"name": qname, "type": qtype}
36
-
37
- response = httpx.get(self.url, headers=self.headers, params=params)
38
- response.raise_for_status()
39
-
40
- answer = dns.message.make_response(question)
41
- answer.flags |= dns.flags.RA
42
-
43
- data = response.json()
44
- for answer_rr in data.get("Answer", []):
45
- name = dns.name.from_text(answer_rr["name"])
46
- rtype = answer_rr["type"]
47
- ttl = answer_rr["TTL"]
48
- rdata = dns.rdata.from_text(dns.rdataclass.IN, rtype, answer_rr["data"])
49
- rrset = dns.rrset.from_rdata(name, ttl, rdata)
50
- answer.answer.append(rrset)
51
-
52
- return answer
53
-
54
-
55
- if __name__ == "__main__":
56
- proxy = Proxy("127.0.0.1", 5354)
57
- proxy.start()
@@ -1 +0,0 @@
1
- 1.2.1
File without changes
File without changes
File without changes
File without changes