fmtr.tools 1.3.62__tar.gz → 1.3.64__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 (101) hide show
  1. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/PKG-INFO +65 -51
  2. fmtr_tools-1.3.64/fmtr/tools/data_modelling_tools.py +218 -0
  3. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/datatype_tools.py +14 -1
  4. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/iterator_tools.py +10 -1
  5. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/string_tools.py +21 -5
  6. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/tools.py +27 -2
  7. fmtr_tools-1.3.64/fmtr/tools/version +1 -0
  8. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr.tools.egg-info/PKG-INFO +65 -51
  9. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr.tools.egg-info/requires.txt +63 -50
  10. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/setup.py +4 -1
  11. fmtr_tools-1.3.62/fmtr/tools/data_modelling_tools.py +0 -93
  12. fmtr_tools-1.3.62/fmtr/tools/version +0 -1
  13. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/LICENSE +0 -0
  14. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/README.md +0 -0
  15. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/__init__.py +0 -0
  16. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/ai_tools/__init__.py +0 -0
  17. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/ai_tools/agentic_tools.py +0 -0
  18. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/ai_tools/inference_tools.py +0 -0
  19. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/api_tools.py +0 -0
  20. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/async_tools.py +0 -0
  21. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/augmentation_tools.py +0 -0
  22. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/caching_tools.py +0 -0
  23. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/constants.py +0 -0
  24. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/context_tools.py +0 -0
  25. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/database_tools/__init__.py +0 -0
  26. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/database_tools/document.py +0 -0
  27. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/dataclass_tools.py +0 -0
  28. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/datetime_tools.py +0 -0
  29. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/debugging_tools.py +0 -0
  30. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/dns_tools/__init__.py +0 -0
  31. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/dns_tools/client.py +0 -0
  32. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/dns_tools/dm.py +0 -0
  33. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/dns_tools/proxy.py +0 -0
  34. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/dns_tools/server.py +0 -0
  35. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/docker_tools/__init__.py +0 -0
  36. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/entrypoints/__init__.py +0 -0
  37. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/entrypoints/cache_hfh.py +0 -0
  38. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/entrypoints/ep_test.py +0 -0
  39. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/entrypoints/install_yamlscript.py +0 -0
  40. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/entrypoints/remote_debug_test.py +0 -0
  41. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/entrypoints/shell_debug.py +0 -0
  42. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/environment_tools.py +0 -0
  43. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/function_tools.py +0 -0
  44. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/google_api_tools.py +0 -0
  45. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/hash_tools.py +0 -0
  46. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/hfh_tools.py +0 -0
  47. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/html_tools.py +0 -0
  48. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/http_tools.py +0 -0
  49. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/import_tools.py +0 -0
  50. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/inherit_tools.py +0 -0
  51. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/inspection_tools.py +0 -0
  52. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/interface_tools/__init__.py +0 -0
  53. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/interface_tools/context.py +0 -0
  54. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/interface_tools/controls.py +0 -0
  55. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/interface_tools/interface_tools.py +0 -0
  56. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/json_fix_tools.py +0 -0
  57. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/json_tools.py +0 -0
  58. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/logging_tools.py +0 -0
  59. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/merging_tools.py +0 -0
  60. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/metric_tools.py +0 -0
  61. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/name_tools.py +0 -0
  62. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/netrc_tools.py +0 -0
  63. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/openai_tools.py +0 -0
  64. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/packaging_tools.py +0 -0
  65. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/parallel_tools.py +0 -0
  66. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/path_tools/__init__.py +0 -0
  67. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/path_tools/app_path_tools.py +0 -0
  68. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/path_tools/path_tools.py +0 -0
  69. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/path_tools/type_path_tools.py +0 -0
  70. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/pattern_tools.py +0 -0
  71. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/pdf_tools.py +0 -0
  72. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/platform_tools.py +0 -0
  73. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/process_tools.py +0 -0
  74. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/profiling_tools.py +0 -0
  75. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/random_tools.py +0 -0
  76. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/semantic_tools.py +0 -0
  77. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/settings_tools.py +0 -0
  78. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/setup_tools/__init__.py +0 -0
  79. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/setup_tools/setup_tools.py +0 -0
  80. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/spaces_tools.py +0 -0
  81. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/tabular_tools.py +0 -0
  82. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/tests/__init__.py +0 -0
  83. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/tests/conftest.py +0 -0
  84. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/tests/helpers.py +0 -0
  85. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/tests/test_datatype.py +0 -0
  86. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/tests/test_environment.py +0 -0
  87. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/tests/test_json.py +0 -0
  88. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/tests/test_path.py +0 -0
  89. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/tests/test_yaml.py +0 -0
  90. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/tokenization_tools.py +0 -0
  91. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/unicode_tools.py +0 -0
  92. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/version_tools/__init__.py +0 -0
  93. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/version_tools/version_tools.py +0 -0
  94. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/webhook_tools.py +0 -0
  95. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr/tools/yaml_tools.py +0 -0
  96. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr.tools.egg-info/SOURCES.txt +0 -0
  97. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr.tools.egg-info/dependency_links.txt +0 -0
  98. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr.tools.egg-info/entry_points.txt +0 -0
  99. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/fmtr.tools.egg-info/top_level.txt +0 -0
  100. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/pyproject.toml +0 -0
  101. {fmtr_tools-1.3.62 → fmtr_tools-1.3.64}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmtr.tools
