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