fmtr.tools 1.3.13__py3-none-any.whl → 1.3.15__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/client.py +6 -2
- fmtr/tools/dns_tools/dm.py +25 -10
- fmtr/tools/dns_tools/proxy.py +13 -4
- fmtr/tools/dns_tools/server.py +40 -14
- fmtr/tools/version +1 -1
- {fmtr_tools-1.3.13.dist-info → fmtr_tools-1.3.15.dist-info}/METADATA +41 -41
- {fmtr_tools-1.3.13.dist-info → fmtr_tools-1.3.15.dist-info}/RECORD +11 -11
- {fmtr_tools-1.3.13.dist-info → fmtr_tools-1.3.15.dist-info}/WHEEL +0 -0
- {fmtr_tools-1.3.13.dist-info → fmtr_tools-1.3.15.dist-info}/entry_points.txt +0 -0
- {fmtr_tools-1.3.13.dist-info → fmtr_tools-1.3.15.dist-info}/licenses/LICENSE +0 -0
- {fmtr_tools-1.3.13.dist-info → fmtr_tools-1.3.15.dist-info}/top_level.txt +0 -0
fmtr/tools/dns_tools/client.py
CHANGED
|
@@ -3,6 +3,7 @@ from dataclasses import dataclass
|
|
|
3
3
|
from dns import query
|
|
4
4
|
from functools import cached_property
|
|
5
5
|
from httpx_retries import Retry, RetryTransport
|
|
6
|
+
from typing import Optional
|
|
6
7
|
|
|
7
8
|
from fmtr.tools import http_tools as http
|
|
8
9
|
from fmtr.tools.dns_tools.dm import Exchange, Response
|
|
@@ -38,14 +39,17 @@ class Plain:
|
|
|
38
39
|
"""
|
|
39
40
|
host: str
|
|
40
41
|
port: int = 53
|
|
42
|
+
ttl_min: Optional[int] = None
|
|
41
43
|
|
|
42
44
|
def resolve(self, exchange: Exchange):
|
|
43
45
|
|
|
44
46
|
with logger.span(f'UDP {self.host}:{self.port}'):
|
|
45
47
|
response_plain = query.udp(q=exchange.query_last, where=self.host, port=self.port)
|
|
46
48
|
response = Response.from_message(response_plain)
|
|
49
|
+
for answer in response.message.answer:
|
|
50
|
+
answer.ttl = max(answer.ttl, self.ttl_min or answer.ttl)
|
|
47
51
|
|
|
48
|
-
exchange.response
|
|
52
|
+
exchange.response = response
|
|
49
53
|
|
|
50
54
|
|
|
51
55
|
@dataclass
|
|
@@ -86,7 +90,7 @@ class HTTP:
|
|
|
86
90
|
response_doh = self.CLIENT.post(url, headers=headers, content=exchange.query_last.to_wire())
|
|
87
91
|
response_doh.raise_for_status()
|
|
88
92
|
response = Response.from_http(response_doh)
|
|
89
|
-
exchange.response
|
|
93
|
+
exchange.response = response
|
|
90
94
|
|
|
91
95
|
except Exception as exception:
|
|
92
96
|
exchange.response.message.set_rcode(dnspython.rcode.SERVFAIL)
|
fmtr/tools/dns_tools/dm.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import dns
|
|
2
2
|
import httpx
|
|
3
|
-
from dataclasses import dataclass
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
4
|
from dns import rcode as dnsrcode
|
|
5
|
+
from dns import reversename
|
|
5
6
|
from dns.message import Message, QueryMessage
|
|
6
7
|
from dns.rrset import RRset
|
|
7
8
|
from functools import cached_property
|
|
8
|
-
from typing import Self, Optional
|
|
9
|
+
from typing import Self, Optional, List
|
|
9
10
|
|
|
10
11
|
from fmtr.tools.string_tools import join
|
|
11
12
|
|
|
@@ -36,7 +37,7 @@ class Response(BaseDNSData):
|
|
|
36
37
|
"""
|
|
37
38
|
|
|
38
39
|
http: Optional[httpx.Response] = None
|
|
39
|
-
|
|
40
|
+
blocked_by: Optional[str] = None
|
|
40
41
|
|
|
41
42
|
@classmethod
|
|
42
43
|
def from_http(cls, response: httpx.Response) -> Self:
|
|
@@ -142,14 +143,17 @@ class Exchange:
|
|
|
142
143
|
|
|
143
144
|
request: Request
|
|
144
145
|
response: Optional[Response] = None
|
|
146
|
+
answers_pre: List[RRset] = field(default_factory=list)
|
|
147
|
+
is_internal: bool = False
|
|
148
|
+
client_name: Optional[str] = None
|
|
149
|
+
is_complete: bool = False
|
|
145
150
|
|
|
146
151
|
|
|
147
152
|
@classmethod
|
|
148
|
-
def from_wire(cls, wire: bytes,
|
|
153
|
+
def from_wire(cls, wire: bytes, **kwargs) -> Self:
|
|
149
154
|
request = Request(wire)
|
|
150
155
|
response = Response.from_message(request.get_response_template())
|
|
151
|
-
|
|
152
|
-
return cls(request=request, response=response, ip=ip, port=port)
|
|
156
|
+
return cls(request=request, response=response, **kwargs)
|
|
153
157
|
|
|
154
158
|
@cached_property
|
|
155
159
|
def client(self):
|
|
@@ -162,8 +166,8 @@ class Exchange:
|
|
|
162
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.
|
|
163
167
|
|
|
164
168
|
"""
|
|
165
|
-
if self.
|
|
166
|
-
rrset = self.
|
|
169
|
+
if self.answers_pre:
|
|
170
|
+
rrset = self.answers_pre[-1]
|
|
167
171
|
ty = self.request.type
|
|
168
172
|
ttl = self.request.question.ttl
|
|
169
173
|
rdclass = self.request.question.rdclass
|
|
@@ -174,7 +178,6 @@ class Exchange:
|
|
|
174
178
|
ttl=ttl,
|
|
175
179
|
rdtype=ty,
|
|
176
180
|
rdclass=rdclass,
|
|
177
|
-
|
|
178
181
|
)
|
|
179
182
|
|
|
180
183
|
return rrset_contrived
|
|
@@ -190,7 +193,7 @@ class Exchange:
|
|
|
190
193
|
"""
|
|
191
194
|
|
|
192
195
|
question_last = self.question_last
|
|
193
|
-
query = dns.message.make_query(qname=question_last.name, rdclass=question_last.rdclass, rdtype=question_last.rdtype)
|
|
196
|
+
query = dns.message.make_query(qname=question_last.name, rdclass=question_last.rdclass, rdtype=question_last.rdtype, id=self.request.message.id)
|
|
194
197
|
return query
|
|
195
198
|
|
|
196
199
|
@property
|
|
@@ -202,3 +205,15 @@ class Exchange:
|
|
|
202
205
|
"""
|
|
203
206
|
data = tuple(self.request.question.to_text().split())
|
|
204
207
|
return data
|
|
208
|
+
|
|
209
|
+
@cached_property
|
|
210
|
+
def reverse(self) -> Self:
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
Create an Exchange for a reverse lookup of this Exchange's client IP.
|
|
214
|
+
|
|
215
|
+
"""
|
|
216
|
+
name = reversename.from_address(self.ip)
|
|
217
|
+
query = dns.message.make_query(name, dns.rdatatype.PTR)
|
|
218
|
+
exchange = self.__class__.from_wire(query.to_wire(), ip=self.ip, port=self.port, is_internal=True)
|
|
219
|
+
return exchange
|
fmtr/tools/dns_tools/proxy.py
CHANGED
|
@@ -31,6 +31,14 @@ class Proxy(server.Plain):
|
|
|
31
31
|
"""
|
|
32
32
|
return
|
|
33
33
|
|
|
34
|
+
def finalize(self, exchange: Exchange):
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
Finalize a still open exchange.
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
exchange.is_complete = True
|
|
41
|
+
|
|
34
42
|
def resolve(self, exchange: Exchange) -> Exchange:
|
|
35
43
|
"""
|
|
36
44
|
|
|
@@ -38,20 +46,21 @@ class Proxy(server.Plain):
|
|
|
38
46
|
Subclasses can override the relevant processing methods to implement custom behaviour.
|
|
39
47
|
|
|
40
48
|
"""
|
|
41
|
-
|
|
42
49
|
with logger.span(f'Processing question...'):
|
|
43
50
|
self.process_question(exchange)
|
|
44
|
-
if exchange.
|
|
51
|
+
if exchange.is_complete:
|
|
45
52
|
return exchange
|
|
46
53
|
|
|
47
54
|
with logger.span(f'Making upstream request...'):
|
|
48
55
|
self.client.resolve(exchange)
|
|
49
|
-
if exchange.
|
|
56
|
+
if exchange.is_complete:
|
|
50
57
|
return exchange
|
|
51
58
|
|
|
52
59
|
with logger.span(f'Processing upstream response...'):
|
|
53
60
|
self.process_upstream(exchange)
|
|
54
|
-
if exchange.
|
|
61
|
+
if exchange.is_complete:
|
|
55
62
|
return exchange
|
|
56
63
|
|
|
64
|
+
self.finalize(exchange)
|
|
65
|
+
|
|
57
66
|
return exchange
|
fmtr/tools/dns_tools/server.py
CHANGED
|
@@ -66,35 +66,61 @@ class Plain:
|
|
|
66
66
|
logger.info(f'Request found in cache.')
|
|
67
67
|
exchange.response = self.cache[exchange.key]
|
|
68
68
|
exchange.response.message.id = exchange.request.message.id
|
|
69
|
-
exchange.
|
|
69
|
+
exchange.is_complete = True
|
|
70
70
|
|
|
71
|
-
def
|
|
71
|
+
def get_span(self, exchange: Exchange):
|
|
72
72
|
"""
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
Get handling span
|
|
75
75
|
|
|
76
76
|
"""
|
|
77
77
|
request = exchange.request
|
|
78
|
+
span = logger.span(
|
|
79
|
+
f'Handling request {exchange.client_name=} {request.message.id=} {request.type_text} {request.name_text} {request.question=}...'
|
|
80
|
+
)
|
|
81
|
+
return span
|
|
78
82
|
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
def log_response(self, exchange: Exchange):
|
|
84
|
+
"""
|
|
81
85
|
|
|
82
|
-
|
|
83
|
-
|
|
86
|
+
Log when resolution complete
|
|
87
|
+
|
|
88
|
+
"""
|
|
89
|
+
request = exchange.request
|
|
90
|
+
response = exchange.response
|
|
91
|
+
|
|
92
|
+
logger.info(
|
|
93
|
+
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=}...'
|
|
84
94
|
)
|
|
85
|
-
with span:
|
|
86
95
|
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def handle(self, exchange: Exchange):
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
Check validity of request, reverse lookup client address, check presence in cache and resolve.
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
if not exchange.request.is_valid:
|
|
106
|
+
raise ValueError(f'Only one question per request is supported. Got {len(exchange.request.question)} questions.')
|
|
107
|
+
|
|
108
|
+
if not exchange.is_internal:
|
|
109
|
+
self.handle(exchange.reverse)
|
|
110
|
+
client_name = exchange.reverse.question_last.name.to_text()
|
|
111
|
+
if not exchange.reverse.response.answer:
|
|
112
|
+
logger.warning(f'Client name could not be resolved {client_name=}.')
|
|
113
|
+
exchange.client_name = client_name
|
|
114
|
+
|
|
115
|
+
with self.get_span(exchange):
|
|
87
116
|
with logger.span(f'Checking cache...'):
|
|
88
117
|
self.check_cache(exchange)
|
|
89
118
|
|
|
90
|
-
if not exchange.
|
|
119
|
+
if not exchange.is_complete:
|
|
91
120
|
exchange = self.resolve(exchange)
|
|
92
|
-
exchange.
|
|
121
|
+
exchange.is_complete = True
|
|
93
122
|
|
|
94
123
|
self.cache[exchange.key] = exchange.response
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
attribs = dict(rcode=exchange.response.rcode, rcode_text=exchange.response.rcode_text)
|
|
98
|
-
span.set_attributes(attribs)
|
|
124
|
+
self.log_response(exchange)
|
|
99
125
|
|
|
100
126
|
return exchange
|
fmtr/tools/version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.3.
|
|
1
|
+
1.3.15
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fmtr.tools
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.15
|
|
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: logfire; extra == "all"
|
|
130
|
-
Requires-Dist:
|
|
129
|
+
Requires-Dist: logfire[fastapi]; extra == "all"
|
|
130
|
+
Requires-Dist: json_repair; extra == "all"
|
|
131
|
+
Requires-Dist: flet-webview; extra == "all"
|
|
132
|
+
Requires-Dist: google-auth-oauthlib; extra == "all"
|
|
133
|
+
Requires-Dist: httpx_retries; extra == "all"
|
|
134
|
+
Requires-Dist: pandas; extra == "all"
|
|
135
|
+
Requires-Dist: google-auth; extra == "all"
|
|
136
|
+
Requires-Dist: yamlscript; extra == "all"
|
|
137
|
+
Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
|
|
131
138
|
Requires-Dist: pydantic; extra == "all"
|
|
132
|
-
Requires-Dist:
|
|
139
|
+
Requires-Dist: setuptools; extra == "all"
|
|
140
|
+
Requires-Dist: google-auth-httplib2; extra == "all"
|
|
141
|
+
Requires-Dist: diskcache; extra == "all"
|
|
142
|
+
Requires-Dist: pymupdf; extra == "all"
|
|
143
|
+
Requires-Dist: flet-video; extra == "all"
|
|
133
144
|
Requires-Dist: contexttimer; extra == "all"
|
|
134
|
-
Requires-Dist: Unidecode; extra == "all"
|
|
135
|
-
Requires-Dist: bokeh; extra == "all"
|
|
136
|
-
Requires-Dist: dask[bag]; extra == "all"
|
|
137
145
|
Requires-Dist: faker; extra == "all"
|
|
138
|
-
Requires-Dist: yamlscript; extra == "all"
|
|
139
146
|
Requires-Dist: pytest-cov; extra == "all"
|
|
140
|
-
Requires-Dist: logfire
|
|
147
|
+
Requires-Dist: logfire; extra == "all"
|
|
148
|
+
Requires-Dist: bokeh; extra == "all"
|
|
149
|
+
Requires-Dist: pydantic-settings; extra == "all"
|
|
141
150
|
Requires-Dist: logfire[httpx]; extra == "all"
|
|
142
|
-
Requires-Dist:
|
|
151
|
+
Requires-Dist: appdirs; extra == "all"
|
|
152
|
+
Requires-Dist: openpyxl; extra == "all"
|
|
143
153
|
Requires-Dist: pyyaml; extra == "all"
|
|
144
|
-
Requires-Dist:
|
|
145
|
-
Requires-Dist:
|
|
154
|
+
Requires-Dist: semver; extra == "all"
|
|
155
|
+
Requires-Dist: fastapi; extra == "all"
|
|
156
|
+
Requires-Dist: torchaudio; extra == "all"
|
|
157
|
+
Requires-Dist: ollama; extra == "all"
|
|
158
|
+
Requires-Dist: dask[bag]; extra == "all"
|
|
159
|
+
Requires-Dist: torchvision; extra == "all"
|
|
146
160
|
Requires-Dist: deepmerge; extra == "all"
|
|
161
|
+
Requires-Dist: tokenizers; extra == "all"
|
|
162
|
+
Requires-Dist: cachetools; extra == "all"
|
|
147
163
|
Requires-Dist: uvicorn[standard]; extra == "all"
|
|
148
|
-
Requires-Dist: pymupdf4llm; extra == "all"
|
|
149
|
-
Requires-Dist: filetype; extra == "all"
|
|
150
|
-
Requires-Dist: sre_yield; extra == "all"
|
|
151
|
-
Requires-Dist: transformers[sentencepiece]; extra == "all"
|
|
152
164
|
Requires-Dist: sentence_transformers; extra == "all"
|
|
153
|
-
Requires-Dist: fastapi; extra == "all"
|
|
154
|
-
Requires-Dist: flet[all]; extra == "all"
|
|
155
|
-
Requires-Dist: flet-webview; extra == "all"
|
|
156
|
-
Requires-Dist: setuptools; extra == "all"
|
|
157
165
|
Requires-Dist: openai; extra == "all"
|
|
158
|
-
Requires-Dist:
|
|
166
|
+
Requires-Dist: distributed; extra == "all"
|
|
167
|
+
Requires-Dist: filetype; extra == "all"
|
|
168
|
+
Requires-Dist: regex; extra == "all"
|
|
169
|
+
Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
|
|
159
170
|
Requires-Dist: docker; extra == "all"
|
|
160
|
-
Requires-Dist:
|
|
161
|
-
Requires-Dist: tabulate; extra == "all"
|
|
162
|
-
Requires-Dist: semver; extra == "all"
|
|
163
|
-
Requires-Dist: httpx_retries; extra == "all"
|
|
164
|
-
Requires-Dist: json_repair; extra == "all"
|
|
165
|
-
Requires-Dist: tokenizers; extra == "all"
|
|
166
|
-
Requires-Dist: pydantic-settings; extra == "all"
|
|
167
|
-
Requires-Dist: torchvision; extra == "all"
|
|
171
|
+
Requires-Dist: tinynetrc; extra == "all"
|
|
168
172
|
Requires-Dist: google-api-python-client; extra == "all"
|
|
173
|
+
Requires-Dist: peft; extra == "all"
|
|
174
|
+
Requires-Dist: transformers[sentencepiece]; extra == "all"
|
|
175
|
+
Requires-Dist: flet[all]; extra == "all"
|
|
169
176
|
Requires-Dist: html2text; extra == "all"
|
|
170
|
-
Requires-Dist:
|
|
171
|
-
Requires-Dist: cachetools; extra == "all"
|
|
172
|
-
Requires-Dist: httpx; extra == "all"
|
|
173
|
-
Requires-Dist: google-auth; extra == "all"
|
|
174
|
-
Requires-Dist: pymupdf; extra == "all"
|
|
175
|
-
Requires-Dist: regex; extra == "all"
|
|
177
|
+
Requires-Dist: Unidecode; extra == "all"
|
|
176
178
|
Requires-Dist: huggingface_hub; extra == "all"
|
|
177
|
-
Requires-Dist:
|
|
178
|
-
Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
|
|
179
|
-
Requires-Dist: appdirs; extra == "all"
|
|
179
|
+
Requires-Dist: sre_yield; extra == "all"
|
|
180
180
|
Requires-Dist: dnspython[doh]; extra == "all"
|
|
181
|
-
Requires-Dist:
|
|
182
|
-
Requires-Dist:
|
|
183
|
-
Requires-Dist:
|
|
181
|
+
Requires-Dist: pymupdf4llm; extra == "all"
|
|
182
|
+
Requires-Dist: tabulate; extra == "all"
|
|
183
|
+
Requires-Dist: httpx; extra == "all"
|
|
184
184
|
Dynamic: author
|
|
185
185
|
Dynamic: author-email
|
|
186
186
|
Dynamic: description
|
|
@@ -44,16 +44,16 @@ fmtr/tools/tabular_tools.py,sha256=tpIpZzYku1HcJrHZJL6BC39LmN3WUWVhFbK2N7nDVmE,1
|
|
|
44
44
|
fmtr/tools/tokenization_tools.py,sha256=me-IBzSLyNYejLybwjO9CNB6Mj2NYfKPaOVThXyaGNg,4268
|
|
45
45
|
fmtr/tools/tools.py,sha256=CAsApa1YwVdNE6H66Vjivs_mXYvOas3rh7fPELAnTpk,795
|
|
46
46
|
fmtr/tools/unicode_tools.py,sha256=yS_9wpu8ogNoiIL7s1G_8bETFFO_YQlo4LNPv1NLDeY,52
|
|
47
|
-
fmtr/tools/version,sha256=
|
|
47
|
+
fmtr/tools/version,sha256=mv8tnhpePoHazD4Fsoh-tA0aqi5t684gOq6SnQNd-oE,6
|
|
48
48
|
fmtr/tools/yaml_tools.py,sha256=Bhhyd6GQVKO72Lp8ky7bAUjIB_65Hdh0Q45SKIEe6S8,1901
|
|
49
49
|
fmtr/tools/ai_tools/__init__.py,sha256=JZrLuOFNV1A3wvJgonxOgz_4WS-7MfCuowGWA5uYCjs,372
|
|
50
50
|
fmtr/tools/ai_tools/agentic_tools.py,sha256=acSEPFS-aguDXanWGs3fAAlRyJSYPZW7L-Kb2qDLm-I,4300
|
|
51
51
|
fmtr/tools/ai_tools/inference_tools.py,sha256=2UP2gXEyOJUjyyV6zmFIYmIxUsh1rXkRH0IbFvr2bRs,11908
|
|
52
52
|
fmtr/tools/dns_tools/__init__.py,sha256=PjD3Og6D5yvDVpKmsUsrnSpz_rjXpl4zBtvMqm8xKWU,237
|
|
53
|
-
fmtr/tools/dns_tools/client.py,sha256=
|
|
54
|
-
fmtr/tools/dns_tools/dm.py,sha256=
|
|
55
|
-
fmtr/tools/dns_tools/proxy.py,sha256=
|
|
56
|
-
fmtr/tools/dns_tools/server.py,sha256=
|
|
53
|
+
fmtr/tools/dns_tools/client.py,sha256=uUFoFRwoPtetPVH6PZ3ssHmx3WEquT6UW4FCBKq4n74,2722
|
|
54
|
+
fmtr/tools/dns_tools/dm.py,sha256=_kjsJx9cyEH7qWDjSGj6C54KRvi21h1em5FZCagrFgk,5271
|
|
55
|
+
fmtr/tools/dns_tools/proxy.py,sha256=0lgn1pq5KLoGA4644ZXYs2lPjXjRB-ibFb7JHzlMY9o,1618
|
|
56
|
+
fmtr/tools/dns_tools/server.py,sha256=zLZIXJil6_FeZjIyBytkpaavkoQGN4e9lEjQdOWnazc,3629
|
|
57
57
|
fmtr/tools/entrypoints/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
58
58
|
fmtr/tools/entrypoints/cache_hfh.py,sha256=fQNs4J9twQuZH_Yj98-oOvEX7-LrSUP3kO8nzw2HrHs,60
|
|
59
59
|
fmtr/tools/entrypoints/ep_test.py,sha256=B8HfWISfSgw_xVX475CbJGh_QnpOe9MH65H8qGjTWbY,46
|
|
@@ -76,9 +76,9 @@ fmtr/tools/tests/test_path.py,sha256=AkZQa6_8BQ-VaCyL_J-iKmdf2ZaM-xFYR37Kun3k4_g
|
|
|
76
76
|
fmtr/tools/tests/test_yaml.py,sha256=jc0TwwKu9eC0LvFGNMERdgBue591xwLxYXFbtsRwXVM,287
|
|
77
77
|
fmtr/tools/version_tools/__init__.py,sha256=pg4iLtmIr5HtyEW_j0fMFoIdzqi_w9xH8-grQaXLB28,318
|
|
78
78
|
fmtr/tools/version_tools/version_tools.py,sha256=Hcc6yferZS1hHbugRTdiHhSNmXEEG0hjCiTTXKna-YY,1127
|
|
79
|
-
fmtr_tools-1.3.
|
|
80
|
-
fmtr_tools-1.3.
|
|
81
|
-
fmtr_tools-1.3.
|
|
82
|
-
fmtr_tools-1.3.
|
|
83
|
-
fmtr_tools-1.3.
|
|
84
|
-
fmtr_tools-1.3.
|
|
79
|
+
fmtr_tools-1.3.15.dist-info/licenses/LICENSE,sha256=FW9aa6vVN5IjRQWLT43hs4_koYSmpcbIovlKeAJ0_cI,10757
|
|
80
|
+
fmtr_tools-1.3.15.dist-info/METADATA,sha256=63hO7H6HYE93QoQ0Osr-VPu6NUnkgdpz21Gt6-ifylM,15938
|
|
81
|
+
fmtr_tools-1.3.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
82
|
+
fmtr_tools-1.3.15.dist-info/entry_points.txt,sha256=h-r__Xh5njtFqreMLg6cGuTFS4Qh-QqJPU1HB-_BS-Q,357
|
|
83
|
+
fmtr_tools-1.3.15.dist-info/top_level.txt,sha256=LXem9xCgNOD72tE2gRKESdiQTL902mfFkwWb6-dlwEE,5
|
|
84
|
+
fmtr_tools-1.3.15.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|