fmtr.tools 1.3.63__py3-none-any.whl → 1.3.65__py3-none-any.whl

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

Potentially problematic release.


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

@@ -1,4 +1,110 @@
1
- from pydantic import BaseModel, RootModel, ConfigDict
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
2
108
 
3
109
 
4
110
  def to_df(*objs, name_value='value'):
@@ -51,12 +157,47 @@ class MixinFromJson:
51
157
  return self
52
158
 
53
159
 
160
+
54
161
  class Base(BaseModel, MixinFromJson):
55
162
  """
56
163
 
57
- Base model
164
+ Base model allowing model definition via a list of custom Field objects.
58
165
 
59
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
60
201
 
61
202
  def to_df(self, name_value='value'):
62
203
  """
@@ -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()
fmtr/tools/tools.py CHANGED
@@ -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()
fmtr/tools/version CHANGED
@@ -1 +1 @@
1
- 1.3.63
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
171
+ Requires-Dist: pydantic; extra == "all"
172
+ Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
157
173
  Requires-Dist: uvicorn[standard]; extra == "all"
174
+ Requires-Dist: google-auth; extra == "all"
175
+ Requires-Dist: pymupdf4llm; extra == "all"
158
176
  Requires-Dist: contexttimer; extra == "all"
159
- Requires-Dist: pymupdf; extra == "all"
160
- Requires-Dist: motor; extra == "all"
161
- Requires-Dist: huggingface_hub; extra == "all"
162
- Requires-Dist: google-auth-httplib2; extra == "all"
163
- Requires-Dist: sre_yield; extra == "all"
164
- Requires-Dist: json_repair; extra == "all"
165
- Requires-Dist: fastapi; extra == "all"
166
- Requires-Dist: tabulate; extra == "all"
167
- Requires-Dist: flet-webview; extra == "all"
168
- Requires-Dist: beanie[odm]; extra == "all"
169
- Requires-Dist: openai; extra == "all"
170
- Requires-Dist: pydantic-extra-types; extra == "all"
171
- Requires-Dist: torchaudio; extra == "all"
172
- Requires-Dist: flet-video; extra == "all"
173
- Requires-Dist: tinynetrc; extra == "all"
174
- Requires-Dist: flet[all]; extra == "all"
175
- Requires-Dist: pycountry; extra == "all"
176
- Requires-Dist: torchvision; extra == "all"
177
- Requires-Dist: deepdiff; extra == "all"
178
- Requires-Dist: pydantic; extra == "all"
177
+ Requires-Dist: google-auth-oauthlib; extra == "all"
178
+ Requires-Dist: python-on-whales; extra == "all"
179
179
  Requires-Dist: httpx_retries; extra == "all"
180
- Requires-Dist: logfire[fastapi]; extra == "all"
180
+ Requires-Dist: distributed; extra == "all"
181
+ Requires-Dist: beanie[odm]; extra == "all"
181
182
  Requires-Dist: diskcache; extra == "all"
182
- Requires-Dist: dask[bag]; extra == "all"
183
- Requires-Dist: yamlscript; extra == "all"
184
- Requires-Dist: Unidecode; extra == "all"
185
- Requires-Dist: openpyxl; extra == "all"
186
- Requires-Dist: httpx; extra == "all"
187
183
  Requires-Dist: transformers[sentencepiece]; extra == "all"
188
- Requires-Dist: html2text; extra == "all"
184
+ Requires-Dist: appdirs; extra == "all"
185
+ Requires-Dist: torchaudio; extra == "all"
186
+ Requires-Dist: filetype; extra == "all"
187
+ Requires-Dist: flet-webview; extra == "all"
188
+ Requires-Dist: motor; extra == "all"
189
+ Requires-Dist: openai; extra == "all"
190
+ Requires-Dist: ollama; extra == "all"
189
191
  Requires-Dist: pytest-cov; extra == "all"
192
+ Requires-Dist: pydantic-settings; extra == "all"
193
+ Requires-Dist: Unidecode; extra == "all"
194
+ Requires-Dist: huggingface_hub; extra == "all"
195
+ Requires-Dist: bokeh; extra == "all"
190
196
  Requires-Dist: logfire; extra == "all"