3
- Version: 1.3.62
3
+ Version: 1.3.64
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
@@ -8,6 +8,20 @@ Author-email: innovative.fowler@mask.pro.fmtr.dev
8
8
  License: Copyright © 2025 Frontmatter. All rights reserved.
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
+ Provides-Extra: dev
12
+ Requires-Dist: logfire; extra == "dev"
13
+ Requires-Dist: semver; extra == "dev"
14
+ Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "dev"
15
+ Requires-Dist: pydantic-settings; extra == "dev"
16
+ Requires-Dist: pydantic; extra == "dev"
17
+ Requires-Dist: pydantic-extra-types; extra == "dev"
18
+ Requires-Dist: pycountry; extra == "dev"
19
+ Requires-Dist: yamlscript; extra == "dev"
20
+ Requires-Dist: pyyaml; extra == "dev"
21
+ Requires-Dist: yamlscript; extra == "dev"
22
+ Requires-Dist: pyyaml; extra == "dev"
23
+ Requires-Dist: beanie[odm]; extra == "dev"
24
+ Requires-Dist: motor; extra == "dev"
11
25
  Provides-Extra: test
12
26
  Requires-Dist: pytest-cov; extra == "test"
13
27
  Provides-Extra: yaml
@@ -154,68 +168,68 @@ Provides-Extra: db-document
154
168
  Requires-Dist: beanie[odm]; extra == "db-document"
155
169
  Requires-Dist: motor; extra == "db-document"
156
170
  Provides-Extra: all
157
- Requires-Dist: setuptools; extra == "all"
158
- Requires-Dist: google-auth; extra == "all"
159
- Requires-Dist: google-auth-oauthlib; extra == "all"
160
- Requires-Dist: cachetools; extra == "all"
161
- Requires-Dist: uvicorn[standard]; extra == "all"
162
- Requires-Dist: pydantic; extra == "all"
163
- Requires-Dist: openai; extra == "all"
164
- Requires-Dist: ollama; extra == "all"
165
- Requires-Dist: json_repair; extra == "all"
166
- Requires-Dist: sentence_transformers; extra == "all"
167
- Requires-Dist: tabulate; extra == "all"
168
- Requires-Dist: sre_yield; extra == "all"
171
+ Requires-Dist: deepmerge; extra == "all"
172
+ Requires-Dist: faker; extra == "all"
173
+ Requires-Dist: pandas; extra == "all"
169
174
  Requires-Dist: google-api-python-client; extra == "all"
175
+ Requires-Dist: html2text; extra == "all"
170
176
  Requires-Dist: pyyaml; extra == "all"
171
- Requires-Dist: regex; extra == "all"
172
- Requires-Dist: httpx_retries; extra == "all"
173
- Requires-Dist: flet-video; extra == "all"
174
- Requires-Dist: pandas; extra == "all"
175
- Requires-Dist: openpyxl; extra == "all"
176
- Requires-Dist: google-auth-httplib2; extra == "all"
177
- Requires-Dist: semver; extra == "all"
178
- Requires-Dist: yamlscript; extra == "all"
179
177
  Requires-Dist: dask[bag]; extra == "all"
180
- Requires-Dist: peft; extra == "all"
181
- Requires-Dist: html2text; extra == "all"
182
- Requires-Dist: transformers[sentencepiece]; extra == "all"
183
- Requires-Dist: tinynetrc; extra == "all"
184
- Requires-Dist: flet[all]; extra == "all"
185
- Requires-Dist: appdirs; extra == "all"
186
- Requires-Dist: deepmerge; extra == "all"
187
- Requires-Dist: httpx; extra == "all"
188
- Requires-Dist: huggingface_hub; extra == "all"
178
+ Requires-Dist: google-auth-httplib2; extra == "all"
179
+ Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
189
180
  Requires-Dist: pymupdf; extra == "all"
190
- Requires-Dist: pytest-cov; extra == "all"
191
181
  Requires-Dist: beanie[odm]; extra == "all"
192
- Requires-Dist: logfire[httpx]; extra == "all"
193
- Requires-Dist: deepdiff; extra == "all"
194
- Requires-Dist: bokeh; extra == "all"
195
- Requires-Dist: pycountry; extra == "all"
196
- Requires-Dist: dnspython[doh]; extra == "all"
197
- Requires-Dist: faker; extra == "all"
198
- Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
199
- Requires-Dist: odfpy; extra == "all"
200
- Requires-Dist: pydantic-settings; extra == "all"
182
+ Requires-Dist: fastapi; extra == "all"
183
+ Requires-Dist: uvicorn[standard]; extra == "all"
184
+ Requires-Dist: appdirs; extra == "all"
201
185
  Requires-Dist: pydantic-extra-types; extra == "all"
