fmtr.tools 1.1.15__tar.gz → 1.1.17__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of fmtr.tools might be problematic. Click here for more details.

Files changed (78) hide show
  1. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/PKG-INFO +58 -40
  2. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/__init__.py +16 -1
  3. fmtr_tools-1.1.17/fmtr/tools/dns_tools.py +57 -0
  4. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/logging_tools.py +11 -3
  5. fmtr_tools-1.1.17/fmtr/tools/pattern_tools.py +237 -0
  6. fmtr_tools-1.1.17/fmtr/tools/setup_tools.py +268 -0
  7. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/string_tools.py +0 -6
  8. fmtr_tools-1.1.17/fmtr/tools/version +1 -0
  9. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr.tools.egg-info/PKG-INFO +58 -40
  10. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr.tools.egg-info/SOURCES.txt +3 -0
  11. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr.tools.egg-info/requires.txt +61 -39
  12. fmtr_tools-1.1.15/fmtr/tools/version +0 -1
  13. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/LICENSE +0 -0
  14. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/README.md +0 -0
  15. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/ai_tools/__init__.py +0 -0
  16. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/ai_tools/agentic_tools.py +0 -0
  17. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/ai_tools/inference_tools.py +0 -0
  18. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/api_tools.py +0 -0
  19. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/async_tools.py +0 -0
  20. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/augmentation_tools.py +0 -0
  21. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/caching_tools.py +0 -0
  22. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/console_script_tools.py +0 -0
  23. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/constants.py +0 -0
  24. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/data_modelling_tools.py +0 -0
  25. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/dataclass_tools.py +0 -0
  26. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/datatype_tools.py +0 -0
  27. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/debugging_tools.py +0 -0
  28. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/docker_tools.py +0 -0
  29. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/environment_tools.py +0 -0
  30. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/function_tools.py +0 -0
  31. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/google_api_tools.py +0 -0
  32. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/hash_tools.py +0 -0
  33. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/hfh_tools.py +0 -0
  34. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/html_tools.py +0 -0
  35. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/import_tools.py +0 -0
  36. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/inspection_tools.py +0 -0
  37. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/interface_tools.py +0 -0
  38. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/iterator_tools.py +0 -0
  39. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/json_fix_tools.py +0 -0
  40. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/json_tools.py +0 -0
  41. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/merging_tools.py +0 -0
  42. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/metric_tools.py +0 -0
  43. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/name_tools.py +0 -0
  44. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/netrc_tools.py +0 -0
  45. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/openai_tools.py +0 -0
  46. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/packaging_tools.py +0 -0
  47. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/parallel_tools.py +0 -0
  48. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/path_tools/__init__.py +0 -0
  49. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/path_tools/app_path_tools.py +0 -0
  50. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/path_tools/path_tools.py +0 -0
  51. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/path_tools/type_path_tools.py +0 -0
  52. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/pdf_tools.py +0 -0
  53. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/platform_tools.py +0 -0
  54. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/process_tools.py +0 -0
  55. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/profiling_tools.py +0 -0
  56. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/random_tools.py +0 -0
  57. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/semantic_tools.py +0 -0
  58. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/settings_tools.py +0 -0
  59. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/spaces_tools.py +0 -0
  60. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/tabular_tools.py +0 -0
  61. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/tests/__init__.py +0 -0
  62. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/tests/conftest.py +0 -0
  63. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/tests/helpers.py +0 -0
  64. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/tests/test_datatype.py +0 -0
  65. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/tests/test_environment.py +0 -0
  66. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/tests/test_json.py +0 -0
  67. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/tests/test_path.py +0 -0
  68. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/tests/test_yaml.py +0 -0
  69. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/tokenization_tools.py +0 -0
  70. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/tools.py +0 -0
  71. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/unicode_tools.py +0 -0
  72. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/version_tools.py +0 -0
  73. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr/tools/yaml_tools.py +0 -0
  74. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr.tools.egg-info/dependency_links.txt +0 -0
  75. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr.tools.egg-info/entry_points.txt +0 -0
  76. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/fmtr.tools.egg-info/top_level.txt +0 -0
  77. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/setup.cfg +0 -0
  78. {fmtr_tools-1.1.15 → fmtr_tools-1.1.17}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmtr.tools
3
- Version: 1.1.15
3
+ Version: 1.1.17
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
@@ -9,54 +9,60 @@ License: Copyright © 2025 Frontmatter. All rights reserved.
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Provides-Extra: test
12
- Requires-Dist: google-auth-oauthlib; extra == "test"
13
- Requires-Dist: logfire; extra == "test"
14
- Requires-Dist: torchvision; extra == "test"
15
- Requires-Dist: uvicorn[standard]; extra == "test"
16
- Requires-Dist: tinynetrc; extra == "test"
17
- Requires-Dist: google-auth; extra == "test"
12
+ Requires-Dist: fastapi; extra == "test"
13
+ Requires-Dist: tabulate; extra == "test"
14
+ Requires-Dist: torchaudio; extra == "test"
15
+ Requires-Dist: semver; extra == "test"
16
+ Requires-Dist: distributed; extra == "test"
17
+ Requires-Dist: filetype; extra == "test"
18
+ Requires-Dist: pandas; extra == "test"
19
+ Requires-Dist: dnspython[doh]; extra == "test"
20
+ Requires-Dist: sentence_transformers; extra == "test"
21
+ Requires-Dist: logfire[httpx]; extra == "test"
22
+ Requires-Dist: transformers[sentencepiece]; extra == "test"
18
23
  Requires-Dist: openai; extra == "test"
