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.
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/PKG-INFO +47 -42
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/ai_tools/agentic_tools.py +28 -4
- fmtr_tools-1.2.3/fmtr/tools/dns_tools.py +221 -0
- fmtr_tools-1.2.3/fmtr/tools/version +1 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr.tools.egg-info/PKG-INFO +47 -42
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr.tools.egg-info/requires.txt +46 -41
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/setup.py +1 -1
- fmtr_tools-1.2.1/fmtr/tools/dns_tools.py +0 -57
- fmtr_tools-1.2.1/fmtr/tools/version +0 -1
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/LICENSE +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/README.md +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/__init__.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/ai_tools/__init__.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/ai_tools/inference_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/api_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/async_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/augmentation_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/caching_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/constants.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/data_modelling_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/dataclass_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/datatype_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/debugging_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/docker_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/entrypoints/__init__.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/entrypoints/cache_hfh.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/entrypoints/ep_test.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/entrypoints/remote_debug_test.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/entrypoints/shell_debug.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/environment_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/function_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/google_api_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/hash_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/hfh_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/html_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/http_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/import_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/inspection_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/interface_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/iterator_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/json_fix_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/json_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/logging_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/merging_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/metric_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/name_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/netrc_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/openai_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/packaging_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/parallel_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/path_tools/__init__.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/path_tools/app_path_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/path_tools/path_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/path_tools/type_path_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/pattern_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/pdf_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/platform_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/process_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/profiling_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/random_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/semantic_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/settings_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/setup_tools/__init__.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/setup_tools/setup_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/spaces_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/string_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tabular_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/__init__.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/conftest.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/helpers.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/test_datatype.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/test_environment.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/test_json.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/test_path.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tests/test_yaml.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tokenization_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/unicode_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/version_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr/tools/yaml_tools.py +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr.tools.egg-info/SOURCES.txt +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr.tools.egg-info/dependency_links.txt +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr.tools.egg-info/entry_points.txt +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/fmtr.tools.egg-info/top_level.txt +0 -0
- {fmtr_tools-1.2.1 → fmtr_tools-1.2.3}/pyproject.toml +0 -0
- {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.
|
|
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:
|
|
127
|
-
Requires-Dist:
|
|
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:
|
|
130
|
-
Requires-Dist:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
158
|
+
Requires-Dist: deepmerge; extra == "all"
|
|
161
159
|
Requires-Dist: contexttimer; extra == "all"
|
|
162
|
-
Requires-Dist:
|
|
163
|
-
Requires-Dist:
|
|
164
|
-
Requires-Dist:
|
|
165
|
-
Requires-Dist:
|
|
166
|
-
Requires-Dist:
|
|
167
|
-
Requires-Dist:
|
|
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:
|
|
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:
|
|
179
|
-
Requires-Dist:
|
|
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
|
-
|
|
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.
|
|
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:
|
|
127
|
-
Requires-Dist:
|
|
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:
|
|
130
|
-
Requires-Dist:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
158
|
+
Requires-Dist: deepmerge; extra == "all"
|
|
161
159
|
Requires-Dist: contexttimer; extra == "all"
|
|
162
|
-
Requires-Dist:
|
|
163
|
-
Requires-Dist:
|
|
164
|
-
Requires-Dist:
|
|
165
|
-
Requires-Dist:
|
|
166
|
-
Requires-Dist:
|
|
167
|
-
Requires-Dist:
|
|
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:
|
|
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:
|
|
179
|
-
Requires-Dist:
|
|
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
|
-
|
|
19
|
-
|
|
18
|
+
logfire
|
|
19
|
+
tokenizers
|
|
20
|
+
appdirs
|
|
21
|
+
pydantic-settings
|
|
20
22
|
filetype
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
pymupdf
|
|
24
|
+
json_repair
|
|
25
|
+
sentence_transformers
|
|
23
26
|
fastapi
|
|
24
|
-
|
|
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
|
-
|
|
32
|
+
html2text
|
|
33
|
+
google-auth-httplib2
|
|
34
|
+
bokeh
|
|
48
35
|
flet-webview
|
|
49
|
-
|
|
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
|
-
|
|
45
|
+
deepmerge
|
|
53
46
|
contexttimer
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
pydantic
|
|
64
|
-
peft
|
|
65
|
-
google-auth
|
|
66
|
-
httpx_retries
|
|
67
|
-
tabulate
|
|
68
|
-
json_repair
|
|
61
|
+
httpx
|
|
69
62
|
sre_yield
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
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
|