186
+ Requires-Dist: distributed; extra == "all"
202
187
  Requires-Dist: torchaudio; extra == "all"
203
- Requires-Dist: motor; extra == "all"
204
- Requires-Dist: torchvision; extra == "all"
205
- Requires-Dist: filetype; extra == "all"
188
+ Requires-Dist: setuptools; extra == "all"
189
+ Requires-Dist: deepdiff; extra == "all"
190
+ Requires-Dist: odfpy; extra == "all"
206
191
  Requires-Dist: python-on-whales; extra == "all"
207
- Requires-Dist: pymupdf4llm; extra == "all"
208
- Requires-Dist: logfire; extra == "all"
209
- Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
210
- Requires-Dist: logfire[fastapi]; extra == "all"
192
+ Requires-Dist: Unidecode; extra == "all"
211
193
  Requires-Dist: contexttimer; extra == "all"
212
- Requires-Dist: fastapi; extra == "all"
194
+ Requires-Dist: tokenizers; extra == "all"
195
+ Requires-Dist: pycountry; extra == "all"
196
+ Requires-Dist: openai; extra == "all"
213
197
  Requires-Dist: flet-webview; extra == "all"
198
+ Requires-Dist: logfire[httpx]; extra == "all"
199
+ Requires-Dist: regex; extra == "all"
200
+ Requires-Dist: peft; extra == "all"
201
+ Requires-Dist: logfire; extra == "all"
202
+ Requires-Dist: pytest-cov; extra == "all"
214
203
  Requires-Dist: playwright; extra == "all"
215
- Requires-Dist: Unidecode; extra == "all"
216
- Requires-Dist: distributed; extra == "all"
204
+ Requires-Dist: httpx; extra == "all"
205
+ Requires-Dist: logfire[fastapi]; extra == "all"
206
+ Requires-Dist: pydantic-settings; extra == "all"
207
+ Requires-Dist: sentence_transformers; extra == "all"
208
+ Requires-Dist: filetype; extra == "all"
209
+ Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
210
+ Requires-Dist: openpyxl; extra == "all"
211
+ Requires-Dist: ollama; extra == "all"
212
+ Requires-Dist: httpx_retries; extra == "all"
213
+ Requires-Dist: tinynetrc; extra == "all"
214
+ Requires-Dist: flet-video; extra == "all"
215
+ Requires-Dist: pymupdf4llm; extra == "all"
216
+ Requires-Dist: bokeh; extra == "all"
217
217
  Requires-Dist: diskcache; extra == "all"
218
- Requires-Dist: tokenizers; extra == "all"
218
+ Requires-Dist: semver; extra == "all"
219
+ Requires-Dist: huggingface_hub; extra == "all"
220
+ Requires-Dist: flet[all]; extra == "all"
221
+ Requires-Dist: google-auth; extra == "all"
222
+ Requires-Dist: tabulate; extra == "all"
223
+ Requires-Dist: torchvision; extra == "all"
224
+ Requires-Dist: yamlscript; extra == "all"
225
+ Requires-Dist: sre_yield; extra == "all"
226
+ Requires-Dist: json_repair; extra == "all"
227
+ Requires-Dist: transformers[sentencepiece]; extra == "all"
228
+ Requires-Dist: pydantic; extra == "all"
229
+ Requires-Dist: cachetools; extra == "all"
230
+ Requires-Dist: google-auth-oauthlib; extra == "all"
231
+ Requires-Dist: dnspython[doh]; extra == "all"
232
+ Requires-Dist: motor; extra == "all"
219
233
  Dynamic: author
220
234
  Dynamic: author-email
221
235
  Dynamic: description