19
- Requires-Dist: bokeh; extra == "test"
20
- Requires-Dist: appdirs; extra == "test"
24
+ Requires-Dist: regex; extra == "test"
25
+ Requires-Dist: contexttimer; extra == "test"
26
+ Requires-Dist: uvicorn[standard]; extra == "test"
21
27
  Requires-Dist: flet-webview; extra == "test"
28
+ Requires-Dist: pyyaml; extra == "test"
29
+ Requires-Dist: tinynetrc; extra == "test"
22
30
  Requires-Dist: pytest-cov; extra == "test"
23
- Requires-Dist: google-api-python-client; extra == "test"
24
- Requires-Dist: diskcache; extra == "test"
25
- Requires-Dist: openpyxl; extra == "test"
31
+ Requires-Dist: tokenizers; extra == "test"
32
+ Requires-Dist: logfire[fastapi]; extra == "test"
33
+ Requires-Dist: json_repair; extra == "test"
34
+ Requires-Dist: faker; extra == "test"
26
35
  Requires-Dist: huggingface_hub; extra == "test"
27
- Requires-Dist: semver; extra == "test"
28
- Requires-Dist: dask[bag]; extra == "test"
29
- Requires-Dist: sentence_transformers; extra == "test"
30
- Requires-Dist: contexttimer; extra == "test"
36
+ Requires-Dist: flet[all]; extra == "test"
37
+ Requires-Dist: torchvision; extra == "test"
38
+ Requires-Dist: httpx; extra == "test"
39
+ Requires-Dist: pydantic-settings; extra == "test"
40
+ Requires-Dist: google-auth-oauthlib; extra == "test"
41
+ Requires-Dist: setuptools; extra == "test"
31
42
  Requires-Dist: pydantic-ai[logfire,openai]; extra == "test"
32
- Requires-Dist: flet-video; extra == "test"
33
- Requires-Dist: tabulate; extra == "test"
34
- Requires-Dist: faker; extra == "test"
35
- Requires-Dist: deepmerge; extra == "test"
43
+ Requires-Dist: logfire; extra == "test"
44
+ Requires-Dist: dask[bag]; extra == "test"
45
+ Requires-Dist: html2text; extra == "test"
46
+ Requires-Dist: yamlscript; extra == "test"
47
+ Requires-Dist: pymupdf4llm; extra == "test"
48
+ Requires-Dist: peft; extra == "test"
49
+ Requires-Dist: bokeh; extra == "test"
50
+ Requires-Dist: Unidecode; extra == "test"
51
+ Requires-Dist: google-auth; extra == "test"
52
+ Requires-Dist: appdirs; extra == "test"
53
+ Requires-Dist: openpyxl; extra == "test"
54
+ Requires-Dist: diskcache; extra == "test"
55
+ Requires-Dist: pydantic; extra == "test"
56
+ Requires-Dist: pydevd-pycharm; extra == "test"
36
57
  Requires-Dist: sre_yield; extra == "test"
37
58
  Requires-Dist: docker; extra == "test"
38
- Requires-Dist: pandas; extra == "test"
39
- Requires-Dist: pymupdf4llm; extra == "test"
40
- Requires-Dist: json_repair; extra == "test"
41
- Requires-Dist: distributed; extra == "test"
42
- Requires-Dist: pymupdf; extra == "test"
43
- Requires-Dist: html2text; extra == "test"
44
- Requires-Dist: transformers[sentencepiece]; extra == "test"
45
- Requires-Dist: filetype; extra == "test"
46
- Requires-Dist: logfire[fastapi]; extra == "test"
47
59
  Requires-Dist: ollama; extra == "test"
48
- Requires-Dist: tokenizers; extra == "test"
49
- Requires-Dist: Unidecode; extra == "test"
60
+ Requires-Dist: google-api-python-client; extra == "test"
61
+ Requires-Dist: pymupdf; extra == "test"
62
+ Requires-Dist: httpx_retries; extra == "test"
63
+ Requires-Dist: flet-video; extra == "test"
64
+ Requires-Dist: deepmerge; extra == "test"
50
65
  Requires-Dist: google-auth-httplib2; extra == "test"
51
- Requires-Dist: flet[all]; extra == "test"
52
- Requires-Dist: torchaudio; extra == "test"
53
- Requires-Dist: pyyaml; extra == "test"
54
- Requires-Dist: pydantic-settings; extra == "test"
55
- Requires-Dist: yamlscript; extra == "test"
56
- Requires-Dist: fastapi; extra == "test"
57
- Requires-Dist: pydevd-pycharm; extra == "test"
58
- Requires-Dist: pydantic; extra == "test"
59
- Requires-Dist: peft; extra == "test"
60
66
  Provides-Extra: yaml
61
67
  Requires-Dist: yamlscript; extra == "yaml"
62
68
  Requires-Dist: pyyaml; extra == "yaml"
@@ -157,6 +163,18 @@ Provides-Extra: path-app
157
163
  Requires-Dist: appdirs; extra == "path-app"
158
164
  Provides-Extra: path-type
159
165
  Requires-Dist: filetype; extra == "path-type"