191
- Requires-Dist: appdirs; extra == "all"
192
- Requires-Dist: python-on-whales; extra == "all"
193
197
  Requires-Dist: semver; extra == "all"
194
- Requires-Dist: pydevd-pycharm~=251.25410.159; extra == "all"
195
- Requires-Dist: pandas; extra == "all"
196
- Requires-Dist: distributed; extra == "all"
197
- Requires-Dist: filetype; extra == "all"
198
- Requires-Dist: faker; extra == "all"
198
+ Requires-Dist: logfire[fastapi]; extra == "all"
199
+ Requires-Dist: tinynetrc; extra == "all"
200
+ Requires-Dist: torchvision; extra == "all"
201
+ Requires-Dist: pymupdf; extra == "all"
202
+ Requires-Dist: peft; extra == "all"
203
+ Requires-Dist: yamlscript; extra == "all"
204
+ Requires-Dist: logfire[httpx]; extra == "all"
205
+ Requires-Dist: pycountry; extra == "all"
206
+ Requires-Dist: setuptools; extra == "all"
207
+ Requires-Dist: playwright; extra == "all"
199
208
  Requires-Dist: sentence_transformers; extra == "all"
200
- Requires-Dist: regex; extra == "all"
209
+ Requires-Dist: faker; extra == "all"
201
210
  Requires-Dist: deepmerge; extra == "all"
202
- Requires-Dist: google-api-python-client; extra == "all"
203
- Requires-Dist: setuptools; extra == "all"
204
- Requires-Dist: google-auth; extra == "all"
211
+ Requires-Dist: httpx; extra == "all"
212
+ Requires-Dist: tabulate; extra == "all"
213
+ Requires-Dist: openpyxl; extra == "all"
214
+ Requires-Dist: deepdiff; extra == "all"
215
+ Requires-Dist: flet[all]; extra == "all"
216
+ Requires-Dist: sre_yield; extra == "all"
217
+ Requires-Dist: pydantic-extra-types; extra == "all"
205
218
  Requires-Dist: pydantic-ai[logfire,openai]; extra == "all"
206
- Requires-Dist: pydantic-settings; extra == "all"
207
- Requires-Dist: dnspython[doh]; extra == "all"
208
- Requires-Dist: ollama; extra == "all"
209
- Requires-Dist: cachetools; extra == "all"
210
- Requires-Dist: pymupdf4llm; extra == "all"
219
+ Requires-Dist: json_repair; extra == "all"
220
+ Requires-Dist: tokenizers; extra == "all"
221
+ Requires-Dist: pandas; extra == "all"
222
+ Requires-Dist: fastapi; extra == "all"
223
+ Requires-Dist: html2text; extra == "all"
224
+ Requires-Dist: flet-video; extra == "all"
211
225
  Requires-Dist: pyyaml; extra == "all"
212
- Requires-Dist: logfire[httpx]; extra == "all"
213
- Requires-Dist: playwright; extra == "all"
226
+ Requires-Dist: google-api-python-client; extra == "all"
227
+ Requires-Dist: dask[bag]; extra == "all"
228
+ Requires-Dist: google-auth-httplib2; extra == "all"
229
+ Requires-Dist: cachetools; extra == "all"
230
+ Requires-Dist: dnspython[doh]; extra == "all"
231
+ Requires-Dist: regex; extra == "all"
214
232
  Requires-Dist: odfpy; extra == "all"
215
- Requires-Dist: tokenizers; extra == "all"
216
- Requires-Dist: bokeh; extra == "all"
217
- Requires-Dist: google-auth-oauthlib; extra == "all"
218
- Requires-Dist: peft; extra == "all"
219
233
  Dynamic: author
220
234
  Dynamic: author-email
221
235
  Dynamic: description
@@ -5,9 +5,9 @@ fmtr/tools/augmentation_tools.py,sha256=-6ESbO4CDlKqVOV1J1V6qBeoBMzbFIinkDHRHnCB
5
5
  fmtr/tools/caching_tools.py,sha256=74p7m2GLFfeP41LX69wxgfkilxEAoWkMIfFMjKsYpyg,4976
6
6
  fmtr/tools/constants.py,sha256=90wCirUpw4bWMAbjhip0LL6k57eS5VmxRUP9pLCChBg,1812