@@ -0,0 +1,218 @@
1
+ from functools import cached_property
2
+ from typing import ClassVar, List, Any, Dict
3
+
4
+ from pydantic import BaseModel
5
+ from pydantic import RootModel, ConfigDict
6
+ from pydantic.fields import FieldInfo
7
+ from pydantic_core import PydanticUndefined, PydanticUndefinedType
8
+
9
+ from fmtr.tools.datatype_tools import is_optional
10
+ from fmtr.tools.iterator_tools import get_class_lookup
11
+ from fmtr.tools.tools import Auto, Required
12
+
13
+
14
+ class Field(FieldInfo):
15
+ """
16
+
17
+ Allow DRYer field definitions, set annotation and defaults at the same time, easier field inheritance, etc.
18
+
19
+ """
20
+ ANNOTATION = None
21
+ DEFAULT = Auto
22
+ FILLS = None
23
+ DESCRIPTION = None
24
+ TITLE = Auto
25
+ KWARGS = None
26
+
27
+ def __init__(self):
28
+ """
29
+
30
+ Infer default from type annotation, if enabled, use class/argument fills to create titles/descriptions, etc.
31
+
32
+ """
33
+ title = self.get_title_auto()
34
+ description = self.get_desc()
35
+ default = self.get_default_auto()
36
+ kwargs = self.KWARGS or {}
37
+
38
+ if default is Required:
39
+ default = PydanticUndefined
40
+
41
+ super().__init__(default=default, title=title, description=description, **kwargs)
42
+
43
+ @cached_property
44
+ def fills(self) -> Dict[str, str]:
45
+ """
46
+
47
+ Get fills with filled title merged in
48
+
49
+ """
50
+ return (self.FILLS or {}) | dict(title=self.get_title_auto())
51
+
52
+ def get_default_auto(self) -> type[Any] | None | PydanticUndefinedType:
53
+ """
54
+
55
+ Infer default, if not specified.
56
+
57
+ """
58
+ if self.DEFAULT is not Auto:
59
+ return self.DEFAULT
60
+
61
+ if is_optional(self.ANNOTATION):
62
+ return None
63
+ else:
64
+ return Required
65
+
66
+ def get_title_auto(self) -> str | None:
67
+ """
68
+
69
+ Get title from classname/mask
70
+
71
+ """
72
+
73
+ mask = self.__class__.__name__ if self.TITLE is Auto else self.TITLE
74
+ fills = (self.FILLS or {})
75
+
76
+ if mask:
77
+ return mask.format(**fills)
78
+
79
+ return None
80
+
81
+ def get_desc(self) -> str | None:
82
+ """
83
+
84
+ Get description from classname/mask
85
+
86
+ """
87
+
88
+ if self.DESCRIPTION:
89
+ return self.DESCRIPTION.format(**self.fills)
90
+
91
+ return None
92
+
93
+
94
+ def to_df(*objs, name_value='value'):
95
+ """
96
+
97
+ DataFrame representation of Data Models as rows.
98
+
99
+ """
100
+ from fmtr.tools import tabular
101
+
102
+ rows = []
103
+ for obj in objs:
104
+ if isinstance(obj, BaseModel):
105
+ row = obj.model_dump()
106
+ else:
107
+ row = {name_value: obj}
108
+ rows.append(row)
109
+
110
+ df = tabular.DataFrame(rows)
111
+ if 'id' in df.columns:
112
+ df.set_index('id', inplace=True, drop=True)
113
+ return df
114
+
115
+
116
+ class MixinArbitraryTypes:
117
+ """
118
+
119
+ Convenience for when non-serializable types are needed
120
+ """
121
+
122
+ model_config = ConfigDict(arbitrary_types_allowed=True)
123
+
124
+ class MixinFromJson:
125
+
126
+ @classmethod
127
+ def from_json(cls, json_str):
128
+ """
129
+
130
+ Error-tolerant deserialization
131
+
132
+ """
133
+ from fmtr.tools import json_fix
134
+ data = json_fix.from_json(json_str, default={})
135
+
136
+ if type(data) is dict:
137
+ self = cls(**data)
138
+ else:
139
+ self = cls(data)
140
+
141
+ return self
142
+
143
+
144
+
145
+ class Base(BaseModel, MixinFromJson):
146
+ """
147
+
148
+ Base model allowing model definition via a list of custom Field objects.
149
+
150
+ """
151
+ FIELDS: ClassVar[List[Field] | Dict[str, Field]] = []
152
+
153
+ def __init_subclass__(cls, **kwargs):
154
+ """
155
+
156
+ Fetch aggregated fields metadata from the hierarchy and set annotations and FieldInfo objects in the class.
157
+
158
+ """
159
+ super().__init_subclass__(**kwargs)
160
+
161
+ fields = {}
162
+ for base in reversed(cls.__mro__):
163
+
164
+ try:
165
+ raw = base.FIELDS
166
+ except AttributeError:
167
+ raw = {}
168
+
169
+ if isinstance(raw, dict):
170
+ fields |= raw
171
+ else:
172
+ fields |= get_class_lookup(*raw, name_function=str.lower)
173
+
174
+ cls.FIELDS = fields
175
+
176
+ for name, FieldInfoType in fields.items():
177
+ if name in cls.__annotations__:
178
+ continue
179
+
180
+ field = FieldInfoType()
181
+ setattr(cls, name, field)
182
+
183
+ annotation = FieldInfoType.ANNOTATION
184
+ cls.__annotations__[name] = annotation
185
+
186
+ def to_df(self, name_value='value'):
187
+ """
188
+
189
+ DataFrame representation with Fields as rows.
190
+
191
+ """
192
+
193
+ objs = []
194
+ for name in self.model_fields.keys():
195
+ val = getattr(self, name)
196
+ objs.append(val)
197
+
198
+ df = to_df(*objs, name_value=name_value)
199
+ df['id'] = list(self.model_fields.keys())
200
+ df = df.set_index('id', drop=True)
201
+ return df
202
+
203
+
204
+ class Root(RootModel, MixinFromJson):
205
+ """
206
+
207
+ Root (list) model
208
+
209
+ """
210
+
211
+ def to_df(self):
212
+ """
213
+
214
+ DataFrame representation with items as rows.
215
+
216
+ """
217
+
218
+ return to_df(*self.items)
@@ -1,4 +1,5 @@
1
- from typing import Any
1
+ from types import UnionType, NoneType
2
+ from typing import Any, get_origin, get_args
2
3
 