166
+ Provides-Extra: dns
167
+ Requires-Dist: dnspython[doh]; extra == "dns"
168
+ Provides-Extra: patterns
169
+ Requires-Dist: regex; extra == "patterns"
170
+ Provides-Extra: http
171
+ Requires-Dist: httpx; extra == "http"
172
+ Requires-Dist: httpx_retries; extra == "http"
173
+ Requires-Dist: logfire; extra == "http"
174
+ Requires-Dist: semver; extra == "http"
175
+ Requires-Dist: logfire[httpx]; extra == "http"
176
+ Provides-Extra: setup
177
+ Requires-Dist: setuptools; extra == "setup"
160
178
  Dynamic: author
161
179
  Dynamic: author-email
162
180
  Dynamic: description
@@ -163,4 +163,19 @@ except ImportError as exception:
163
163
  try:
164
164
  from fmtr.tools import settings_tools as sets
165
165
  except ImportError as exception:
166
- sets = MissingExtraMockModule('sets', exception)
166
+ sets = MissingExtraMockModule('sets', exception)
167
+
168
+ try:
169
+ from fmtr.tools import pattern_tools as patterns
170
+ except ImportError as exception:
171
+ patterns = MissingExtraMockModule('patterns', exception)
172
+
173
+ try:
174
+ from fmtr.tools import dns_tools as dns
175
+ except ImportError as exception:
176
+ dns = MissingExtraMockModule('dns', exception)
177
+
178
+ try:
179
+ from fmtr.tools import setup_tools as setup
180
+ except ImportError as exception:
181
+ setup = MissingExtraMockModule('setup', exception)
@@ -0,0 +1,57 @@
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()
@@ -43,16 +43,23 @@ def get_logger(name, version=None, host=Constants.FMTR_OBS_HOST, key=None, org=C
43
43
  from fmtr.tools import version_tools
44
44
  version = version_tools.read()
45
45
 
46
+ # Rigmarole to translate native levels to logfire/otel ones.
47
+ lev_num_otel = logfire._internal.constants.LOGGING_TO_OTEL_LEVEL_NUMBERS[level]
48
+ lev_name_otel = logfire._internal.constants.NUMBER_TO_LEVEL[lev_num_otel]
49
+
50
+ console_opts = logfire.ConsoleOptions(
51
+ colors='always' if environment_tools.IS_DEBUG else 'auto',
52
+ min_log_level=lev_name_otel,
53
+ )
54
+
46
55
  logfire.configure(
47
56
  service_name=name,
48
57
  service_version=version,
49
58
  environment=environment,
50
59
  send_to_logfire=False,
51
- console=logfire.ConsoleOptions(colors='always' if environment_tools.IS_DEBUG else 'auto')
60
+ console=console_opts
52
61
  )
53
62
 
54
- logging.getLogger(name).setLevel(level)
55
-
56
63
  logger = logfire
57
64
  return logger
58
65
 
@@ -63,3 +70,4 @@ if __name__ == '__main__':
63
70
  logger.info('Hello World')
64
71
  logger.warning('test warning')
65
72
  logger.debug('Hello World')
73
+ logger
@@ -0,0 +1,237 @@
1
+ import regex as re
2
+ from dataclasses import dataclass
3
+ from functools import cached_property
4
+ from typing import List
5
+
6
+ from fmtr.tools.logging_tools import logger
7
+
8
+
9
+ class RewriteCircularLoopError(Exception):
10
+ """
11
+
12
+ Circular loop error
13
+
14
+ """
15
+
16
+
17
+ @dataclass
18
+ class Rule:
19
+ """
20
+ Represents a single rule for pattern matching and target string replacement.
21
+
22
+ This class is used to define a rule with a pattern and a target string.
23
+ The `pattern` is a regular expression used to identify matches in input text.
24
+ The `target` allows rewriting the identified matches with a formatted string.
25
+ It provides properties for generating a unique identifier for use as a regex group name and compiling the provided pattern into a regular expression object.
26
+
27
+ """
28
+ pattern: str
29
+ target: str
30
+
31
+ @cached_property
32
+ def id(self):
33
+ """
34
+
35
+ Regex group name.
36
+
37
+ """
38
+ return f'id{abs(hash(self.pattern))}'
39
+
40
+ @cached_property
41
+ def rx(self):
42
+ """
43
+
44
+ Regex object.
45
+
46
+ """
47
+ return re.compile(self.pattern)
48
+
49
+ def apply(self, match: re.Match):
50
+ """
51
+
52
+ Rewrite using the target string and match groups.
53
+
54
+ """
55
+ target = self.target.format(**match.groupdict())
56
+ return target
57
+
58
+
59
+ @dataclass
60
+ class Rewriter:
61
+ """
62
+
63
+ Represents a Rewriter class that handles pattern matching, rule application, and text rewriting.
64
+ Compiles a single regex pattern from a list of rules, and determines which rule matched.
65
+ It supports initialization from structured rule data, execution of a single rewrite pass, and
66
+ recursive rewriting until a stable state is reached.
67
+
68
+ """
69
+ rules: List[Rule]
70
+
71
+ @cached_property
72
+ def pattern(self):
73
+ """
74
+
75
+ Provides a dynamically generated regex pattern based on the rules provided.
76
+
77
+ """
78
+ patterns = [fr"(?P<{rule.id}>{rule.pattern})" for rule in self.rules]
79
+ sorted(patterns, key=len, reverse=True)
80
+ pattern = '|'.join(patterns)
81
+ return pattern
82
+
83
+ @cached_property
84
+ def rule_lookup(self):
85
+ """
86
+
87
+ Dictionary mapping rule identifiers to their corresponding rules.
88
+ """
89
+
90
+ return {rule.id: rule for rule in self.rules}
91
+
92
+ @cached_property
93
+ def rx(self):
94
+ """
95
+
96
+ Regex object.
97
+
98
+ """
99
+ return re.compile(self.pattern)
100
+
101
+ def rewrite_pass(self, source: str):
102
+ """
103
+
104
+ Single rewrite pass.
105
+ Rewrites the provided source string based on the matching rule.
106
+
107
+ """
108
+
109
+ match = self.rx.match(source)
110
+
111
+ if not match:
112
+ return source
113
+
114
+ match_ids = {k: v for k, v in match.groupdict().items() if v}
115
+ match_id = match_ids & self.rule_lookup.keys()
116
+
117
+ if len(match_id) != 1:
118
+ msg = f'Multiple group matches: {match_id}'
119
+ raise ValueError(msg)
120
+
121
+ match_id = next(iter(match_id))
122
+ rule = self.rule_lookup[match_id]
123
+ target = rule.apply(match)
124
+
125
+ logger.debug(f'Rewrote using {match_id=}: {source=} -> {target=}')
126
+
127
+ return target
128
+
129
+ def rewrite(self, source: str) -> str:
130
+ """
131
+
132
+ Rewrites the provided text by continuously applying rewrite rules until no changes are made
133
+ or a circular loop is detected.
134
+
135
+ """
136
+ history = []
137
+ previous = source
138
+
139
+ def get_history_str():
140
+ return ' -> '.join(history)
141
+
142
+ with logger.span(f'Rewriting "{source}"...'):
143
+ while True:
144
+ if previous in history:
145
+ history.append(previous)
146
+ msg = f'Loop detected on node "{previous}": {get_history_str()}'
147
+ raise RewriteCircularLoopError(msg)
148
+
149
+ history.append(previous)
150
+
151
+ new = previous
152
+
153
+ new = self.rewrite_pass(new)
154
+
155
+ if new == previous:
156
+ break
157
+
158
+ previous = new
159
+
160
+ logger.debug(f'Finished rewriting: {get_history_str()}')
161
+
162
+ return previous
163
+
164
+ @classmethod
165
+ def from_data(cls, data):
166
+ rules = [Rule(*pair) for pair in data.items()]
167
+ self = cls(rules=rules)
168
+ return self
169
+
170
+
171
+ if __name__ == '__main__':
172
+ data = {
173
+
174
+ r'(?P<name>[a-z]+)\.dev\.example\.com': '{name}.test.example.com',
175
+ r'img\d+\.static\.cdn\.example\.com': 'images.cdn.example.com',
176
+ r'service\.(?P<env>dev|staging|prod)\.example\.org': '{env}-service.example.org',
177
+ r'legacy\.(?P<region>[a-z]+)\.oldsite\.com': '{region}.newsitenow.com',
178
+ r'shop\.(?P<country_code>[a-z]{2})\.example\.net': 'store.{country_code}.example.net',
179
+ r'(?P<user>[a-z]+)\.mail\.example\.com': '{user}.email.example.com',
180
+ r'app1\.cluster(?P<num>[0-9]+)\.example\.cloud': 'service{num}.example.cloud',
181
+ r'(?P<project>[a-z]+)\.research\.corp\.com': '{project}.lab.corp.com',
182
+ r'cdn\.(?P<version>v[0-9]+)\.content\.net': 'static.{version}.content.net',
183
+ # Literal rule without named group
184
+ r'corp\.secureaccess\.com': 'access.corp.com',
185
+ # Literal rule without named group
186
+ r'redirect\.oldsite\.org': 'homepage.newsite.org',
187
+ # # Recursive rules
188
+
189
+ r'archive\.(?P<year>\d{4})\.oldsite\.net': 'legacy.{year}.oldsite.net', # Recursive matching
190
+ r'legacy\.(?P<year>\d{4})\.oldsite\.net': 'archive-backup.{year}.net', # Continuation
191
+
192
+ # r'(?P<subd>[a-zA-Z]+)\.vpn': '{subd}.loop.ts.net',
193
+ # r'(?P<subd>[a-zA-Z]+)\.loop\.ts\.net': '{subd}.vpn', # Recursive loop back to .vpn
194
+ }
195
+
196
+ rewriter = Rewriter.from_data(data)
197
+
198
+ tests = [
199
+ "sales.vpn",
200
+ "marketing.dev.example.com",
201
+ "img01.static.cdn.example.com",
202
+ "service.dev.example.org",
203
+ "legacy.eu.oldsite.com",
204
+ "shop.us.example.net",
205
+ "alice.mail.example.com",
206
+ "app1.cluster1.example.cloud",
207
+ "genetics.research.corp.com",
208
+ "cdn.v2.content.net",
209
+ "corp.secureaccess.com",
210
+ "redirect.oldsite.org",
211
+ "support.bbb.ts.net",
212
+ "support.ccc.ts.net",
213
+ "archive.2022.oldsite.net",
214
+ "legacy.2022.oldsite.net",
215
+ "finance.vpn",
216
+ "engineering.dev.example.com",
217
+ "img02.static.cdn.example.com",
218
+ "service.staging.example.org",
219
+ "legacy.apac.oldsite.com",
220
+ "shop.uk.example.net",
221
+ "bob.mail.example.com",
222
+ "app1.cluster2.example.cloud",
223
+ "astrophysics.research.corp.com",
224
+ "cdn.v3.content.net",
225
+ "archive.2021.oldsite.net",
226
+ "legacy.2021.oldsite.net",
227
+ "quality.bbb.ts.net",
228
+ "quality.ccc.ts.net"
229
+ ]
230
+
231
+ logger.warning('hello?')
232
+ for test in tests:
233
+ print(test)
234
+ text = rewriter.rewrite(test)
235
+ text
236
+
237
+ tests
@@ -0,0 +1,268 @@
1
+ from itertools import chain
2
+
3
+ from datetime import datetime
4
+ from functools import cached_property
5
+ from setuptools import find_namespace_packages, find_packages
6
+ from typing import List, Dict
7
+
8
+ from fmtr.tools.constants import Constants
9
+ from fmtr.tools.path_tools import Path
10
+ from fmtr.tools.path_tools.path_tools import PathsBase, path
11
+
12
+
13
+ class SetupPaths(PathsBase):
14
+ """
15
+
16
+ Canonical paths for a package.
17
+
18
+ """
19
+
20
+ SKIP_DIRS = {'data'}
21
+
22
+ def __init__(self, path=None):
23
+
24
+ """
25
+
26
+ Use calling module path as default path, if not otherwise specified.
27
+
28
+ """
29
+ if not path:
30
+ path = self.from_caller()
31
+
32
+ self.repo = Path(path)
33
+
34
+ @property
35
+ def path(self):
36
+ if self.is_namespace:
37
+ return self.repo / self.org / self.name
38
+ else:
39
+ return self.repo / self.name
40
+
41
+ @property
42
+ def readme(self):
43
+ return self.repo / 'README.md'
44
+
45
+ @property
46
+ def version(self):
47
+ return self.path / Constants.FILENAME_VERSION
48
+
49
+ @cached_property
50
+ def layout(self):
51
+
52
+ directories = [
53
+ dir for dir in self.repo.iterdir()
54
+ if dir.is_dir() and not dir.name.startswith('.') and dir.name not in self.SKIP_DIRS
55
+ ]
56
+
57
+ if len(directories) != 1:
58
+ raise ValueError(f'Expected exactly one directory in "{path}", found {','.join(directories)}')
59
+
60
+ target = next(iter(directories))
61
+
62
+ contents = list(target.iterdir())
63
+ if len(contents) == 1 and (item := next(iter(contents))).is_dir():
64
+ return True, target.name, item.name
65
+
66
+ else:
67
+ return False, None, target.name
68
+
69
+ @property
70
+ def is_namespace(self) -> str:
71
+ is_namespace, org, name = self.layout
72
+ return is_namespace
73
+
74
+ @property
75
+ def org(self) -> str:
76
+ is_namespace, org, name = self.layout
77
+ return org
78
+
79
+ @property
80
+ def name(self) -> str:
81
+ is_namespace, org, name = self.layout
82
+ return name
83
+
84
+
85
+ class Setup:
86
+ AUTHOR = 'Frontmatter'
87
+ AUTHOR_EMAIL = 'innovative.fowler@mask.pro.fmtr.dev'
88
+
89
+ paths = SetupPaths(path=Path(__file__).absolute().parent.parent.parent)
90
+
91
+ def __init__(self, dependencies, console_scripts=None, client=None, **kwargs):
92
+
93
+ self.client = client
94
+ self.kwargs = kwargs
95
+ self.dependencies = dependencies
96
+ self.paths
97
+
98
+ self.console_scripts = console_scripts
99
+
100
+ def get_entrypoint_path(self, key, value):
101
+ if value:
102
+ return f'{self.name}.{value}:{key}'
103
+ else:
104
+ return f'{self.name}:{key}'
105
+
106
+ @property
107
+ def entrypoints(self):
108
+ if self.console_scripts:
109
+ return dict(
110
+ console_scripts=[f'{key} = {self.get_entrypoint_path(key, value)}' for key, value in self.console_scripts.items()],
111
+ )
112
+ else:
113
+ return dict()
114
+
115
+ @property
116
+ def name(self):
117
+ if self.paths.is_namespace:
118
+ return f'{self.paths.org}.{self.paths.name}'
119
+ return self.paths.name
120
+
121
+ @property
122
+ def author(self):
123
+ if self.client:
124
+ return f'{self.AUTHOR} on behalf of {self.client}'
125
+ return self.AUTHOR
126
+
127
+ @property
128
+ def copyright(self):
129
+ if self.client:
130
+ return self.client
131
+ return self.AUTHOR
132
+
133
+ @property
134
+ def long_description(self):
135
+
136
+ return self.paths.readme.read_text()
137
+
138
+ @property
139
+ def version(self):
140
+ return self.paths.version.read_text().strip()
141
+
142
+ @property
143
+ def packages(self):
144
+ if self.paths.is_namespace:
145
+ return find_namespace_packages(where=str(self.paths.repo))
146
+ else:
147
+ return find_packages(where=str(self.paths.repo))
148
+
149
+ @property
150
+ def package_dir(self):
151
+ if self.paths.is_namespace:
152
+ return {'': str(self.paths.repo)}
153
+ else:
154
+ return None
155
+
156
+ @property
157
+ def package_data(self):
158
+ return {self.name: [Constants.FILENAME_VERSION]}
159
+
160
+ @property
161
+ def url(self):
162
+ return f'https://github.com/{self.paths.org}/{self.paths.name}'
163
+
164
+ def get_data_setup(self):
165
+ return dict(
166
+ name=self.name,
167
+ version=self.version,
168
+ author=self.author,
169
+ author_email=self.AUTHOR_EMAIL,
170
+ url=self.url,
171
+ license=f'Copyright © {datetime.now().year} {self.copyright}. All rights reserved.',
172
+ long_description=self.long_description,
173
+ long_description_content_type='text/markdown',
174
+ packages=self.packages,
175
+ package_dir=self.package_dir,
176
+ package_data=self.package_data,
177
+ entry_points=self.entrypoints,
178
+ install_requires=self.dependencies.install,
179
+ extras_require=self.dependencies.extras,
180
+ ) | self.kwargs
181
+
182
+
183
+ class Entrypoints:
184
+ ALL = 'all'
185
+ INSTALL = 'install'
186
+
187
+ def __init__(self, console_scripts=None, **kwargs):
188
+ self.kwargs = kwargs
189
+ self._console_scripts = console_scripts
190
+
191
+ @property
192
+ def console_scripts(self):
193
+ return [f'{key} = {value}:{key}' for key, value in self._console_scripts.items()]
194
+
195
+ @property
196
+ def data(self):
197
+ return dict(
198
+ console_scripts=self.console_scripts,
199
+ ) | self.kwargs
200
+
201
+
202
+ class Dependencies:
203
+ ALL = 'all'
204
+ INSTALL = 'install'
205
+
206
+ def __init__(self, **kwargs):
207
+ self.dependencies = kwargs
208
+
209
+ def resolve_values(self, key) -> List[str]:
210
+ """
211
+
212
+ Flatten a list of values.
213
+
214
+ """
215
+ values_resolved = []
216
+ values = self.dependencies[key]
217
+
218
+ for value in values:
219
+ if value == key or value not in self.dependencies:
220
+ # Add the value directly if it references itself or is not a dependency key.
221
+ values_resolved.append(value)
222
+ else:
223
+ # Recurse into nested dependencies.
224
+ values_resolved += self.resolve_values(value)
225
+
226
+ return values_resolved
227
+
228
+ @property
229
+ def extras(self) -> Dict[str, List[str]]:
230
+ """
231
+
232
+ Flatten dependencies.
233
+
234
+ """
235
+ resolved = {key: self.resolve_values(key) for key in self.dependencies.keys()}
236
+ resolved.pop(self.INSTALL, None)
237
+ resolved[self.ALL] = list(set(chain.from_iterable(resolved.values())))
238
+ return resolved
239
+
240
+ @property
241
+ def install(self):
242
+ return self.resolve_values(self.INSTALL)
243
+
244
+
245
+ if __name__ == '__main__':
246
+ ds = Dependencies(
247
+ install=['version', 'yaml'],
248
+
249
+ yaml=['yamlscript', 'pyyaml'],
250
+ logging=['logfire', 'version'],
251
+ version=['semver', 'av'],
252
+ av=['av']
253
+ # Add the rest of your dependencies...
254
+ )
255
+
256
+ ds
257
+
258
+ setup = Setup(
259
+ # client='Acme',
260
+ dependencies=ds,
261
+ description='some tools test',
262
+ console_scripts=dict(
263
+ cache_hfh='console_script_tools',
264
+ test=None,
265
+ )
266
+ )
267
+ data = setup.get_data_setup()
268
+ data
@@ -171,9 +171,3 @@ def trim(text: str) -> str:
171
171
 
172
172
  """
173
173
  return dedent(text).strip()
174
-
175
- if __name__ == '__main__':
176
- import numpy as np
177
-
178
- st = join([1, None, 'test', np.nan, 0, '', 'yeah'])
179
- st
@@ -0,0 +1 @@
1
+ 1.1.17
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmtr.tools
3
- Version: 1.1.15
3
+ Version: 1.1.17
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
@@ -9,54 +9,60 @@ License: Copyright © 2025 Frontmatter. All rights reserved.
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Provides-Extra: test
12
- Requires-Dist: google-auth-oauthlib; extra == "test"
13
- Requires-Dist: logfire; extra == "test"
14
- Requires-Dist: torchvision; extra == "test"
15
- Requires-Dist: uvicorn[standard]; extra == "test"
16
- Requires-Dist: tinynetrc; extra == "test"
17
- Requires-Dist: google-auth; extra == "test"
12
+ Requires-Dist: fastapi; extra == "test"
13
+ Requires-Dist: tabulate; extra == "test"
14
+ Requires-Dist: torchaudio; extra == "test"
15
+ Requires-Dist: semver; extra == "test"
16
+ Requires-Dist: distributed; extra == "test"
17
+ Requires-Dist: filetype; extra == "test"
18
+ Requires-Dist: pandas; extra == "test"
19
+ Requires-Dist: dnspython[doh]; extra == "test"
20
+ Requires-Dist: sentence_transformers; extra == "test"
21
+ Requires-Dist: logfire[httpx]; extra == "test"
22
+ Requires-Dist: transformers[sentencepiece]; extra == "test"
18
23
  Requires-Dist: openai; extra == "test"
19
- Requires-Dist: bokeh; extra == "test"
20
- Requires-Dist: appdirs; extra == "test"
24
+ Requires-Dist: regex; extra == "test"
25
+ Requires-Dist: contexttimer; extra == "test"
26
+ Requires-Dist: uvicorn[standard]; extra == "test"
21
27
  Requires-Dist: flet-webview; extra == "test"
28
+ Requires-Dist: pyyaml; extra == "test"
29
+ Requires-Dist: tinynetrc; extra == "test"
22
30
  Requires-Dist: pytest-cov; extra == "test"
23
- Requires-Dist: google-api-python-client; extra == "test"
24
- Requires-Dist: diskcache; extra == "test"
25
- Requires-Dist: openpyxl; extra == "test"
31
+ Requires-Dist: tokenizers; extra == "test"
32
+ Requires-Dist: logfire[fastapi]; extra == "test"
33
+ Requires-Dist: json_repair; extra == "test"
34
+ Requires-Dist: faker; extra == "test"
26
35
  Requires-Dist: huggingface_hub; extra == "test"
27
- Requires-Dist: semver; extra == "test"
28
- Requires-Dist: dask[bag]; extra == "test"
29
- Requires-Dist: sentence_transformers; extra == "test"
30
- Requires-Dist: contexttimer; extra == "test"
36
+ Requires-Dist: flet[all]; extra == "test"
37
+ Requires-Dist: torchvision; extra == "test"
38
+ Requires-Dist: httpx; extra == "test"
39
+ Requires-Dist: pydantic-settings; extra == "test"
40
+ Requires-Dist: google-auth-oauthlib; extra == "test"
41
+ Requires-Dist: setuptools; extra == "test"
31
42
  Requires-Dist: pydantic-ai[logfire,openai]; extra == "test"
32
- Requires-Dist: flet-video; extra == "test"
33
- Requires-Dist: tabulate; extra == "test"
34
- Requires-Dist: faker; extra == "test"
35
- Requires-Dist: deepmerge; extra == "test"
43
+ Requires-Dist: logfire; extra == "test"
44
+ Requires-Dist: dask[bag]; extra == "test"
45
+ Requires-Dist: html2text; extra == "test"
46
+ Requires-Dist: yamlscript; extra == "test"
47
+ Requires-Dist: pymupdf4llm; extra == "test"
48
+ Requires-Dist: peft; extra == "test"
49
+ Requires-Dist: bokeh; extra == "test"
50
+ Requires-Dist: Unidecode; extra == "test"
51
+ Requires-Dist: google-auth; extra == "test"
52
+ Requires-Dist: appdirs; extra == "test"
53
+ Requires-Dist: openpyxl; extra == "test"
54
+ Requires-Dist: diskcache; extra == "test"
55
+ Requires-Dist: pydantic; extra == "test"
56
+ Requires-Dist: pydevd-pycharm; extra == "test"
36
57
  Requires-Dist: sre_yield; extra == "test"
37
58
  Requires-Dist: docker; extra == "test"
38
- Requires-Dist: pandas; extra == "test"
39
- Requires-Dist: pymupdf4llm; extra == "test"
40
- Requires-Dist: json_repair; extra == "test"
41
- Requires-Dist: distributed; extra == "test"
42
- Requires-Dist: pymupdf; extra == "test"
43
- Requires-Dist: html2text; extra == "test"
44
- Requires-Dist: transformers[sentencepiece]; extra == "test"
45
- Requires-Dist: filetype; extra == "test"
46
- Requires-Dist: logfire[fastapi]; extra == "test"
47
59
  Requires-Dist: ollama; extra == "test"
48
- Requires-Dist: tokenizers; extra == "test"
49
- Requires-Dist: Unidecode; extra == "test"
60
+ Requires-Dist: google-api-python-client; extra == "test"
61
+ Requires-Dist: pymupdf; extra == "test"
62
+ Requires-Dist: httpx_retries; extra == "test"
63
+ Requires-Dist: flet-video; extra == "test"
64
+ Requires-Dist: deepmerge; extra == "test"
50
65
  Requires-Dist: google-auth-httplib2; extra == "test"
51
- Requires-Dist: flet[all]; extra == "test"
52
- Requires-Dist: torchaudio; extra == "test"
53
- Requires-Dist: pyyaml; extra == "test"
54
- Requires-Dist: pydantic-settings; extra == "test"
55
- Requires-Dist: yamlscript; extra == "test"
56
- Requires-Dist: fastapi; extra == "test"
57
- Requires-Dist: pydevd-pycharm; extra == "test"
58
- Requires-Dist: pydantic; extra == "test"
59
- Requires-Dist: peft; extra == "test"
60
66
  Provides-Extra: yaml
61
67
  Requires-Dist: yamlscript; extra == "yaml"
62
68
  Requires-Dist: pyyaml; extra == "yaml"
@@ -157,6 +163,18 @@ Provides-Extra: path-app
157
163
  Requires-Dist: appdirs; extra == "path-app"
158
164
  Provides-Extra: path-type
159
165
  Requires-Dist: filetype; extra == "path-type"
166
+ Provides-Extra: dns
167
+ Requires-Dist: dnspython[doh]; extra == "dns"
168
+ Provides-Extra: patterns
169
+ Requires-Dist: regex; extra == "patterns"
170
+ Provides-Extra: http
171
+ Requires-Dist: httpx; extra == "http"
172
+ Requires-Dist: httpx_retries; extra == "http"
173
+ Requires-Dist: logfire; extra == "http"
174
+ Requires-Dist: semver; extra == "http"
175
+ Requires-Dist: logfire[httpx]; extra == "http"
176
+ Provides-Extra: setup
177
+ Requires-Dist: setuptools; extra == "setup"
160
178
  Dynamic: author
161
179
  Dynamic: author-email
162
180
  Dynamic: description
@@ -12,6 +12,7 @@ setup.py
12
12
  ./fmtr/tools/dataclass_tools.py
13
13
  ./fmtr/tools/datatype_tools.py
14
14
  ./fmtr/tools/debugging_tools.py
15
+ ./fmtr/tools/dns_tools.py
15
16
  ./fmtr/tools/docker_tools.py
16
17
  ./fmtr/tools/environment_tools.py
17
18
  ./fmtr/tools/function_tools.py
@@ -33,6 +34,7 @@ setup.py
33
34
  ./fmtr/tools/openai_tools.py
34
35
  ./fmtr/tools/packaging_tools.py
35
36
  ./fmtr/tools/parallel_tools.py
37
+ ./fmtr/tools/pattern_tools.py
36
38
  ./fmtr/tools/pdf_tools.py
37
39
  ./fmtr/tools/platform_tools.py
38
40
  ./fmtr/tools/process_tools.py
@@ -40,6 +42,7 @@ setup.py
40
42
  ./fmtr/tools/random_tools.py
41
43
  ./fmtr/tools/semantic_tools.py
42
44
  ./fmtr/tools/settings_tools.py
45
+ ./fmtr/tools/setup_tools.py
43
46
  ./fmtr/tools/spaces_tools.py
44
47
  ./fmtr/tools/string_tools.py
45
48
  ./fmtr/tools/tabular_tools.py
@@ -35,6 +35,9 @@ pydevd-pycharm
35
35
  [dm]
36
36
  pydantic
37
37
 
38
+ [dns]
39
+ dnspython[doh]
40
+
38
41
  [docker.api]
39
42
  docker
40
43
 
@@ -50,6 +53,13 @@ huggingface_hub
50
53
  [html]
51
54
  html2text
52
55
 
56
+ [http]
57
+ httpx
58
+ httpx_retries
59
+ logfire
60
+ semver
61
+ logfire[httpx]
62
+
53
63
  [interface]
54
64
  flet[all]
55
65
  flet-video
@@ -88,6 +98,9 @@ appdirs
88
98
  [path.type]
89
99
  filetype
90
100
 
101
+ [patterns]
102
+ regex
103
+
91
104
  [pdf]
92
105
  pymupdf
93
106
  pydantic
@@ -110,6 +123,9 @@ openpyxl
110
123
  pydantic-settings
111
124
  pydantic
112
125
 
126
+ [setup]
127
+ setuptools
128
+
113
129
  [spaces]
114
130
  tinynetrc
115
131
 
@@ -119,54 +135,60 @@ tabulate
119
135
  openpyxl
120
136
 
121
137
  [test]
122
- google-auth-oauthlib
123
- logfire
124
- torchvision
125
- uvicorn[standard]
126
- tinynetrc
127
- google-auth
138
+ fastapi
139
+ tabulate
140
+ torchaudio
141
+ semver
142
+ distributed
143
+ filetype
144
+ pandas
145
+ dnspython[doh]
146
+ sentence_transformers
147
+ logfire[httpx]
148
+ transformers[sentencepiece]
128
149
  openai
129
- bokeh
130
- appdirs
150
+ regex
151
+ contexttimer
152
+ uvicorn[standard]
131
153
  flet-webview
154
+ pyyaml
155
+ tinynetrc
132
156
  pytest-cov
133
- google-api-python-client
134
- diskcache
135
- openpyxl
157
+ tokenizers
158
+ logfire[fastapi]
159
+ json_repair
160
+ faker
136
161
  huggingface_hub
137
- semver
138
- dask[bag]
139
- sentence_transformers
140
- contexttimer
162
+ flet[all]
163
+ torchvision
164
+ httpx
165
+ pydantic-settings
166
+ google-auth-oauthlib
167
+ setuptools
141
168
  pydantic-ai[logfire,openai]
142
- flet-video
143
- tabulate
144
- faker
145
- deepmerge
169
+ logfire
170
+ dask[bag]
171
+ html2text
172
+ yamlscript
173
+ pymupdf4llm
174
+ peft
175
+ bokeh
176
+ Unidecode
177
+ google-auth
178
+ appdirs
179
+ openpyxl
180
+ diskcache
181
+ pydantic
182
+ pydevd-pycharm
146
183
  sre_yield
147
184
  docker
148
- pandas
149
- pymupdf4llm
150
- json_repair
151
- distributed
152
- pymupdf
153
- html2text
154
- transformers[sentencepiece]
155
- filetype
156
- logfire[fastapi]
157
185
  ollama
158
- tokenizers
159
- Unidecode
186
+ google-api-python-client
187
+ pymupdf
188
+ httpx_retries
189
+ flet-video
190
+ deepmerge
160
191
  google-auth-httplib2
161
- flet[all]
162
- torchaudio
163
- pyyaml
164
- pydantic-settings
165
- yamlscript
166
- fastapi
167
- pydevd-pycharm
168
- pydantic
169
- peft
170
192
 
171
193
  [tokenization]
172
194
  tokenizers
@@ -1 +0,0 @@
1
- 1.1.15
File without changes
File without changes
File without changes
File without changes