7
7
  fmtr/tools/context_tools.py,sha256=4UvIHYgLqAh7dXMX9EBrLEpYp81qfzhSVrkffOSAoGA,350
8
- fmtr/tools/data_modelling_tools.py,sha256=0BFm-F_cYzVTxftWQwORkPd0FM2BTLVh9-s0-rTTFoo,1744
8
+ fmtr/tools/data_modelling_tools.py,sha256=vD7nbqeQh_azoNZUiVWvK8GuyNE-Qagcqg_RE7wYNBk,5303
9
9
  fmtr/tools/dataclass_tools.py,sha256=0Gt6KeLhtPgubo_2tYkIVqB8oQ91Qzag8OAGZDdjvMU,1209
10
- fmtr/tools/datatype_tools.py,sha256=3P4AWIFGkJ-UqvXlj0Jc9IvkIIgTOE9jRrOk3NVbpH8,1508
10
+ fmtr/tools/datatype_tools.py,sha256=1XznVwlY4-DkYa6FwhHMstD8qz27vCcV64IvCrcJgMA,1830
11
11
  fmtr/tools/datetime_tools.py,sha256=L7wmBoQbD9h_pJIL92WQOX32r_vrXgRvE-_0PVPRAGY,232
12
12
  fmtr/tools/debugging_tools.py,sha256=_xzqS0V5BpL8d06j-jVQjGgI7T020QsqVXKAKMz7Du8,2082
13
13
  fmtr/tools/environment_tools.py,sha256=43uqfj1G1bNX0IwKz-NKbu3AbFYSdbBuGN9rlThe030,1845
@@ -20,7 +20,7 @@ fmtr/tools/http_tools.py,sha256=RVwGrBNMyjfbpgAPCSnxEkXfSzXXWARb3ayq981ONQE,464
20
20
  fmtr/tools/import_tools.py,sha256=XJmiWLukRncJAcaGReDn4jIz1_IpVBjfYCQHH1hIg7c,588
21
21
  fmtr/tools/inherit_tools.py,sha256=gTGL4mRm5RsbFW76s25AbuAJ2vlymbh1c8Q4Hl2uJGU,646
22
22
  fmtr/tools/inspection_tools.py,sha256=tLTRvzy9XVomQPi0dfnF_cgwc7KiDVZAr7gPTk4S_bQ,278
23
- fmtr/tools/iterator_tools.py,sha256=ymxo2U9MrPhouIhWCVvh1TrP1bXJPm_p0Lqwgi5Jr6w,1628
23
+ fmtr/tools/iterator_tools.py,sha256=ucsDj-T4YgwRCieFvjBXLfTe4RipL1CJ1_1GOxvEIW8,1816
24
24
  fmtr/tools/json_fix_tools.py,sha256=vNSlswVQnujPmKEqDjFJcO901mjMyv59q3awsT7mlhs,477
25
25
  fmtr/tools/json_tools.py,sha256=WkFc5q7oqMtcFejhN1K5zQFULa9TdLOup83Fr0saDRY,348
26
26
  fmtr/tools/logging_tools.py,sha256=jZFKnL-7HHOaPkn7F3fT9DyffIgwY-g7SEQ0p1RhzBo,2673
@@ -40,12 +40,12 @@ fmtr/tools/random_tools.py,sha256=4VlQdk5THbR8ka4pZaLbk_ZO_4yy6PF_lHZes_rgenY,22
40
40
  fmtr/tools/semantic_tools.py,sha256=cxY9NSAHWj4nEc6Oj4qA1omR3dWbl2OuH7_PkINc6_E,1386
41
41
  fmtr/tools/settings_tools.py,sha256=o11W3T60UZSvCTkh_eEQq1Mx74GycQ6JxUr0plBDbsk,2356
42
42
  fmtr/tools/spaces_tools.py,sha256=D_he3mve6DruB3OPS6QyzqD05ChHnRTb4buViKPe7To,1099
43
- fmtr/tools/string_tools.py,sha256=Lz_H9l25OOoxE48QBJ_Upkk5nno7dPA6G2Gc0Wo8rOk,5275
43
+ fmtr/tools/string_tools.py,sha256=ZKHBrl2tui9VjWt7qit4UWbvpuzY6zp5ytiQvhlJVG4,5610
44
44
  fmtr/tools/tabular_tools.py,sha256=mw6vOij1Ch-pVAyHMPtm5zj__ULZN_TKeBYOfj33wFM,1634