3
4
  from fmtr.tools.tools import Raise
4
5
 
@@ -84,3 +85,15 @@ def none_else(value: Any, default: Any) -> Any:
84
85
  if is_none(value):
85
86
  return default
86
87
  return value
88
+
89
+
90
+ def is_optional(annotation) -> bool:
91
+ """
92
+
93
+ Is type/annotation optional? todo should be in typing_tools?
94
+
95
+ """
96
+ origin = get_origin(annotation)
97
+ args = get_args(annotation)
98
+ is_opt = origin is UnionType and NoneType in args
99
+ return is_opt
@@ -1,8 +1,8 @@
1
1
  from itertools import chain, batched
2
-
3
2
  from typing import List, Dict, Any
4
3
 
5
4
  from fmtr.tools.datatype_tools import is_none
5
+ from fmtr.tools.tools import identity
6
6
 
7
7
 
8
8
  def enlist(value) -> List[Any]:
@@ -73,3 +73,12 @@ def dedupe(items):
73
73
 
74
74
  """
75
75
  return list(dict.fromkeys(items))
76
+
77
+
78
+ def get_class_lookup(*classes, name_function=identity):
79
+ """
80
+
81
+ Dictionary of class names to classes
82
+
83
+ """
84
+ return {name_function(cls.__name__): cls for cls in classes}
@@ -1,8 +1,7 @@
1
- from collections import namedtuple
2
- from string import Formatter
3
-
4
1
  import re
2
+ from collections import namedtuple
5
3
  from dataclasses import dataclass
4
+ from string import Formatter
6
5
  from textwrap import dedent
7
6
  from typing import List
8
7
 
@@ -151,14 +150,14 @@ def truncate_mid(text, length=None, sep=ELLIPSIS, return_type=str):
151
150
  )
152
151
 
153
152
 
154
- def flatten(raw):
153
+ def flatten(raw, sep=' '):
155
154
  """
156
155
 
157
156
  Flatten a multiline string to a single line
158
157
 
159
158
  """
160
159
  lines = raw.splitlines()
161
- text = ' '.join(lines)
160
+ text = sep.join(lines)
162
161
  text = text.strip()
163
162
  return text
164
163
 
@@ -175,6 +174,23 @@ def join(strings, sep=' '):
175
174
  return text
176
175
 
177
176
 
177
+ def join_natural(items, sep=', ', conj='and'):
178
+ """
179
+
180
+ Natural language list
181
+
182
+ """
183
+
184
+ items = list(items)
185
+ if not items:
186
+ return ""
187
+ if len(items) == 1:
188
+ return items[0]
189
+ firsts, last = items[:-1], items[-1]
190
+ firsts_str = join(firsts, sep=sep)
191
+ text = f"{firsts_str} {conj} {last}"
192
+ return text
193
+
178
194
  class Mask:
179
195
  """
180
196
 
@@ -27,7 +27,15 @@ def identity(x: Any) -> Any:
27
27
  return x
28
28
 
29
29
 
30
- class Empty:
30
+ class Special:
31
+ """
32
+
33
+ Classes to differentiate special arguments from primitive arguments.
34
+
35
+ """
36
+
37
+
38
+ class Empty(Special):
31
39
  """
32
40
 
33
41
  Class to denote an unspecified object (e.g. argument) when `None` cannot be used.
@@ -35,7 +43,7 @@ class Empty:
35
43
  """
36
44
 
37
45
 
38
- class Raise:
46
+ class Raise(Special):
39
47
  """
40
48
 
41
49
  Class to denote when a function should raise instead of e.g. returning a default.
@@ -43,4 +51,21 @@ class Raise:
43
51
  """
44
52
 
45
53
 
54
+ class Auto(Special):
55
+ """
56
+
57
+ Class to denote when an argument should be inferred.
58
+
59
+ """
60
+
61
+
62
+ class Required(Special):
63
+ """
64
+
65
+ Class to denote when an argument is required.
66
+
67
+ """
68
+
69
+
70
+
46
71
  EMPTY = Empty()
