fmtr.tools 1.2.2__py3-none-any.whl → 1.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fmtr.tools might be problematic. Click here for more details.
- fmtr/tools/dns_tools.py +194 -30
- fmtr/tools/pattern_tools.py +6 -3
- fmtr/tools/version +1 -1
- {fmtr_tools-1.2.2.dist-info → fmtr_tools-1.2.4.dist-info}/METADATA +50 -45
- {fmtr_tools-1.2.2.dist-info → fmtr_tools-1.2.4.dist-info}/RECORD +9 -9
- {fmtr_tools-1.2.2.dist-info → fmtr_tools-1.2.4.dist-info}/WHEEL +0 -0
- {fmtr_tools-1.2.2.dist-info → fmtr_tools-1.2.4.dist-info}/entry_points.txt +0 -0
- {fmtr_tools-1.2.2.dist-info → fmtr_tools-1.2.4.dist-info}/licenses/LICENSE +0 -0
- {fmtr_tools-1.2.2.dist-info → fmtr_tools-1.2.4.dist-info}/top_level.txt +0 -0
fmtr/tools/dns_tools.py
CHANGED
|
@@ -1,57 +1,221 @@
|
|
|
1
1
|
import dns
|
|
2
2
|
import httpx
|
|
3
3
|
import socket
|
|
4
|
-
from
|
|
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
|
|
5
9
|
|
|
10
|
+
from fmtr.tools import Client, logger
|
|
6
11
|
|
|
7
|
-
|
|
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
|
+
"""
|
|
8
139
|
|
|
9
140
|
def __init__(self, host, port):
|
|
10
141
|
self.host = host
|
|
11
142
|
self.port = port
|
|
12
143
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
13
144
|
|
|
14
|
-
def resolve(self,
|
|
145
|
+
def resolve(self, exchange: Exchange):
|
|
15
146
|
raise NotImplemented
|
|
16
147
|
|
|
17
148
|
def start(self):
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
Listen and resolve via overridden resolve method.
|
|
152
|
+
|
|
153
|
+
"""
|
|
18
154
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
19
155
|
sock.bind((self.host, self.port))
|
|
20
156
|
print(f"Listening on {self.host}:{self.port}")
|
|
21
157
|
while True:
|
|
22
|
-
data,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
sock.sendto(
|
|
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.
|
|
26
196
|
|
|
197
|
+
"""
|
|
27
198
|
|
|
28
|
-
|
|
29
|
-
url = "https://cloudflare-dns.com/dns-query"
|
|
30
|
-
headers = {"accept": "application/dns-json"}
|
|
199
|
+
request = exchange.request
|
|
31
200
|
|
|
32
|
-
|
|
33
|
-
qname = question.question[0].name.to_text()
|
|
34
|
-
qtype = dns.rdatatype.to_text(question.question[0].rdtype)
|
|
35
|
-
params = {"name": qname, "type": qtype}
|
|
201
|
+
with logger.span(f'Handling request for {request.name_text} from {exchange.client}...'):
|
|
36
202
|
|
|
37
|
-
|
|
38
|
-
|
|
203
|
+
if not request.is_valid:
|
|
204
|
+
raise ValueError(f'Only one question per request is supported. Got {len(request.question)} questions.')
|
|
39
205
|
|
|
40
|
-
|
|
41
|
-
|
|
206
|
+
with logger.span(f'Processing question...'):
|
|
207
|
+
self.process_question(exchange)
|
|
208
|
+
if exchange.response:
|
|
209
|
+
return
|
|
42
210
|
|
|
43
|
-
|
|
44
|
-
|
|
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)
|
|
211
|
+
with logger.span(f'Making upstream request for {request.name_text}...'):
|
|
212
|
+
self.from_upstream(exchange)
|
|
51
213
|
|
|
52
|
-
|
|
214
|
+
with logger.span(f'Processing upstream response...'):
|
|
215
|
+
self.process_upstream(exchange)
|
|
53
216
|
|
|
217
|
+
if exchange.response:
|
|
218
|
+
return
|
|
54
219
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
proxy.start()
|
|
220
|
+
exchange.response = exchange.response_upstream
|
|
221
|
+
return
|
fmtr/tools/pattern_tools.py
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import regex as re
|
|
1
2
|
from dataclasses import dataclass
|
|
2
3
|
from functools import cached_property
|
|
3
4
|
from typing import List
|
|
4
5
|
|
|
5
|
-
import regex as re
|
|
6
|
-
|
|
7
6
|
from fmtr.tools.logging_tools import logger
|
|
8
7
|
|
|
9
8
|
|
|
@@ -158,7 +157,11 @@ class Rewriter:
|
|
|
158
157
|
|
|
159
158
|
previous = new
|
|
160
159
|
|
|
161
|
-
|
|
160
|
+
if len(history) == 1:
|
|
161
|
+
history_str = 'No rewrites performed.'
|
|
162
|
+
else:
|
|
163
|
+
history_str = get_history_str()
|
|
164
|
+
logger.debug(f'Finished rewriting: {history_str}')
|
|
162
165
|
|
|
163
166
|
return previous
|
|
164
167
|
|
fmtr/tools/version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.2.
|
|
1
|
+
1.2.4
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fmtr.tools
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.4
|
|
4
4
|
Summary: Collection of high-level tools to simplify everyday development tasks, with a focus on AI/ML
|
|
5
5
|
Home-page: https://github.com/fmtr/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: httpx; extra == "all"
|
|
127
|
-
Requires-Dist: html2text; extra == "all"
|
|
128
|
-
Requires-Dist: ollama; extra == "all"
|
|
129
|
-
Requires-Dist: google-auth-httplib2; extra == "all"
|
|
130
|
-
Requires-Dist: deepmerge; extra == "all"
|
|
131
|
-
Requires-Dist: contexttimer; extra == "all"
|
|
132
|
-
Requires-Dist: sre_yield; extra == "all"
|
|
133
|
-
Requires-Dist: google-auth; extra == "all"
|
|
134
|
-
Requires-Dist: huggingface_hub; extra == "all"
|
|
135
|
-
Requires-Dist: setuptools; extra == "all"
|
|
136
|
-
Requires-Dist: pyyaml; extra == "all"
|
|
137
131
|
Requires-Dist: dask[bag]; extra == "all"
|
|
138
|
-
Requires-Dist:
|
|
139
|
-
Requires-Dist: pymupdf4llm; extra == "all"
|
|
140
|
-
Requires-Dist: logfire[fastapi]; extra == "all"
|
|
141
|
-
Requires-Dist: tinynetrc; extra == "all"
|
|
142
|
-
Requires-Dist: Unidecode; extra == "all"
|
|
132
|
+
Requires-Dist: huggingface_hub; extra == "all"
|
|
143
133
|
Requires-Dist: tabulate; extra == "all"
|
|
144
|
-
Requires-Dist:
|
|
145
|
-
Requires-Dist: fastapi; extra == "all"
|
|
146
|
-
Requires-Dist: appdirs; extra == "all"
|
|
147
|
-
Requires-Dist: flet-webview; extra == "all"
|
|
148
|
-
Requires-Dist: regex; extra == "all"
|
|
134
|
+
Requires-Dist: sre_yield; extra == "all"
|
|
149
135
|
Requires-Dist: semver; extra == "all"
|
|
150
|
-
Requires-Dist: transformers[sentencepiece]; extra == "all"
|
|
151
|
-
Requires-Dist: google-api-python-client; extra == "all"
|
|
152
|
-
Requires-Dist: flet-video; extra == "all"
|
|
153
136
|
Requires-Dist: distributed; extra == "all"
|
|
154
|
-
Requires-Dist:
|
|
155
|
-
Requires-Dist: peft; extra == "all"
|
|
156
|
-
Requires-Dist: pydantic-settings; extra == "all"
|
|
157
|
-
Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
|
|
158
|
-
Requires-Dist: httpx_retries; extra == "all"
|
|
159
|
-
Requires-Dist: pydevd-pycharm; extra == "all"
|
|
160
|
-
Requires-Dist: sentence_transformers; extra == "all"
|
|
161
|
-
Requires-Dist: faker; extra == "all"
|
|
162
|
-
Requires-Dist: diskcache; extra == "all"
|
|
163
|
-
Requires-Dist: tokenizers; extra == "all"
|
|
164
|
-
Requires-Dist: pytest-cov; extra == "all"
|
|
137
|
+
Requires-Dist: docker; extra == "all"
|
|
165
138
|
Requires-Dist: filetype; extra == "all"
|
|
166
|
-
Requires-Dist:
|
|
139
|
+
Requires-Dist: pydevd-pycharm; extra == "all"
|
|
140
|
+
Requires-Dist: uvicorn[standard]; extra == "all"
|
|
141
|
+
Requires-Dist: httpx; extra == "all"
|
|
142
|
+
Requires-Dist: yamlscript; extra == "all"
|
|
167
143
|
Requires-Dist: logfire; extra == "all"
|
|
168
|
-
Requires-Dist:
|
|
144
|
+
Requires-Dist: pydantic-settings; extra == "all"
|
|
169
145
|
Requires-Dist: logfire[httpx]; extra == "all"
|
|
170
|
-
Requires-Dist:
|
|
171
|
-
Requires-Dist:
|
|
146
|
+
Requires-Dist: tinynetrc; extra == "all"
|
|
147
|
+
Requires-Dist: Unidecode; extra == "all"
|
|
148
|
+
Requires-Dist: openai; extra == "all"
|
|
149
|
+
Requires-Dist: contexttimer; extra == "all"
|
|
150
|
+
Requires-Dist: appdirs; extra == "all"
|
|
151
|
+
Requires-Dist: sentence_transformers; extra == "all"
|
|
172
152
|
Requires-Dist: pandas; extra == "all"
|
|
173
|
-
Requires-Dist:
|
|
174
|
-
Requires-Dist:
|
|
175
|
-
Requires-Dist:
|
|
153
|
+
Requires-Dist: ollama; extra == "all"
|
|
154
|
+
Requires-Dist: google-api-python-client; extra == "all"
|
|
155
|
+
Requires-Dist: google-auth; extra == "all"
|
|
156
|
+
Requires-Dist: flet-video; extra == "all"
|
|
157
|
+
Requires-Dist: tokenizers; extra == "all"
|
|
158
|
+
Requires-Dist: pyyaml; extra == "all"
|
|
176
159
|
Requires-Dist: torchvision; extra == "all"
|
|
177
|
-
Requires-Dist:
|
|
160
|
+
Requires-Dist: json_repair; extra == "all"
|
|
161
|
+
Requires-Dist: transformers[sentencepiece]; extra == "all"
|
|
162
|
+
Requires-Dist: setuptools; extra == "all"
|
|
178
163
|
Requires-Dist: bokeh; extra == "all"
|
|
179
|
-
Requires-Dist:
|
|
164
|
+
Requires-Dist: torchaudio; extra == "all"
|
|
165
|
+
Requires-Dist: regex; extra == "all"
|
|
166
|
+
Requires-Dist: openpyxl; extra == "all"
|
|
167
|
+
Requires-Dist: logfire[fastapi]; extra == "all"
|
|
168
|
+
Requires-Dist: faker; extra == "all"
|
|
169
|
+
Requires-Dist: google-auth-oauthlib; extra == "all"
|
|
170
|
+
Requires-Dist: diskcache; extra == "all"
|
|
171
|
+
Requires-Dist: httpx_retries; extra == "all"
|
|
172
|
+
Requires-Dist: pydantic; extra == "all"
|
|
173
|
+
Requires-Dist: google-auth-httplib2; extra == "all"
|
|
174
|
+
Requires-Dist: html2text; extra == "all"
|
|
175
|
+
Requires-Dist: flet-webview; extra == "all"
|
|
176
|
+
Requires-Dist: flet[all]; extra == "all"
|
|
177
|
+
Requires-Dist: peft; extra == "all"
|
|
178
|
+
Requires-Dist: deepmerge; extra == "all"
|
|
179
|
+
Requires-Dist: fastapi; extra == "all"
|
|
180
|
+
Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
|
|
181
|
+
Requires-Dist: pymupdf; extra == "all"
|
|
182
|
+
Requires-Dist: pymupdf4llm; extra == "all"
|
|
183
|
+
Requires-Dist: dnspython[doh]; extra == "all"
|
|
184
|
+
Requires-Dist: pytest-cov; extra == "all"
|
|
180
185
|
Dynamic: author
|
|
181
186
|
Dynamic: author-email
|
|
182
187
|
Dynamic: description
|
|
@@ -8,7 +8,7 @@ fmtr/tools/data_modelling_tools.py,sha256=0BFm-F_cYzVTxftWQwORkPd0FM2BTLVh9-s0-r
|
|
|
8
8
|
fmtr/tools/dataclass_tools.py,sha256=0Gt6KeLhtPgubo_2tYkIVqB8oQ91Qzag8OAGZDdjvMU,1209
|
|
9
9
|
fmtr/tools/datatype_tools.py,sha256=3P4AWIFGkJ-UqvXlj0Jc9IvkIIgTOE9jRrOk3NVbpH8,1508
|
|
10
10
|
fmtr/tools/debugging_tools.py,sha256=_xzqS0V5BpL8d06j-jVQjGgI7T020QsqVXKAKMz7Du8,2082
|
|
11
|
-
fmtr/tools/dns_tools.py,sha256=
|
|
11
|
+
fmtr/tools/dns_tools.py,sha256=7OoELFd9lZ3ULdJ6h8QwHYFNUYS9atqYrflKEzzFW-g,5461
|
|
12
12
|
fmtr/tools/docker_tools.py,sha256=rdaZje2xhlmnfQqZnR7IHgRdWncTLjrJcViUTt5oEwk,617
|
|
13
13
|
fmtr/tools/environment_tools.py,sha256=ZO2e8pTzbQ8jNXnmKpaZF7_3qkVM6U5iqku5YSwOszE,1849
|
|
14
14
|
fmtr/tools/function_tools.py,sha256=_oW3-HZXMst2pcU-I-U7KTMmzo0g9MIQKmX-c2_NEoE,858
|
|
@@ -31,7 +31,7 @@ fmtr/tools/netrc_tools.py,sha256=PpNpz_mWlQi6VHGromKwFfTyLpHUXsd4LY6-OKLCbeI,376
|
|
|
31
31
|
fmtr/tools/openai_tools.py,sha256=6SUgejgzUzmlKKct2_ePXntvMegu3FJgfk9x7aqtqYc,742
|
|
32
32
|
fmtr/tools/packaging_tools.py,sha256=FlgOTnDRHZWQL2iR-wucTsyGEHRE-MlddKL30MPmUqE,253
|
|
33
33
|
fmtr/tools/parallel_tools.py,sha256=QEb_gN1StkxsqYaH4HSjiJX8Y3gpb2uKNsOzG4uFpaM,3071
|
|
34
|
-
fmtr/tools/pattern_tools.py,sha256=
|
|
34
|
+
fmtr/tools/pattern_tools.py,sha256=T9f2wVi_0lPuj5npcxs0yBU91LAgWGHwtrdN6rUZKm8,4357
|
|
35
35
|
fmtr/tools/pdf_tools.py,sha256=xvv9B84uAF81rFJRnXhSsxYuP42vY9ZdPVFrSMVe8G8,4069
|
|
36
36
|
fmtr/tools/platform_tools.py,sha256=7p69CmAHe_sF68Fx9uVhns1k5EewTHTWgUYzkl6ZQKA,308
|
|
37
37
|
fmtr/tools/process_tools.py,sha256=Ysh5Dk2QFBhXQerArjKdt7xZd3JrN5Ho02AaOjH0Nnw,1425
|
|
@@ -45,7 +45,7 @@ fmtr/tools/tabular_tools.py,sha256=tpIpZzYku1HcJrHZJL6BC39LmN3WUWVhFbK2N7nDVmE,1
|
|
|
45
45
|
fmtr/tools/tokenization_tools.py,sha256=me-IBzSLyNYejLybwjO9CNB6Mj2NYfKPaOVThXyaGNg,4268
|
|
46
46
|
fmtr/tools/tools.py,sha256=CAsApa1YwVdNE6H66Vjivs_mXYvOas3rh7fPELAnTpk,795
|
|
47
47
|
fmtr/tools/unicode_tools.py,sha256=yS_9wpu8ogNoiIL7s1G_8bETFFO_YQlo4LNPv1NLDeY,52
|
|
48
|
-
fmtr/tools/version,sha256=
|
|
48
|
+
fmtr/tools/version,sha256=MPP62sP52LbIqBiKDnsMNqvJuQiGrD-pRlTilhxP92U,5
|
|
49
49
|
fmtr/tools/version_tools.py,sha256=yNs_CGqWpqE4jbK9wsPIi14peJVXYbhIcMqHAFOw3yE,1480
|
|
50
50
|
fmtr/tools/yaml_tools.py,sha256=9kuYChqJelWQIjGlSnK4iDdOWWH06P0gp9jIcRrC3UI,1903
|
|
51
51
|
fmtr/tools/ai_tools/__init__.py,sha256=JZrLuOFNV1A3wvJgonxOgz_4WS-7MfCuowGWA5uYCjs,372
|
|
@@ -70,9 +70,9 @@ fmtr/tools/tests/test_environment.py,sha256=iHaiMQfECYZPkPKwfuIZV9uHuWe3aE-p_dN_
|
|
|
70
70
|
fmtr/tools/tests/test_json.py,sha256=IeSP4ziPvRcmS8kq7k9tHonC9rN5YYq9GSNT2ul6Msk,287
|
|
71
71
|
fmtr/tools/tests/test_path.py,sha256=AkZQa6_8BQ-VaCyL_J-iKmdf2ZaM-xFYR37Kun3k4_g,2188
|
|
72
72
|
fmtr/tools/tests/test_yaml.py,sha256=jc0TwwKu9eC0LvFGNMERdgBue591xwLxYXFbtsRwXVM,287
|
|
73
|
-
fmtr_tools-1.2.
|
|
74
|
-
fmtr_tools-1.2.
|
|
75
|
-
fmtr_tools-1.2.
|
|
76
|
-
fmtr_tools-1.2.
|
|
77
|
-
fmtr_tools-1.2.
|
|
78
|
-
fmtr_tools-1.2.
|
|
73
|
+
fmtr_tools-1.2.4.dist-info/licenses/LICENSE,sha256=FW9aa6vVN5IjRQWLT43hs4_koYSmpcbIovlKeAJ0_cI,10757
|
|
74
|
+
fmtr_tools-1.2.4.dist-info/METADATA,sha256=Xm5-3QRQLHzVWpsgq34hCEPasBOx1F-BfMfWqxApHIg,15943
|
|
75
|
+
fmtr_tools-1.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
76
|
+
fmtr_tools-1.2.4.dist-info/entry_points.txt,sha256=fSQrDGNctdQXbUxpMWYVfVQ0mhZMDyaEDG3D3a0zOSc,278
|
|
77
|
+
fmtr_tools-1.2.4.dist-info/top_level.txt,sha256=LXem9xCgNOD72tE2gRKESdiQTL902mfFkwWb6-dlwEE,5
|
|
78
|
+
fmtr_tools-1.2.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|