45
45
  fmtr/tools/tokenization_tools.py,sha256=me-IBzSLyNYejLybwjO9CNB6Mj2NYfKPaOVThXyaGNg,4268
46
- fmtr/tools/tools.py,sha256=CAsApa1YwVdNE6H66Vjivs_mXYvOas3rh7fPELAnTpk,795
46
+ fmtr/tools/tools.py,sha256=sLMXk8juOL8_n_D776cJ-kzjyMHqFI_fctDEjy6PIKs,1115
47
47
  fmtr/tools/unicode_tools.py,sha256=yS_9wpu8ogNoiIL7s1G_8bETFFO_YQlo4LNPv1NLDeY,52
48
- fmtr/tools/version,sha256=1-w7DNphgnwgQa3EckgnbD0Qx9ULcbjMgAOknBRyoWI,6
48
+ fmtr/tools/version,sha256=jSMjAm19YQafVxIPX-mu0uNeQjDY56c4NghzH2KMxTY,6
49
49
  fmtr/tools/webhook_tools.py,sha256=q3pVJ1NCem2SrMuFcLxiWd7DibFs7Q-uGtojfXd3Qcg,380
50
50
  fmtr/tools/yaml_tools.py,sha256=Bhhyd6GQVKO72Lp8ky7bAUjIB_65Hdh0Q45SKIEe6S8,1901
51
51
  fmtr/tools/ai_tools/__init__.py,sha256=O8VRlPnnQCncg2ZZ2l_VdWLJf4jkKH6dkZFVbv6o7IM,388
@@ -85,9 +85,9 @@ fmtr/tools/tests/test_path.py,sha256=AkZQa6_8BQ-VaCyL_J-iKmdf2ZaM-xFYR37Kun3k4_g
85
85
  fmtr/tools/tests/test_yaml.py,sha256=jc0TwwKu9eC0LvFGNMERdgBue591xwLxYXFbtsRwXVM,287
86
86
  fmtr/tools/version_tools/__init__.py,sha256=cjE6nO6AoVOUp3RwgTbqL9wiw8J1l2pHJOz6Gn6bxjA,326
87
87
  fmtr/tools/version_tools/version_tools.py,sha256=Hcc6yferZS1hHbugRTdiHhSNmXEEG0hjCiTTXKna-YY,1127
88
- fmtr_tools-1.3.63.dist-info/licenses/LICENSE,sha256=FW9aa6vVN5IjRQWLT43hs4_koYSmpcbIovlKeAJ0_cI,10757
89
- fmtr_tools-1.3.63.dist-info/METADATA,sha256=nSjkJECziz3f5ss_9fCnnyyVJCoAKvOl0LzYDPShNzo,17455
90
- fmtr_tools-1.3.63.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
91
- fmtr_tools-1.3.63.dist-info/entry_points.txt,sha256=h-r__Xh5njtFqreMLg6cGuTFS4Qh-QqJPU1HB-_BS-Q,357
92
- fmtr_tools-1.3.63.dist-info/top_level.txt,sha256=LXem9xCgNOD72tE2gRKESdiQTL902mfFkwWb6-dlwEE,5
93
- fmtr_tools-1.3.63.dist-info/RECORD,,
88
+ fmtr_tools-1.3.65.dist-info/licenses/LICENSE,sha256=FW9aa6vVN5IjRQWLT43hs4_koYSmpcbIovlKeAJ0_cI,10757
89
+ fmtr_tools-1.3.65.dist-info/METADATA,sha256=GU4COvHnYDWpDNeNy4PX0q3x6dxqRjxj0Ps0s_tCLX4,18035
90
+ fmtr_tools-1.3.65.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
91
+ fmtr_tools-1.3.65.dist-info/entry_points.txt,sha256=h-r__Xh5njtFqreMLg6cGuTFS4Qh-QqJPU1HB-_BS-Q,357
92
+ fmtr_tools-1.3.65.dist-info/top_level.txt,sha256=LXem9xCgNOD72tE2gRKESdiQTL902mfFkwWb6-dlwEE,5
93
+ fmtr_tools-1.3.65.dist-info/RECORD,,