@@ -0,0 +1 @@
1
+ 1.3.64
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fmtr.tools
3
- Version: 1.3.62
3
+ Version: 1.3.64
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
@@ -8,6 +8,20 @@ Author-email: innovative.fowler@mask.pro.fmtr.dev
8
8
  License: Copyright © 2025 Frontmatter. All rights reserved.
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
+ Provides-Extra: dev
12
+ Requires-Dist: logfire; extra == "dev"
13
+ Requires-Dist: semver; extra == "dev"
14
+ Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "dev"
15
+ Requires-Dist: pydantic-settings; extra == "dev"
16
+ Requires-Dist: pydantic; extra == "dev"
17
+ Requires-Dist: pydantic-extra-types; extra == "dev"
18
+ Requires-Dist: pycountry; extra == "dev"
19
+ Requires-Dist: yamlscript; extra == "dev"
20
+ Requires-Dist: pyyaml; extra == "dev"
21
+ Requires-Dist: yamlscript; extra == "dev"
22
+ Requires-Dist: pyyaml; extra == "dev"
23
+ Requires-Dist: beanie[odm]; extra == "dev"
24
+ Requires-Dist: motor; extra == "dev"
11
25
  Provides-Extra: test
12
26
  Requires-Dist: pytest-cov; extra == "test"
13
27
  Provides-Extra: yaml
@@ -154,68 +168,68 @@ Provides-Extra: db-document
154
168
  Requires-Dist: beanie[odm]; extra == "db-document"
155
169
  Requires-Dist: motor; extra == "db-document"
156
170
  Provides-Extra: all
157
- Requires-Dist: setuptools; extra == "all"
158
- Requires-Dist: google-auth; extra == "all"
159
- Requires-Dist: google-auth-oauthlib; extra == "all"
160
- Requires-Dist: cachetools; extra == "all"
161
- Requires-Dist: uvicorn[standard]; extra == "all"
162
- Requires-Dist: pydantic; extra == "all"
163
- Requires-Dist: openai; extra == "all"
164
- Requires-Dist: ollama; extra == "all"
165
- Requires-Dist: json_repair; extra == "all"
166
- Requires-Dist: sentence_transformers; extra == "all"
167
- Requires-Dist: tabulate; extra == "all"
168
- Requires-Dist: sre_yield; extra == "all"
171
+ Requires-Dist: deepmerge; extra == "all"
172
+ Requires-Dist: faker; extra == "all"
173
+ Requires-Dist: pandas; extra == "all"
169
174
  Requires-Dist: google-api-python-client; extra == "all"
175
+ Requires-Dist: html2text; extra == "all"
170
176
  Requires-Dist: pyyaml; extra == "all"
171
- Requires-Dist: regex; extra == "all"
172
- Requires-Dist: httpx_retries; extra == "all"
173
- Requires-Dist: flet-video; extra == "all"
174
- Requires-Dist: pandas; extra == "all"
175
- Requires-Dist: openpyxl; extra == "all"
176
- Requires-Dist: google-auth-httplib2; extra == "all"
177
- Requires-Dist: semver; extra == "all"
178
- Requires-Dist: yamlscript; extra == "all"
179
177
  Requires-Dist: dask[bag]; extra == "all"
180
- Requires-Dist: peft; extra == "all"
181
- Requires-Dist: html2text; extra == "all"
182
- Requires-Dist: transformers[sentencepiece]; extra == "all"
183
- Requires-Dist: tinynetrc; extra == "all"
184
- Requires-Dist: flet[all]; extra == "all"
185
- Requires-Dist: appdirs; extra == "all"
186
- Requires-Dist: deepmerge; extra == "all"
187
- Requires-Dist: httpx; extra == "all"
188
- Requires-Dist: huggingface_hub; extra == "all"
178
+ Requires-Dist: google-auth-httplib2; extra == "all"
179
+ Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
189
180
  Requires-Dist: pymupdf; extra == "all"
190
- Requires-Dist: pytest-cov; extra == "all"
191
181
  Requires-Dist: beanie[odm]; extra == "all"
192
- Requires-Dist: logfire[httpx]; extra == "all"
193
- Requires-Dist: deepdiff; extra == "all"
194
- Requires-Dist: bokeh; extra == "all"
195
- Requires-Dist: pycountry; extra == "all"
196
- Requires-Dist: dnspython[doh]; extra == "all"
197
- Requires-Dist: faker; extra == "all"
198
- Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
199
- Requires-Dist: odfpy; extra == "all"
200
- Requires-Dist: pydantic-settings; extra == "all"
182
+ Requires-Dist: fastapi; extra == "all"
183
+ Requires-Dist: uvicorn[standard]; extra == "all"
184
+ Requires-Dist: appdirs; extra == "all"
201
185
  Requires-Dist: pydantic-extra-types; extra == "all"
186
+ Requires-Dist: distributed; extra == "all"
202
187
  Requires-Dist: torchaudio; extra == "all"
203
- Requires-Dist: motor; extra == "all"
204
- Requires-Dist: torchvision; extra == "all"
205
- Requires-Dist: filetype; extra == "all"
188
+ Requires-Dist: setuptools; extra == "all"
189
+ Requires-Dist: deepdiff; extra == "all"
190
+ Requires-Dist: odfpy; extra == "all"
206
191
  Requires-Dist: python-on-whales; extra == "all"
