fmtr.tools 1.3.1__tar.gz → 1.3.2__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.
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/PKG-INFO +45 -45
- fmtr_tools-1.3.2/fmtr/tools/dns_tools/__init__.py +6 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/dns_tools/client.py +28 -19
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/dns_tools/dm.py +59 -10
- fmtr_tools-1.3.1/fmtr/tools/dns_tools/server.py → fmtr_tools-1.3.2/fmtr/tools/dns_tools/proxy.py +17 -38
- fmtr_tools-1.3.2/fmtr/tools/dns_tools/server.py +38 -0
- fmtr_tools-1.3.2/fmtr/tools/version +1 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr.tools.egg-info/PKG-INFO +45 -45
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr.tools.egg-info/SOURCES.txt +2 -0
- fmtr_tools-1.3.1/fmtr/tools/dns_tools/__init__.py +0 -6
- fmtr_tools-1.3.1/fmtr/tools/version +0 -1
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/LICENSE +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/README.md +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/__init__.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/ai_tools/__init__.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/ai_tools/agentic_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/ai_tools/inference_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/api_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/async_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/augmentation_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/caching_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/constants.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/data_modelling_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/dataclass_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/datatype_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/debugging_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/docker_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/entrypoints/__init__.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/entrypoints/cache_hfh.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/entrypoints/ep_test.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/entrypoints/remote_debug_test.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/entrypoints/shell_debug.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/environment_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/function_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/google_api_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/hash_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/hfh_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/html_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/http_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/import_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/inspection_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/interface_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/iterator_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/json_fix_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/json_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/logging_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/merging_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/metric_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/name_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/netrc_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/openai_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/packaging_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/parallel_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/path_tools/__init__.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/path_tools/app_path_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/path_tools/path_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/path_tools/type_path_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/pattern_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/pdf_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/platform_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/process_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/profiling_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/random_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/semantic_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/settings_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/setup_tools/__init__.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/setup_tools/setup_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/spaces_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/string_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/tabular_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/tests/__init__.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/tests/conftest.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/tests/helpers.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/tests/test_datatype.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/tests/test_environment.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/tests/test_json.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/tests/test_path.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/tests/test_yaml.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/tokenization_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/unicode_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/version_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr/tools/yaml_tools.py +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr.tools.egg-info/dependency_links.txt +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr.tools.egg-info/entry_points.txt +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr.tools.egg-info/requires.txt +44 -44
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/fmtr.tools.egg-info/top_level.txt +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/pyproject.toml +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/setup.cfg +0 -0
- {fmtr_tools-1.3.1 → fmtr_tools-1.3.2}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fmtr.tools
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
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
|
|
@@ -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:
|
|
134
|
-
Requires-Dist:
|
|
135
|
-
Requires-Dist:
|
|
136
|
-
Requires-Dist:
|
|
137
|
-
Requires-Dist:
|
|
133
|
+
Requires-Dist: uvicorn[standard]; extra == "all"
|
|
134
|
+
Requires-Dist: openpyxl; extra == "all"
|
|
135
|
+
Requires-Dist: sentence_transformers; extra == "all"
|
|
136
|
+
Requires-Dist: peft; extra == "all"
|
|
137
|
+
Requires-Dist: httpx_retries; extra == "all"
|
|
138
138
|
Requires-Dist: sre_yield; extra == "all"
|
|
139
|
-
Requires-Dist:
|
|
139
|
+
Requires-Dist: faker; extra == "all"
|
|
140
140
|
Requires-Dist: ollama; extra == "all"
|
|
141
|
-
Requires-Dist:
|
|
142
|
-
Requires-Dist:
|
|
143
|
-
Requires-Dist: google-auth-httplib2; extra == "all"
|
|
144
|
-
Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
|
|
145
|
-
Requires-Dist: pydevd-pycharm; extra == "all"
|
|
146
|
-
Requires-Dist: torchvision; extra == "all"
|
|
147
|
-
Requires-Dist: pyyaml; extra == "all"
|
|
148
|
-
Requires-Dist: tabulate; extra == "all"
|
|
149
|
-
Requires-Dist: pymupdf; extra == "all"
|
|
150
|
-
Requires-Dist: docker; extra == "all"
|
|
151
|
-
Requires-Dist: openai; extra == "all"
|
|
152
|
-
Requires-Dist: sentence_transformers; extra == "all"
|
|
153
|
-
Requires-Dist: dnspython[doh]; extra == "all"
|
|
154
|
-
Requires-Dist: regex; extra == "all"
|
|
155
|
-
Requires-Dist: semver; extra == "all"
|
|
141
|
+
Requires-Dist: logfire; extra == "all"
|
|
142
|
+
Requires-Dist: bokeh; extra == "all"
|
|
156
143
|
Requires-Dist: setuptools; extra == "all"
|
|
157
|
-
Requires-Dist:
|
|
158
|
-
Requires-Dist:
|
|
159
|
-
Requires-Dist: google-api-python-client; extra == "all"
|
|
144
|
+
Requires-Dist: dask[bag]; extra == "all"
|
|
145
|
+
Requires-Dist: diskcache; extra == "all"
|
|
160
146
|
Requires-Dist: torchaudio; extra == "all"
|
|
161
|
-
Requires-Dist:
|
|
162
|
-
Requires-Dist:
|
|
163
|
-
Requires-Dist:
|
|
147
|
+
Requires-Dist: tokenizers; extra == "all"
|
|
148
|
+
Requires-Dist: pydantic; extra == "all"
|
|
149
|
+
Requires-Dist: pandas; extra == "all"
|
|
150
|
+
Requires-Dist: flet-video; extra == "all"
|
|
151
|
+
Requires-Dist: Unidecode; extra == "all"
|
|
152
|
+
Requires-Dist: pydantic-settings; extra == "all"
|
|
153
|
+
Requires-Dist: dnspython[doh]; extra == "all"
|
|
154
|
+
Requires-Dist: logfire[httpx]; extra == "all"
|
|
164
155
|
Requires-Dist: yamlscript; extra == "all"
|
|
156
|
+
Requires-Dist: fastapi; extra == "all"
|
|
157
|
+
Requires-Dist: openai; extra == "all"
|
|
158
|
+
Requires-Dist: deepmerge; extra == "all"
|
|
159
|
+
Requires-Dist: html2text; extra == "all"
|
|
160
|
+
Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
|
|
161
|
+
Requires-Dist: contexttimer; extra == "all"
|
|
162
|
+
Requires-Dist: flet[all]; extra == "all"
|
|
163
|
+
Requires-Dist: logfire[fastapi]; extra == "all"
|
|
164
|
+
Requires-Dist: regex; extra == "all"
|
|
165
|
+
Requires-Dist: docker; extra == "all"
|
|
165
166
|
Requires-Dist: filetype; extra == "all"
|
|
166
|
-
Requires-Dist: pydantic-settings; extra == "all"
|
|
167
|
-
Requires-Dist: flet-webview; extra == "all"
|
|
168
167
|
Requires-Dist: google-auth-oauthlib; extra == "all"
|
|
168
|
+
Requires-Dist: tabulate; extra == "all"
|
|
169
169
|
Requires-Dist: pytest-cov; extra == "all"
|
|
170
|
-
Requires-Dist:
|
|
171
|
-
Requires-Dist: logfire[fastapi]; extra == "all"
|
|
172
|
-
Requires-Dist: pandas; extra == "all"
|
|
173
|
-
Requires-Dist: html2text; extra == "all"
|
|
174
|
-
Requires-Dist: dask[bag]; extra == "all"
|
|
175
|
-
Requires-Dist: uvicorn[standard]; extra == "all"
|
|
176
|
-
Requires-Dist: json_repair; extra == "all"
|
|
177
|
-
Requires-Dist: flet-video; extra == "all"
|
|
178
|
-
Requires-Dist: httpx; extra == "all"
|
|
179
|
-
Requires-Dist: faker; extra == "all"
|
|
180
|
-
Requires-Dist: peft; extra == "all"
|
|
181
|
-
Requires-Dist: tokenizers; extra == "all"
|
|
170
|
+
Requires-Dist: pyyaml; extra == "all"
|
|
182
171
|
Requires-Dist: transformers[sentencepiece]; extra == "all"
|
|
183
|
-
Requires-Dist:
|
|
184
|
-
Requires-Dist:
|
|
172
|
+
Requires-Dist: pymupdf; extra == "all"
|
|
173
|
+
Requires-Dist: pydevd-pycharm; extra == "all"
|
|
174
|
+
Requires-Dist: torchvision; extra == "all"
|
|
175
|
+
Requires-Dist: google-auth; extra == "all"
|
|
176
|
+
Requires-Dist: flet-webview; extra == "all"
|
|
185
177
|
Requires-Dist: huggingface_hub; extra == "all"
|
|
186
|
-
Requires-Dist:
|
|
178
|
+
Requires-Dist: httpx; extra == "all"
|
|
179
|
+
Requires-Dist: semver; extra == "all"
|
|
180
|
+
Requires-Dist: json_repair; extra == "all"
|
|
181
|
+
Requires-Dist: tinynetrc; extra == "all"
|
|
182
|
+
Requires-Dist: google-auth-httplib2; extra == "all"
|
|
183
|
+
Requires-Dist: appdirs; extra == "all"
|
|
184
|
+
Requires-Dist: pymupdf4llm; extra == "all"
|
|
185
|
+
Requires-Dist: distributed; extra == "all"
|
|
186
|
+
Requires-Dist: google-api-python-client; extra == "all"
|
|
187
187
|
Dynamic: author
|
|
188
188
|
Dynamic: author-email
|
|
189
189
|
Dynamic: description
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import dns
|
|
2
1
|
from dataclasses import dataclass
|
|
3
|
-
from dns import query
|
|
4
2
|
from functools import cached_property
|
|
3
|
+
|
|
4
|
+
import dns as dnspython
|
|
5
|
+
from dns import query
|
|
5
6
|
from httpx_retries import Retry, RetryTransport
|
|
6
7
|
|
|
7
8
|
from fmtr.tools import http_tools as http
|
|
8
|
-
from fmtr.tools.dns_tools.dm import Exchange, Response
|
|
9
|
+
from fmtr.tools.dns_tools.dm import Exchange, Response
|
|
9
10
|
from fmtr.tools.logging_tools import logger
|
|
10
11
|
|
|
11
12
|
RETRY_STRATEGY = Retry(
|
|
@@ -29,28 +30,36 @@ class HTTPClientDoH(http.Client):
|
|
|
29
30
|
TRANSPORT = RetryTransport(retry=RETRY_STRATEGY)
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
@dataclass
|
|
34
|
+
class Plain:
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
Plain DNS
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
host: str
|
|
41
|
+
port: int = 53
|
|
36
42
|
|
|
37
43
|
def resolve(self, exchange: Exchange):
|
|
44
|
+
|
|
38
45
|
with logger.span(f'UDP {self.host}:{self.port}'):
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
response_plain = query.udp(q=exchange.query_last, where=self.host, port=self.port)
|
|
47
|
+
response = Response.from_message(response_plain)
|
|
48
|
+
|
|
49
|
+
exchange.response.message.answer += response.message.answer
|
|
41
50
|
|
|
42
51
|
|
|
43
52
|
@dataclass
|
|
44
|
-
class
|
|
53
|
+
class HTTP:
|
|
45
54
|
"""
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
DNS over HTTP
|
|
48
57
|
|
|
49
58
|
"""
|
|
50
59
|
|
|
51
60
|
HEADERS = {"Content-Type": "application/dns-message"}
|
|
52
61
|
CLIENT = HTTPClientDoH()
|
|
53
|
-
BOOTSTRAP =
|
|
62
|
+
BOOTSTRAP = Plain('8.8.8.8')
|
|
54
63
|
|
|
55
64
|
host: str
|
|
56
65
|
url: str
|
|
@@ -58,11 +67,10 @@ class ClientDoH:
|
|
|
58
67
|
|
|
59
68
|
@cached_property
|
|
60
69
|
def ip(self):
|
|
61
|
-
message =
|
|
62
|
-
|
|
63
|
-
exchange = Exchange(request=request, ip=None, port=None)
|
|
70
|
+
message = dnspython.message.make_query(self.host, dnspython.rdatatype.A, flags=0)
|
|
71
|
+
exchange = Exchange.from_wire(message.to_wire(), ip=None, port=None)
|
|
64
72
|
self.BOOTSTRAP.resolve(exchange)
|
|
65
|
-
ip = next(iter(exchange.
|
|
73
|
+
ip = next(iter(exchange.response.answer.items.keys())).address
|
|
66
74
|
return ip
|
|
67
75
|
|
|
68
76
|
def resolve(self, exchange: Exchange):
|
|
@@ -71,10 +79,11 @@ class ClientDoH:
|
|
|
71
79
|
Resolve via DoH
|
|
72
80
|
|
|
73
81
|
"""
|
|
74
|
-
|
|
82
|
+
|
|
75
83
|
headers = self.HEADERS | dict(Host=self.host)
|
|
76
84
|
url = self.url.format(host=self.ip)
|
|
77
|
-
response_doh = self.CLIENT.post(url, headers=headers, content=
|
|
85
|
+
response_doh = self.CLIENT.post(url, headers=headers, content=exchange.query_last.to_wire())
|
|
78
86
|
response_doh.raise_for_status()
|
|
79
87
|
response = Response.from_http(response_doh)
|
|
80
|
-
|
|
88
|
+
|
|
89
|
+
exchange.response.message.answer += response.message.answer
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import dns
|
|
2
|
-
import httpx
|
|
3
1
|
from dataclasses import dataclass
|
|
4
|
-
from dns.message import Message
|
|
5
2
|
from functools import cached_property
|
|
6
3
|
from typing import Self, Optional
|
|
7
4
|
|
|
5
|
+
import dns
|
|
6
|
+
import httpx
|
|
7
|
+
from dns.message import Message, QueryMessage
|
|
8
|
+
from dns.rrset import RRset
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
@dataclass
|
|
10
12
|
class BaseDNSData:
|
|
@@ -33,14 +35,17 @@ class Response(BaseDNSData):
|
|
|
33
35
|
"""
|
|
34
36
|
|
|
35
37
|
http: Optional[httpx.Response] = None
|
|
38
|
+
is_complete: bool = False
|
|
36
39
|
|
|
37
40
|
@classmethod
|
|
38
41
|
def from_http(cls, response: httpx.Response) -> Self:
|
|
39
42
|
self = cls(response.content, http=response)
|
|
40
43
|
return self
|
|
41
44
|
|
|
42
|
-
@
|
|
43
|
-
def answer(self):
|
|
45
|
+
@property
|
|
46
|
+
def answer(self) -> Optional[RRset]:
|
|
47
|
+
if not self.message.answer:
|
|
48
|
+
return None
|
|
44
49
|
return self.message.answer[-1]
|
|
45
50
|
|
|
46
51
|
|
|
@@ -54,7 +59,7 @@ class Request(BaseDNSData):
|
|
|
54
59
|
wire: bytes
|
|
55
60
|
|
|
56
61
|
@cached_property
|
|
57
|
-
def question(self):
|
|
62
|
+
def question(self) -> RRset:
|
|
58
63
|
return self.message.question[0]
|
|
59
64
|
|
|
60
65
|
@cached_property
|
|
@@ -77,10 +82,14 @@ class Request(BaseDNSData):
|
|
|
77
82
|
def name_text(self):
|
|
78
83
|
return self.name.to_text()
|
|
79
84
|
|
|
85
|
+
def get_response_template(self):
|
|
86
|
+
message = dns.message.make_response(self.message)
|
|
87
|
+
message.flags |= dns.flags.RA
|
|
88
|
+
return message
|
|
89
|
+
|
|
80
90
|
@cached_property
|
|
81
91
|
def blackhole(self) -> Response:
|
|
82
|
-
blackhole =
|
|
83
|
-
blackhole.flags |= dns.flags.RA
|
|
92
|
+
blackhole = self.get_response_template()
|
|
84
93
|
blackhole.set_rcode(dns.rcode.NXDOMAIN)
|
|
85
94
|
response = Response.from_message(blackhole)
|
|
86
95
|
return response
|
|
@@ -98,13 +107,53 @@ class Exchange:
|
|
|
98
107
|
|
|
99
108
|
request: Request
|
|
100
109
|
response: Optional[Response] = None
|
|
101
|
-
|
|
110
|
+
|
|
102
111
|
|
|
103
112
|
@classmethod
|
|
104
113
|
def from_wire(cls, wire: bytes, ip: str, port: int) -> Self:
|
|
105
114
|
request = Request(wire)
|
|
106
|
-
|
|
115
|
+
response = Response.from_message(request.get_response_template())
|
|
116
|
+
|
|
117
|
+
return cls(request=request, response=response, ip=ip, port=port)
|
|
107
118
|
|
|
108
119
|
@cached_property
|
|
109
120
|
def client(self):
|
|
110
121
|
return f'{self.ip}:{self.port}'
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def question_last(self) -> RRset:
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
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.
|
|
128
|
+
|
|
129
|
+
"""
|
|
130
|
+
if self.response.answer:
|
|
131
|
+
rrset = self.response.answer
|
|
132
|
+
ty = self.request.type
|
|
133
|
+
ttl = self.request.question.ttl
|
|
134
|
+
rdclass = self.request.question.rdclass
|
|
135
|
+
name = next(iter(rrset.items.keys())).to_text()
|
|
136
|
+
|
|
137
|
+
rrset_contrived = dns.rrset.from_text(
|
|
138
|
+
name=name,
|
|
139
|
+
ttl=ttl,
|
|
140
|
+
rdtype=ty,
|
|
141
|
+
rdclass=rdclass,
|
|
142
|
+
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return rrset_contrived
|
|
146
|
+
else:
|
|
147
|
+
return self.request.question # Solves the issue of digging out the name.
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def query_last(self) -> QueryMessage:
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
Create a query (e.g. for use by upstream) based on the last question.
|
|
154
|
+
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
question_last = self.question_last
|
|
158
|
+
query = dns.message.make_query(qname=question_last.name, rdclass=question_last.rdclass, rdtype=question_last.rdtype)
|
|
159
|
+
return query
|
fmtr_tools-1.3.1/fmtr/tools/dns_tools/server.py → fmtr_tools-1.3.2/fmtr/tools/dns_tools/proxy.py
RENAMED
|
@@ -1,59 +1,34 @@
|
|
|
1
|
-
import socket
|
|
2
1
|
from dataclasses import dataclass
|
|
3
2
|
|
|
4
3
|
from fmtr.tools import logger
|
|
5
|
-
from fmtr.tools.dns_tools
|
|
4
|
+
from fmtr.tools.dns_tools import server, client
|
|
6
5
|
from fmtr.tools.dns_tools.dm import Exchange
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
@dataclass
|
|
10
|
-
class
|
|
9
|
+
class Proxy(server.Plain):
|
|
11
10
|
"""
|
|
12
11
|
|
|
13
|
-
Base for
|
|
12
|
+
Base for a DNS Proxy server (plain server) TODO: Allow subclassing of any server type.
|
|
14
13
|
|
|
15
14
|
"""
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
port: int
|
|
16
|
+
client: client.HTTP
|
|
19
17
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
23
|
-
|
|
24
|
-
def resolve(self, exchange: Exchange):
|
|
25
|
-
raise NotImplemented
|
|
26
|
-
|
|
27
|
-
def start(self):
|
|
18
|
+
def process_question(self, exchange: Exchange):
|
|
28
19
|
"""
|
|
29
20
|
|
|
30
|
-
|
|
21
|
+
Modify exchange based on initial question.
|
|
31
22
|
|
|
32
23
|
"""
|
|
33
|
-
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
34
|
-
sock.bind((self.host, self.port))
|
|
35
|
-
print(f"Listening on {self.host}:{self.port}")
|
|
36
|
-
while True:
|
|
37
|
-
data, (ip, port) = sock.recvfrom(512)
|
|
38
|
-
exchange = Exchange.from_wire(data, ip=ip, port=port)
|
|
39
|
-
self.resolve(exchange)
|
|
40
|
-
sock.sendto(exchange.response.wire, (ip, port))
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@dataclass
|
|
44
|
-
class ServerBaseDoHProxy(ServerBasePlain):
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
Base for a DNS Proxy server
|
|
48
|
-
|
|
49
|
-
"""
|
|
50
|
-
|
|
51
|
-
client: ClientDoH
|
|
52
|
-
|
|
53
|
-
def process_question(self, exchange: Exchange):
|
|
54
24
|
return
|
|
55
25
|
|
|
56
26
|
def process_upstream(self, exchange: Exchange):
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
Modify exchange after upstream response.
|
|
30
|
+
|
|
31
|
+
"""
|
|
57
32
|
return
|
|
58
33
|
|
|
59
34
|
def resolve(self, exchange: Exchange):
|
|
@@ -73,17 +48,21 @@ class ServerBaseDoHProxy(ServerBasePlain):
|
|
|
73
48
|
|
|
74
49
|
with logger.span(f'Processing question...'):
|
|
75
50
|
self.process_question(exchange)
|
|
76
|
-
if exchange.response:
|
|
51
|
+
if exchange.response.is_complete:
|
|
77
52
|
return
|
|
78
53
|
|
|
79
54
|
with logger.span(f'Making upstream request for {request.name_text}...'):
|
|
80
55
|
self.client.resolve(exchange)
|
|
56
|
+
if exchange.response.is_complete:
|
|
57
|
+
return
|
|
81
58
|
|
|
82
59
|
with logger.span(f'Processing upstream response...'):
|
|
83
60
|
self.process_upstream(exchange)
|
|
61
|
+
if exchange.response.is_complete:
|
|
62
|
+
return
|
|
84
63
|
|
|
85
64
|
if exchange.response:
|
|
86
65
|
return
|
|
87
66
|
|
|
88
|
-
exchange.response =
|
|
67
|
+
exchange.response.is_complete = True
|
|
89
68
|
return
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
from fmtr.tools.dns_tools.dm import Exchange
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Plain:
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
Base for starting a plain DNS server
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
host: str
|
|
16
|
+
port: int
|
|
17
|
+
|
|
18
|
+
def __post_init__(self):
|
|
19
|
+
|
|
20
|
+
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
21
|
+
|
|
22
|
+
def resolve(self, exchange: Exchange):
|
|
23
|
+
raise NotImplemented
|
|
24
|
+
|
|
25
|
+
def start(self):
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
Listen and resolve via overridden resolve method.
|
|
29
|
+
|
|
30
|
+
"""
|
|
31
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
32
|
+
sock.bind((self.host, self.port))
|
|
33
|
+
print(f"Listening on {self.host}:{self.port}")
|
|
34
|
+
while True:
|
|
35
|
+
data, (ip, port) = sock.recvfrom(512)
|
|
36
|
+
exchange = Exchange.from_wire(data, ip=ip, port=port)
|
|
37
|
+
self.resolve(exchange)
|
|
38
|
+
sock.sendto(exchange.response.message.to_wire(), (ip, port))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.3.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fmtr.tools
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
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
|
|
@@ -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:
|
|
134
|
-
Requires-Dist:
|
|
135
|
-
Requires-Dist:
|
|
136
|
-
Requires-Dist:
|
|
137
|
-
Requires-Dist:
|
|
133
|
+
Requires-Dist: uvicorn[standard]; extra == "all"
|
|
134
|
+
Requires-Dist: openpyxl; extra == "all"
|
|
135
|
+
Requires-Dist: sentence_transformers; extra == "all"
|
|
136
|
+
Requires-Dist: peft; extra == "all"
|
|
137
|
+
Requires-Dist: httpx_retries; extra == "all"
|
|
138
138
|
Requires-Dist: sre_yield; extra == "all"
|
|
139
|
-
Requires-Dist:
|
|
139
|
+
Requires-Dist: faker; extra == "all"
|
|
140
140
|
Requires-Dist: ollama; extra == "all"
|
|
141
|
-
Requires-Dist:
|
|
142
|
-
Requires-Dist:
|
|
143
|
-
Requires-Dist: google-auth-httplib2; extra == "all"
|
|
144
|
-
Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
|
|
145
|
-
Requires-Dist: pydevd-pycharm; extra == "all"
|
|
146
|
-
Requires-Dist: torchvision; extra == "all"
|
|
147
|
-
Requires-Dist: pyyaml; extra == "all"
|
|
148
|
-
Requires-Dist: tabulate; extra == "all"
|
|
149
|
-
Requires-Dist: pymupdf; extra == "all"
|
|
150
|
-
Requires-Dist: docker; extra == "all"
|
|
151
|
-
Requires-Dist: openai; extra == "all"
|
|
152
|
-
Requires-Dist: sentence_transformers; extra == "all"
|
|
153
|
-
Requires-Dist: dnspython[doh]; extra == "all"
|
|
154
|
-
Requires-Dist: regex; extra == "all"
|
|
155
|
-
Requires-Dist: semver; extra == "all"
|
|
141
|
+
Requires-Dist: logfire; extra == "all"
|
|
142
|
+
Requires-Dist: bokeh; extra == "all"
|
|
156
143
|
Requires-Dist: setuptools; extra == "all"
|
|
157
|
-
Requires-Dist:
|
|
158
|
-
Requires-Dist:
|
|
159
|
-
Requires-Dist: google-api-python-client; extra == "all"
|
|
144
|
+
Requires-Dist: dask[bag]; extra == "all"
|
|
145
|
+
Requires-Dist: diskcache; extra == "all"
|
|
160
146
|
Requires-Dist: torchaudio; extra == "all"
|
|
161
|
-
Requires-Dist:
|
|
162
|
-
Requires-Dist:
|
|
163
|
-
Requires-Dist:
|
|
147
|
+
Requires-Dist: tokenizers; extra == "all"
|
|
148
|
+
Requires-Dist: pydantic; extra == "all"
|
|
149
|
+
Requires-Dist: pandas; extra == "all"
|
|
150
|
+
Requires-Dist: flet-video; extra == "all"
|
|
151
|
+
Requires-Dist: Unidecode; extra == "all"
|
|
152
|
+
Requires-Dist: pydantic-settings; extra == "all"
|
|
153
|
+
Requires-Dist: dnspython[doh]; extra == "all"
|
|
154
|
+
Requires-Dist: logfire[httpx]; extra == "all"
|
|
164
155
|
Requires-Dist: yamlscript; extra == "all"
|
|
156
|
+
Requires-Dist: fastapi; extra == "all"
|
|
157
|
+
Requires-Dist: openai; extra == "all"
|
|
158
|
+
Requires-Dist: deepmerge; extra == "all"
|
|
159
|
+
Requires-Dist: html2text; extra == "all"
|
|
160
|
+
Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
|
|
161
|
+
Requires-Dist: contexttimer; extra == "all"
|
|
162
|
+
Requires-Dist: flet[all]; extra == "all"
|
|
163
|
+
Requires-Dist: logfire[fastapi]; extra == "all"
|
|
164
|
+
Requires-Dist: regex; extra == "all"
|
|
165
|
+
Requires-Dist: docker; extra == "all"
|
|
165
166
|
Requires-Dist: filetype; extra == "all"
|
|
166
|
-
Requires-Dist: pydantic-settings; extra == "all"
|
|
167
|
-
Requires-Dist: flet-webview; extra == "all"
|
|
168
167
|
Requires-Dist: google-auth-oauthlib; extra == "all"
|
|
168
|
+
Requires-Dist: tabulate; extra == "all"
|
|
169
169
|
Requires-Dist: pytest-cov; extra == "all"
|
|
170
|
-
Requires-Dist:
|
|
171
|
-
Requires-Dist: logfire[fastapi]; extra == "all"
|
|
172
|
-
Requires-Dist: pandas; extra == "all"
|
|
173
|
-
Requires-Dist: html2text; extra == "all"
|
|
174
|
-
Requires-Dist: dask[bag]; extra == "all"
|
|
175
|
-
Requires-Dist: uvicorn[standard]; extra == "all"
|
|
176
|
-
Requires-Dist: json_repair; extra == "all"
|
|
177
|
-
Requires-Dist: flet-video; extra == "all"
|
|
178
|
-
Requires-Dist: httpx; extra == "all"
|
|
179
|
-
Requires-Dist: faker; extra == "all"
|
|
180
|
-
Requires-Dist: peft; extra == "all"
|
|
181
|
-
Requires-Dist: tokenizers; extra == "all"
|
|
170
|
+
Requires-Dist: pyyaml; extra == "all"
|
|
182
171
|
Requires-Dist: transformers[sentencepiece]; extra == "all"
|
|
183
|
-
Requires-Dist:
|
|
184
|
-
Requires-Dist:
|
|
172
|
+
Requires-Dist: pymupdf; extra == "all"
|
|
173
|
+
Requires-Dist: pydevd-pycharm; extra == "all"
|
|
174
|
+
Requires-Dist: torchvision; extra == "all"
|
|
175
|
+
Requires-Dist: google-auth; extra == "all"
|
|
176
|
+
Requires-Dist: flet-webview; extra == "all"
|
|
185
177
|
Requires-Dist: huggingface_hub; extra == "all"
|
|
186
|
-
Requires-Dist:
|
|
178
|
+
Requires-Dist: httpx; extra == "all"
|
|
179
|
+
Requires-Dist: semver; extra == "all"
|
|
180
|
+
Requires-Dist: json_repair; extra == "all"
|
|
181
|
+
Requires-Dist: tinynetrc; extra == "all"
|
|
182
|
+
Requires-Dist: google-auth-httplib2; extra == "all"
|
|
183
|
+
Requires-Dist: appdirs; extra == "all"
|
|
184
|
+
Requires-Dist: pymupdf4llm; extra == "all"
|
|
185
|
+
Requires-Dist: distributed; extra == "all"
|
|
186
|
+
Requires-Dist: google-api-python-client; extra == "all"
|
|
187
187
|
Dynamic: author
|
|
188
188
|
Dynamic: author-email
|
|
189
189
|
Dynamic: description
|
|
@@ -57,6 +57,7 @@ setup.py
|
|
|
57
57
|
./fmtr/tools/dns_tools/__init__.py
|
|
58
58
|
./fmtr/tools/dns_tools/client.py
|
|
59
59
|
./fmtr/tools/dns_tools/dm.py
|
|
60
|
+
./fmtr/tools/dns_tools/proxy.py
|
|
60
61
|
./fmtr/tools/dns_tools/server.py
|
|
61
62
|
./fmtr/tools/entrypoints/__init__.py
|
|
62
63
|
./fmtr/tools/entrypoints/cache_hfh.py
|
|
@@ -138,6 +139,7 @@ fmtr/tools/ai_tools/inference_tools.py
|
|
|
138
139
|
fmtr/tools/dns_tools/__init__.py
|
|
139
140
|
fmtr/tools/dns_tools/client.py
|
|
140
141
|
fmtr/tools/dns_tools/dm.py
|
|
142
|
+
fmtr/tools/dns_tools/proxy.py
|
|
141
143
|
fmtr/tools/dns_tools/server.py
|
|
142
144
|
fmtr/tools/entrypoints/__init__.py
|
|
143
145
|
fmtr/tools/entrypoints/cache_hfh.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.3.1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -15,60 +15,60 @@ pydantic-ai[logfire,openai]
|
|
|
15
15
|
ollama
|
|
16
16
|
|
|
17
17
|
[all]
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
uvicorn[standard]
|
|
19
|
+
openpyxl
|
|
20
|
+
sentence_transformers
|
|
21
|
+
peft
|
|
22
|
+
httpx_retries
|
|
23
23
|
sre_yield
|
|
24
|
-
|
|
24
|
+
faker
|
|
25
25
|
ollama
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
google-auth-httplib2
|
|
29
|
-
pydantic-ai[logfire,openai]
|
|
30
|
-
pydevd-pycharm
|
|
31
|
-
torchvision
|
|
32
|
-
pyyaml
|
|
33
|
-
tabulate
|
|
34
|
-
pymupdf
|
|
35
|
-
docker
|
|
36
|
-
openai
|
|
37
|
-
sentence_transformers
|
|
38
|
-
dnspython[doh]
|
|
39
|
-
regex
|
|
40
|
-
semver
|
|
26
|
+
logfire
|
|
27
|
+
bokeh
|
|
41
28
|
setuptools
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
google-api-python-client
|
|
29
|
+
dask[bag]
|
|
30
|
+
diskcache
|
|
45
31
|
torchaudio
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
32
|
+
tokenizers
|
|
33
|
+
pydantic
|
|
34
|
+
pandas
|
|
35
|
+
flet-video
|
|
36
|
+
Unidecode
|
|
37
|
+
pydantic-settings
|
|
38
|
+
dnspython[doh]
|
|
39
|
+
logfire[httpx]
|
|
49
40
|
yamlscript
|
|
41
|
+
fastapi
|
|
42
|
+
openai
|
|
43
|
+
deepmerge
|
|
44
|
+
html2text
|
|
45
|
+
pydantic-ai[logfire,openai]
|
|
46
|
+
contexttimer
|
|
47
|
+
flet[all]
|
|
48
|
+
logfire[fastapi]
|
|
49
|
+
regex
|
|
50
|
+
docker
|
|
50
51
|
filetype
|
|
51
|
-
pydantic-settings
|
|
52
|
-
flet-webview
|
|
53
52
|
google-auth-oauthlib
|
|
53
|
+
tabulate
|
|
54
54
|
pytest-cov
|
|
55
|
-
|
|
56
|
-
logfire[fastapi]
|
|
57
|
-
pandas
|
|
58
|
-
html2text
|
|
59
|
-
dask[bag]
|
|
60
|
-
uvicorn[standard]
|
|
61
|
-
json_repair
|
|
62
|
-
flet-video
|
|
63
|
-
httpx
|
|
64
|
-
faker
|
|
65
|
-
peft
|
|
66
|
-
tokenizers
|
|
55
|
+
pyyaml
|
|
67
56
|
transformers[sentencepiece]
|
|
68
|
-
|
|
69
|
-
|
|
57
|
+
pymupdf
|
|
58
|
+
pydevd-pycharm
|
|
59
|
+
torchvision
|
|
60
|
+
google-auth
|
|
61
|
+
flet-webview
|
|
70
62
|
huggingface_hub
|
|
71
|
-
|
|
63
|
+
httpx
|
|
64
|
+
semver
|
|
65
|
+
json_repair
|
|
66
|
+
tinynetrc
|
|
67
|
+
google-auth-httplib2
|
|
68
|
+
appdirs
|
|
69
|
+
pymupdf4llm
|
|
70
|
+
distributed
|
|
71
|
+
google-api-python-client
|
|
72
72
|
|
|
73
73
|
[api]
|
|
74
74
|
fastapi
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|