207
- Requires-Dist: pymupdf4llm; extra == "all"
208
- Requires-Dist: logfire; extra == "all"
209
- Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
210
- Requires-Dist: logfire[fastapi]; extra == "all"
192
+ Requires-Dist: Unidecode; extra == "all"
211
193
  Requires-Dist: contexttimer; extra == "all"
212
- Requires-Dist: fastapi; extra == "all"
194
+ Requires-Dist: tokenizers; extra == "all"
195
+ Requires-Dist: pycountry; extra == "all"
196
+ Requires-Dist: openai; extra == "all"
213
197
  Requires-Dist: flet-webview; extra == "all"
198
+ Requires-Dist: logfire[httpx]; extra == "all"
199
+ Requires-Dist: regex; extra == "all"
200
+ Requires-Dist: peft; extra == "all"
201
+ Requires-Dist: logfire; extra == "all"
202
+ Requires-Dist: pytest-cov; extra == "all"
214
203
  Requires-Dist: playwright; extra == "all"
215
- Requires-Dist: Unidecode; extra == "all"
216
- Requires-Dist: distributed; extra == "all"
204
+ Requires-Dist: httpx; extra == "all"
205
+ Requires-Dist: logfire[fastapi]; extra == "all"
206
+ Requires-Dist: pydantic-settings; extra == "all"
207
+ Requires-Dist: sentence_transformers; extra == "all"
208
+ Requires-Dist: filetype; extra == "all"
209
+ Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
210
+ Requires-Dist: openpyxl; extra == "all"
211
+ Requires-Dist: ollama; extra == "all"
212
+ Requires-Dist: httpx_retries; extra == "all"
213
+ Requires-Dist: tinynetrc; extra == "all"
214
+ Requires-Dist: flet-video; extra == "all"
215
+ Requires-Dist: pymupdf4llm; extra == "all"
216
+ Requires-Dist: bokeh; extra == "all"
217
217
  Requires-Dist: diskcache; extra == "all"
218
- Requires-Dist: tokenizers; extra == "all"
218
+ Requires-Dist: semver; extra == "all"
219
+ Requires-Dist: huggingface_hub; extra == "all"
220
+ Requires-Dist: flet[all]; extra == "all"
221
+ Requires-Dist: google-auth; extra == "all"
222
+ Requires-Dist: tabulate; extra == "all"
223
+ Requires-Dist: torchvision; extra == "all"
224
+ Requires-Dist: yamlscript; extra == "all"
225
+ Requires-Dist: sre_yield; extra == "all"
226
+ Requires-Dist: json_repair; extra == "all"
227
+ Requires-Dist: transformers[sentencepiece]; extra == "all"
228
+ Requires-Dist: pydantic; extra == "all"
229
+ Requires-Dist: cachetools; extra == "all"
230
+ Requires-Dist: google-auth-oauthlib; extra == "all"
231
+ Requires-Dist: dnspython[doh]; extra == "all"
232
+ Requires-Dist: motor; extra == "all"
219
233
  Dynamic: author
220
234
  Dynamic: author-email
221
235
  Dynamic: description
@@ -18,68 +18,68 @@ pydantic-ai[logfire,openai]
18
18
  ollama
19
19
 
20
20
  [all]
21
- setuptools
22
- google-auth
23
- google-auth-oauthlib
24
- cachetools
25
- uvicorn[standard]
26
- pydantic
27
- openai
28
- ollama
29
- json_repair
30
- sentence_transformers
31
- tabulate
32
- sre_yield
21
+ deepmerge
22
+ faker
23
+ pandas
33
24
  google-api-python-client
25
+ html2text
34
26
  pyyaml
35
- regex
36
- httpx_retries
37
- flet-video
38
- pandas
39
- openpyxl
40
- google-auth-httplib2
41
- semver
42
- yamlscript
43
27
  dask[bag]
44
- peft
45
- html2text
46
- transformers[sentencepiece]
47
- tinynetrc
48
- flet[all]
49
- appdirs
50
- deepmerge
51
- httpx
52
- huggingface_hub
28
+ google-auth-httplib2
29
+ pydevd-pycharm~=251.25410.159
53
30
  pymupdf
54
- pytest-cov
55
31
  beanie[odm]
56
- logfire[httpx]
57
- deepdiff
58
- bokeh
59
- pycountry
60
- dnspython[doh]
61
- faker
62
- pydantic-ai[logfire,openai]
63
- odfpy
64
- pydantic-settings
32
+ fastapi
33
+ uvicorn[standard]
34
+ appdirs
65
35
  pydantic-extra-types
36
+ distributed
66
37
  torchaudio
67
- motor
68
- torchvision
69
- filetype
38
+ setuptools
39
+ deepdiff
40
+ odfpy
70
41
  python-on-whales
71
- pymupdf4llm
72
- logfire
73
- pydevd-pycharm~=251.25410.159
74
- logfire[fastapi]
42
+ Unidecode
75
43
  contexttimer
76
- fastapi
44
+ tokenizers
45
+ pycountry
46
+ openai
77
47
  flet-webview
48
+ logfire[httpx]
49
+ regex
50
+ peft
51
+ logfire
52
+ pytest-cov
78
53
  playwright
79
- Unidecode
80
- distributed
54
+ httpx
55
+ logfire[fastapi]
56
+ pydantic-settings
57
+ sentence_transformers
58
+ filetype
59
+ pydantic-ai[logfire,openai]
60
+ openpyxl
61
+ ollama
62
+ httpx_retries
63
+ tinynetrc
64
+ flet-video
65
+ pymupdf4llm
66
+ bokeh
81
67
  diskcache
82
- tokenizers
68
+ semver
69
+ huggingface_hub
70
+ flet[all]
71
+ google-auth
72
+ tabulate
73
+ torchvision
74
+ yamlscript
75
+ sre_yield
76
+ json_repair
77
+ transformers[sentencepiece]
78
+ pydantic
79
+ cachetools
80
+ google-auth-oauthlib
81
+ dnspython[doh]
82
+ motor
83
83
 
84
84
  [api]
85
85
  fastapi
@@ -110,6 +110,19 @@ motor
110
110
  [debug]
111
111
  pydevd-pycharm~=251.25410.159
112
112
 
113
+ [dev]
114
+ logfire
115
+ semver
116
+ pydevd-pycharm~=251.25410.159
117
+ pydantic-settings
118
+ pydantic
119
+ pydantic-extra-types
120
+ pycountry
121
+ yamlscript
122
+ pyyaml
123
+ beanie[odm]
124
+ motor
125
+
113
126
  [dm]
114
127
  pydantic
115
128
  pydantic-extra-types
@@ -1,6 +1,8 @@
1
1
  from fmtr.tools import Setup
2
2
 
3
3
  DEPENDENCIES = {
4
+ 'dev': ['logging', 'version.dev', 'debug', 'sets', 'yaml', 'db.document'],
5
+
4
6
  'install': [],
5
7
  'test': ['pytest-cov'],
6
8
  'yaml': ['yamlscript', 'pyyaml'],
@@ -43,7 +45,8 @@ DEPENDENCIES = {
43
45
  'webhook': ['http'],
44
46
  'browsers': ['playwright'],
45
47
  'db': [],
46
- 'db.document': ['beanie[odm]', 'motor']
48
+ 'db.document': ['beanie[odm]', 'motor'],
49
+
47
50
  }
48
51
 
49
52
  setup = Setup(
@@ -1,93 +0,0 @@
1
- from pydantic import BaseModel, RootModel, ConfigDict
2
-
3
-
4
- def to_df(*objs, name_value='value'):
5
- """
6
-
7
- DataFrame representation of Data Models as rows.
8
-
9
- """
10
- from fmtr.tools import tabular
11
-
12
- rows = []
13
- for obj in objs:
14
- if isinstance(obj, BaseModel):
15
- row = obj.model_dump()
16
- else:
17
- row = {name_value: obj}
18
- rows.append(row)
19
-
20
- df = tabular.DataFrame(rows)
21
- if 'id' in df.columns:
22
- df.set_index('id', inplace=True, drop=True)
23
- return df
24
-
25
-
26
- class MixinArbitraryTypes:
27
- """
28
-
29
- Convenience for when non-serializable types are needed
30
- """
31
-
32
- model_config = ConfigDict(arbitrary_types_allowed=True)
33
-
34
- class MixinFromJson:
35
-
36
- @classmethod
37
- def from_json(cls, json_str):
38
- """
39
-
40
- Error-tolerant deserialization
41
-
42
- """
43
- from fmtr.tools import json_fix
44
- data = json_fix.from_json(json_str, default={})
45
-
46
- if type(data) is dict:
47
- self = cls(**data)
48
- else:
49
- self = cls(data)
50
-
51
- return self
52
-
53
-
54
- class Base(BaseModel, MixinFromJson):
55
- """
56
-
57
- Base model
58
-
59
- """
60
-
61
- def to_df(self, name_value='value'):
62
- """
63
-
64
- DataFrame representation with Fields as rows.
65
-
66
- """
67
-
68
- objs = []
69
- for name in self.model_fields.keys():
70
- val = getattr(self, name)
71
- objs.append(val)
72
-
73
- df = to_df(*objs, name_value=name_value)
74
- df['id'] = list(self.model_fields.keys())
75
- df = df.set_index('id', drop=True)
76
- return df
77
-
78
-
79
- class Root(RootModel, MixinFromJson):
80
- """
81
-
82
- Root (list) model
83
-
84
- """
85
-
86
- def to_df(self):
87
- """
88
-
89
- DataFrame representation with items as rows.
90
-
91
- """
92
-
93
- return to_df(*self.items)
@@ -1 +0,0 @@
1
- 1.3.62
File without changes
File without changes
File without changes
File without changes