formatize 1.0.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Asinerum Conlang Project
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: formatize
3
+ Version: 1.0.0
4
+ Summary: Literp Formatting Lib
5
+ Home-page: https://github.com/asinerum/formatize
6
+ Author: Asinerum Conlang Project
7
+ Author-email: asinerum.com@gmail.com
8
+ License: MIT
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.7
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: license-file
16
+
17
+ Detailed tips, tricks, and examples, can be found at project's repository
18
+ https://github.com/asinerum/formatize
19
+
20
+ (C) 2026 Asinerum Conlang Project
@@ -0,0 +1,4 @@
1
+ Detailed tips, tricks, and examples, can be found at project's repository
2
+ https://github.com/asinerum/formatize
3
+
4
+ (C) 2026 Asinerum Conlang Project
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,28 @@
1
+ [metadata]
2
+ name = formatize
3
+ version = 1.0.0
4
+ author = Asinerum Conlang Project
5
+ author_email = asinerum.com@gmail.com
6
+ description = Literp Formatting Lib
7
+ long_description = file: README.md
8
+ long_description_content_type = text/markdown
9
+ url = https://github.com/asinerum/formatize
10
+ license = MIT
11
+ classifiers =
12
+ Programming Language :: Python :: 3
13
+ License :: OSI Approved :: MIT License
14
+ Operating System :: OS Independent
15
+
16
+ [options]
17
+ package_dir =
18
+ = src
19
+ packages = find:
20
+ python_requires = >=3.7
21
+
22
+ [options.packages.find]
23
+ where = src
24
+
25
+ [egg_info]
26
+ tag_build =
27
+ tag_date = 0
28
+
@@ -0,0 +1,3 @@
1
+ from .form import *
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1,805 @@
1
+ from pydantic import BaseModel, ConfigDict, create_model
2
+ from typing import List, Dict, Optional
3
+ from importlib import import_module
4
+ from bs4 import BeautifulSoup
5
+
6
+ import datetime
7
+ import hashlib
8
+ import json
9
+ import math
10
+ import re
11
+ import inspect
12
+ import string
13
+ import uuid
14
+ import yaml
15
+
16
+ import socket
17
+ import smtplib
18
+ import dns.resolver
19
+
20
+ import tkinter as tk
21
+ from PIL import Image, ImageTk
22
+
23
+ LENGTH_MD5 = 32
24
+ LENGTH_SHA256 = 64
25
+ APP_YAML = 'app.yaml'
26
+ ENCODING = 'utf-8'
27
+ ENCODINGSIG = 'utf-8-sig'
28
+ YAML_ENCODE = 'utf-8-sig'
29
+
30
+ EMPTY = ''
31
+ SPACE = ' '
32
+ DATESTRS = '-'
33
+ TIMESTRS = ':'
34
+ REGPUNCS = r'[' + string.punctuation + r']+'
35
+ REGTIMES = r'[' + SPACE + r'T]+'
36
+
37
+ ERROR = 'error'
38
+ PARAMS = 'params'
39
+
40
+ ADMIN = 'admin'
41
+ ACCOUNTING = 'accounting'
42
+
43
+ APP_PATH = 'script.app_'
44
+ DBA_PATH = 'lib.db.dbase_'
45
+
46
+ ERR_INPUT = 'Invalid input data'
47
+
48
+ ## OLD LAMBDAS
49
+
50
+ def err_input (e):
51
+ return f'{ERR_INPUT}: {e}'
52
+
53
+ def data_dict ():
54
+ return {ERROR: None, PARAMS: {}}
55
+
56
+ def uid ():
57
+ return str(uuid.uuid4())
58
+
59
+ def md5 (text):
60
+ return crypto_md5(text)
61
+
62
+ def sha (text):
63
+ return crypto_sha256(text)
64
+
65
+ def now ():
66
+ return str(datetime.datetime.now())
67
+
68
+ def hstr (x):
69
+ return str(x or '')
70
+
71
+ def caller ():
72
+ return inspect.stack()[1][3]
73
+
74
+ def extract (s, sep='.'):
75
+ return s.split(sep)
76
+
77
+ def add_zero (n, zeros=2):
78
+ return str(n).zfill(zeros)
79
+
80
+ def sort_dict (obj):
81
+ return dict(sorted(obj.items()))
82
+
83
+ def copy_dict (obj): ## CloneObject
84
+ return json.loads(json.dumps(obj))
85
+
86
+ def dict_contain (obj, keys):
87
+ return all(key in obj for key in keys)
88
+
89
+ def sum_over_list_of_floats (lst):
90
+ return math.fsum(lst)
91
+
92
+ def sum_over_list_of_dicts (list_of_dicts, key):
93
+ return sum(float(d[key]) for d in list_of_dicts)
94
+
95
+ def format_decimal_number (num, dec=2):
96
+ return ('{:,.' + str(dec) + 'f}').format(float(num))
97
+
98
+ def draw_aligned_number (num, spaces=20, symb='$', dec=2):
99
+ return ralign(form(num, dec), spaces, symb)
100
+
101
+ def right_align_text (text, spaces=20, suffix=''):
102
+ return ('{:>' + str(spaces) + '}').format(text) + suffix
103
+
104
+ def output_keyval (object, left, right, sep='\n'):
105
+ return sep.join([('{:<' + str(left) + '}').format(key) + ('{:>' + str(right) + '}').format(object[key]) for key in object])
106
+
107
+ fs = sum_over_list_of_floats
108
+
109
+ lds = sum_over_list_of_dicts
110
+
111
+ form = format_decimal_number
112
+
113
+ number = draw_aligned_number
114
+
115
+ ralign = right_align_text
116
+
117
+ output = output_keyval
118
+
119
+ def to_upper (text):
120
+ return str_clean(text).upper()
121
+
122
+ def to_title (text):
123
+ return str_clean(text).title()
124
+
125
+ def dicts (models):
126
+ return [x.model_dump() for x in models]
127
+
128
+ def merge_dict (dict1, dict2):
129
+ return {**dict1, **dict2}
130
+
131
+ def generate_dict (items): ## TupleList
132
+ return {key:val for key,val in items}
133
+
134
+ def generate_json (items): ## TupleList
135
+ return json.dumps(generate_dict(items))
136
+
137
+ def create_dict (**kwargs): ## dcore.kwarg()
138
+ return {arg:kwargs[arg] for arg in kwargs}
139
+
140
+ def function_module2 (modules, funcname):
141
+ return next((key for key in modules if funcname in modules[key]), None)
142
+
143
+ def function_module (modules, funcname):
144
+ return next((key for key in app_functions(modules) if funcname in dir(modules[key])), None)
145
+
146
+ def app_functions (modules):
147
+ return {x: [y for y in dir(modules[x]) if callable(getattr(modules[x], y)) and y.lower()==y and not y.startswith('_')] for x in modules}
148
+
149
+ def prepare_json_info (info, InfoModel, defval=None):
150
+ return json.dumps(InfoModel(**info).model_dump()) if type(info) is dict else defval
151
+
152
+ def unpack_one_vs_many_db_values (model, mid, mvals, formatfunc=int):
153
+ return ','.join('(' + str(formatfunc(getattr(model,mid))) + ',' + str(formatfunc(v)) + ')' for v in getattr(model, mvals))
154
+
155
+ def flatten (matrix):
156
+ return [item for row in matrix for item in row]
157
+
158
+ def find_item (data, key, val): ## data:DictList
159
+ return find_item_in_list_of_dict(data, key, val)
160
+
161
+ def merge_list (list1, list2):
162
+ return merge_two_list_exclusive(list1, list2)
163
+
164
+ def check_list (list1, list2):
165
+ return any(x in list1 for x in list2)
166
+
167
+ def unpack_list_to_tuple_str (lst):
168
+ return f'{*lst,}'
169
+
170
+ def str_to_list (s):
171
+ return list(map(str.strip,s.split(',')))
172
+
173
+ def adjust_array (array, count, defval, defid=0):
174
+ return [defval]*count if type(array)!=list or not array else (array if count==len(array) else [array[defid]]+[defval]*(count-1))
175
+
176
+ def fillup_array (array, count, defval, defid=0):
177
+ return [defval]*count if type(array)!=list or not array else (array if count==len(array) else [array[defid]]*count)
178
+
179
+ news = adjust_array
180
+
181
+ dups = fillup_array
182
+
183
+ def default (params, attr, val):
184
+ return params.setdefault(attr, val)
185
+
186
+ def integer (val, defval=-1):
187
+ return int(float(val)) if is_numeric(val) else defval
188
+
189
+ def boolean (val):
190
+ return 0 if not val else 1
191
+
192
+ def setattrs (Module, attrs=[], vals={}):
193
+ return [setattr(Module, i, vals.get(i)) for i in attrs]
194
+
195
+ def get_attr (module, attr, defval=ValueError):
196
+ return getattr(module, attr) if attr in dir(module) else defval
197
+
198
+ def popattr (module=None, attr=None):
199
+ return getattr(module, attr) if attr in dir(module) else None
200
+
201
+ def popfunc (val, module=None, func=str, pattern=r'\/(.*?)\/'): ## val:/something/
202
+ return (popattr(module, m.group(1)) if (m:=re.search(pattern,str(val))) else val) or func
203
+
204
+ ## BASIC FORMATTINGS
205
+
206
+ def replace_dict_placeholders(text: str, vals: dict, quot: str="'", holder: str=r'\:\S+\b') -> str:
207
+ ## This may cause ambiguous matter in SQL STRING/NUMBER comparison
208
+ try:
209
+ subs = re.findall(holder, text)
210
+ items = iter(str(e) for e in subs)
211
+ return re.sub(holder, lambda lm: f"{quot}{vals[next(items)[1:]]}{quot}", text)
212
+ except:
213
+ return None
214
+
215
+ def replace_placeholders(text: str, vals: list, quot: str="'", holder: str=r'\?', formatfunc=int) -> str:
216
+ ## This may cause ambiguous matter in SQL STRING/NUMBER comparison
217
+ try:
218
+ count = len(re.findall(holder, text))
219
+ if count != len(vals): return None
220
+ items = iter(str(formatfunc(e)) for e in vals)
221
+ return re.sub(holder, lambda lm: f"{quot}{next(items)}{quot}", text)
222
+ except:
223
+ return None
224
+
225
+ def crc16(data: bytes, encoding: str='utf-8', purehex: bool=False) -> str:
226
+ if type(data)==str: data = data.encode(encoding)
227
+ if type(data) is not bytes: return None
228
+ crc = 0xFFFF
229
+ for byte in data:
230
+ crc ^= (byte << 8)
231
+ for _ in range(8):
232
+ if crc & 0x8000:
233
+ crc = (crc << 1) ^ 0x1021
234
+ else:
235
+ crc <<= 1
236
+ crc &= 0xFFFF
237
+ res = f'0x{crc:04X}'
238
+ return res if purehex else res[2:]
239
+
240
+ def module_extract(module, path: str, defval: any=None):
241
+ try:
242
+ obj = module
243
+ paths = extract(path)
244
+ for s in paths:
245
+ obj = getattr(obj, s)
246
+ return obj
247
+ except:
248
+ return defval
249
+
250
+ def dict_assign(object: dict, path: str, value: any, add: bool=False):
251
+ obj = object ## NoCopy
252
+ paths = extract(path)
253
+ for i, s in enumerate(paths):
254
+ if i==len(paths)-1:
255
+ obj[s] = value
256
+ else:
257
+ if s not in obj or type(obj[s]) != dict:
258
+ if add:
259
+ obj[s] = {}
260
+ else:
261
+ return
262
+ obj = obj[s]
263
+
264
+ def dict_extract(object: dict, path: str, defval: any=None) -> any:
265
+ try:
266
+ obj = copy_dict(object)
267
+ paths = extract(path)
268
+ for s in paths:
269
+ obj = obj.get(s)
270
+ return obj
271
+ except:
272
+ return defval
273
+
274
+ def dict_sort(object: dict, path: str='') -> dict:
275
+ subdict = dict_extract(object, path)
276
+ if subdict:
277
+ dict_assign(object, path, value=sort_dict(subdict))
278
+ else:
279
+ object = sort_dict(object)
280
+ return object
281
+
282
+ def merge_dicts(err: str, *sources) -> dict:
283
+ merged_dict = {}
284
+ for d in sources:
285
+ if not isinstance(d, dict): raise TypeError(err)
286
+ merged_dict.update(d)
287
+ return merged_dict
288
+
289
+ def find_field_in_list_of_dict(data: list, sub: dict, field: str, defval: any=None) -> any:
290
+ return next((item.get(field) for item in data if type(item)==dict and item.get(k:=list(sub.keys())[0])==sub[k]), defval)
291
+
292
+ def find_item_in_list_of_dict(data: list, key: str, val: any) -> dict:
293
+ return next((item for item in data if item[key]==val), None)
294
+
295
+ def select_items_from_dict(data: dict, selected_keys: list) -> dict:
296
+ for k in selected_keys:
297
+ if k not in data: selected_keys.remove(k)
298
+ return {k: data[k] for k in selected_keys}
299
+
300
+ def merge_two_list_exclusive(list1: list, list2: list) -> list:
301
+ return list(set(list(set(list2)-set(list1))+list1))
302
+
303
+ def str_is_hex(s: str) -> bool:
304
+ hex_digits = set(string.hexdigits)
305
+ return all(c in hex_digits for c in s)
306
+
307
+ def is_numeric(val: any, func=float) -> bool:
308
+ try:
309
+ func(val)
310
+ return True
311
+ except:
312
+ return False
313
+
314
+ def prepare_password(params: dict, col_hpwd: str) -> bool:
315
+ params[col_hpwd] = prepare_hash_password(params.get(col_hpwd))
316
+ return True
317
+
318
+ def prepare_info(params: dict, col_info: str, InfoModel) -> bool:
319
+ params[col_info] = prepare_json_info(params.get(col_info), InfoModel, params.setdefault(col_info,''))
320
+ return True
321
+
322
+ def prepare_workinterval(model) -> bool:
323
+ model.workbegin = parse_time(model.workbegin)
324
+ model.workend = parse_time(model.workend)
325
+ return True
326
+
327
+ def prepare_worktime(model) -> bool:
328
+ model.worktime = model.worktime or now()
329
+ model.worktime = parse_time(model.worktime)
330
+ return True
331
+
332
+ def totime(time: any=None, defval=None) -> datetime.datetime:
333
+ if not (time:=parse_time(time)): return defval
334
+ return datetime.datetime.fromisoformat(time)
335
+
336
+ def parse_doc_date(time: any=None) -> str:
337
+ if not (time:=parse_time(time)): return None ## For DATE()
338
+ return time[:10]
339
+
340
+ def parse_time(time: any=None) -> str:
341
+ if not time:
342
+ return None
343
+ elif type(time) is datetime.datetime:
344
+ return str(time)
345
+ elif type(time) is int:
346
+ return str(datetime.datetime.fromtimestamp(time))
347
+ try:
348
+ return str(datetime.datetime.fromisoformat(time))
349
+ except Exception:
350
+ try:
351
+ time = str_time_clean(time)
352
+ return str(datetime.datetime.fromisoformat(time))
353
+ except Exception:
354
+ return now()
355
+
356
+ def str_time_clean(text: str) -> str:
357
+ text = re.sub(REGTIMES,SPACE,text).strip()
358
+ part = text.split(SPACE)
359
+ try:
360
+ date = str_add_zero(str_clean(part[0],DATESTRS),DATESTRS)
361
+ time = str_add_zero(str_clean(part[1],TIMESTRS),TIMESTRS)
362
+ except IndexError:
363
+ time = ''
364
+ return (date+SPACE+time).strip()
365
+
366
+ def str_clean(text: str, sep: str=SPACE) -> str:
367
+ return re.sub(REGPUNCS,sep,text).strip()
368
+
369
+ def str_add_zero(text: str, sep: str) -> str:
370
+ try:
371
+ text = text.split(sep)
372
+ part = [f"{int(x):02d}" for x in text]
373
+ return sep.join(part)
374
+ except Exception:
375
+ return ''
376
+
377
+ def prepare_hash_password(given_pwd: str, default_pwd: str='123456') -> str:
378
+ if not given_pwd:
379
+ return text_to_password(default_pwd)
380
+ elif len(given_pwd)==LENGTH_MD5 and str_is_hex(given_pwd):
381
+ return crypto_sha256(given_pwd)
382
+ elif len(given_pwd)!=LENGTH_SHA256 or not str_is_hex(given_pwd):
383
+ return text_to_password(given_pwd)
384
+ return given_pwd
385
+
386
+ def text_to_password(text: str) -> str:
387
+ return crypto_sha256(crypto_md5(text))
388
+
389
+ def crypto_encode(text: str) -> (str, str):
390
+ return crypto_sha256(text), crypto_md5(text)
391
+
392
+ def crypto_sha256(text: str) -> str:
393
+ try:
394
+ text_bytes = text.encode(ENCODING)
395
+ sha256_hash = hashlib.sha256()
396
+ sha256_hash.update(text_bytes)
397
+ return sha256_hash.hexdigest()
398
+ except Exception:
399
+ return None
400
+
401
+ def crypto_md5(text: str) -> str:
402
+ try:
403
+ text_bytes = text.encode(ENCODING)
404
+ md5_hash = hashlib.md5()
405
+ md5_hash.update(text_bytes)
406
+ return md5_hash.hexdigest()
407
+ except Exception:
408
+ return None
409
+
410
+ def render_html(filename: str, dtexts: dict={}, dattributes: dict={}) -> BeautifulSoup:
411
+ soup = soup_from_template(filename)
412
+ return render_soup(soup, dtexts, dattributes) #soup
413
+
414
+ def render_soup(soup: BeautifulSoup, dtexts: dict={}, dattributes: dict={'placeholder':{}}) -> BeautifulSoup:
415
+ for id in dtexts:
416
+ soup = soup_replace_value(soup, id, dtexts[id])
417
+ for attr in dattributes:
418
+ for id in dattributes[attr]:
419
+ soup = soup_replace_attribute(soup, id, attr, dattributes[attr][id])
420
+ return soup
421
+
422
+ def soup_replace_attribute(soup: BeautifulSoup, id: str, attribute: str, newvalue: str) -> BeautifulSoup:
423
+ find = soup.find(id=id)
424
+ if find:
425
+ find[attribute] = newvalue
426
+ return soup
427
+
428
+ def soup_replace_value(soup: BeautifulSoup, id: str, newvalue: str) -> BeautifulSoup:
429
+ find = soup.find(id=id)
430
+ if find:
431
+ find.string.replace_with(newvalue)
432
+ return soup
433
+
434
+ def soup_from_template(filename: str, folder: str='') -> BeautifulSoup:
435
+ with open(f"{folder}{filename}", 'r', encoding=ENCODING) as file:
436
+ filetext = file.read()
437
+ return BeautifulSoup(filetext, 'html.parser')
438
+
439
+ def param_load_from_request(jsonstr: str) -> dict:
440
+ result = data_dict()
441
+ try:
442
+ result[PARAMS] = json.loads(jsonstr)
443
+ if type(result[PARAMS]) is not dict:
444
+ result[ERROR] = ERR_INPUT
445
+ except json.JSONDecodeError as e:
446
+ result[ERROR] = err_input(e)
447
+ except Exception as e:
448
+ result[ERROR] = err_input(e)
449
+ return result
450
+
451
+ def data_load_from_csv_file(filepath: str, ffill: bool=True, nonan: bool=True) -> dict:
452
+ import pandas as pd
453
+ import numpy as np
454
+ result = data_dict()
455
+ try:
456
+ df = pd.read_csv(filepath)
457
+ if ffill: df = df.fillna(method='ffill')
458
+ if nonan: df = df.replace({np.nan: None})
459
+ result[PARAMS] = df.to_dict(orient='records')
460
+ except Exception as e:
461
+ result[ERROR] = err_input(e)
462
+ return result
463
+
464
+ def data_load_from_json_file(filepath: str) -> dict:
465
+ result = data_dict()
466
+ try:
467
+ with open(filepath, encoding=ENCODINGSIG) as file:
468
+ result[PARAMS] = json.load(file)
469
+ except json.JSONDecodeError as e:
470
+ result[ERROR] = err_input(e)
471
+ except Exception as e:
472
+ result[ERROR] = err_input(e)
473
+ return result
474
+
475
+ def data_load_from_yaml_file(filepath: str=APP_YAML) -> dict:
476
+ result = data_dict()
477
+ try:
478
+ with open(filepath, encoding=ENCODINGSIG) as file:
479
+ result[PARAMS] = yaml.load(file, yaml.Loader)
480
+ if type(result[PARAMS]) is not dict:
481
+ result[ERROR] = ERR_INPUT
482
+ result[PARAMS] = {}
483
+ except Exception as e:
484
+ result[ERROR] = err_input(e)
485
+ return result
486
+
487
+ def data_dump_to_yaml_file(data: any, filepath: str, encoding=ENCODINGSIG, sort_keys=False) -> bool:
488
+ try:
489
+ with open(filepath, 'w', encoding=encoding) as file:
490
+ yaml.dump(data, file, default_flow_style=False, sort_keys=sort_keys)
491
+ return True
492
+ except:
493
+ return False
494
+
495
+ def data_dump_to_csv_file(listofdicts: list, filepath: str) -> bool:
496
+ import pandas as pd
497
+ try:
498
+ df = pd.DataFrame(listofdicts)
499
+ df.to_csv(filepath, index=False)
500
+ return True
501
+ except:
502
+ return False
503
+
504
+ dump_csv = data_dump_to_csv_file
505
+
506
+ def jsonloads(text: str) -> dict:
507
+ try:
508
+ return json.loads(text)
509
+ except:
510
+ return None
511
+
512
+ def str_to_json(val: str) -> str:
513
+ if type(val) in [dict, list]: return json.dumps(val)
514
+ try:
515
+ return json.dumps(json.loads(str(val)))
516
+ except:
517
+ return '{}'
518
+
519
+ def load_yaml(yamlfile: str) -> dict:
520
+ with open(yamlfile, encoding=YAML_ENCODE) as file:
521
+ return yaml.load(file, Loader=yaml.Loader)
522
+
523
+ def dump_yaml(data: any, yamlfile: str) -> bool:
524
+ return data_dump_to_yaml_file(data, yamlfile, YAML_ENCODE)
525
+
526
+ def parse_model_dat(dat: dict) -> dict:
527
+ d = {x: tuple([eval(dat[x][0]), dat[x][1]]) for x in dat}
528
+ return d
529
+
530
+ def load_model(modelname: str, dat: dict) -> BaseModel:
531
+ return create_model(modelname, **parse_model_dat(dat), __config__=ConfigDict(coerce_numbers_to_str=True))
532
+
533
+ def load_model_from_yaml(modelname: str, modelfile: str='model.yaml') -> BaseModel:
534
+ try:
535
+ models = load_yaml(modelfile)
536
+ return load_model(modelname, models.get(modelname))
537
+ except:
538
+ return None
539
+
540
+ def load_models_from_yaml(modelfile: str='model.yaml') -> dict:
541
+ dat = {}
542
+ try:
543
+ models = load_yaml(modelfile)
544
+ for model in models: dat[model] = load_model(model, models[model])
545
+ except:
546
+ return None
547
+ return dat
548
+
549
+ def add_models_from_yaml(Module, modelfile: str='model.yaml') -> bool:
550
+ return update_module(Module, load_models_from_yaml(modelfile))
551
+
552
+ def prepare_model(model, BuildModel, DefModel):
553
+ if type(model) is dict:
554
+ return BuildModel(**model)
555
+ elif type(model) is list:
556
+ if issubclass(type(model[0]), BaseModel):
557
+ model = [x.model_dump() for x in model]
558
+ elif type(model[0]) is dict:
559
+ model = [BuildModel(**x).model_dump() for x in model]
560
+ return DefModel(array=model, limit=len(model))
561
+ return model
562
+
563
+ def ext_modules(appfile: str='app.yaml') -> list:
564
+ return data_load_from_yaml_file(appfile)[PARAMS].get('modules')
565
+
566
+ def merge_modules(source: str, targetmodule, exlist: list=[]):
567
+ sourcemodule = import_module(source)
568
+ for item in filter(lambda attr: attr in exlist or attr not in dir(targetmodule), dir(sourcemodule)):
569
+ setattr(targetmodule, item, getattr(sourcemodule, item))
570
+
571
+ def load_modules(sources: list, targetmodule, path: str=APP_PATH):
572
+ for source in sources: merge_modules(f'{path}{source}', targetmodule)
573
+
574
+ def load_ext_modules(targetmodule):
575
+ load_modules(ext_modules(), targetmodule)
576
+
577
+ add_modules_from_yaml = load_ext_modules
578
+
579
+ def setattrs_from_yaml(Module, iofile: str='io.yaml') -> bool:
580
+ return update_module(Module, data_load_from_yaml_file(iofile)[PARAMS])
581
+
582
+ add_consts_from_yaml = setattrs_from_yaml
583
+
584
+ def get_module(module, path=APP_PATH):
585
+ try:
586
+ return import_module(f'{path}{module}')
587
+ except:
588
+ return None
589
+
590
+ def get_modules_from_yaml(path: str=APP_PATH, appfile: str='app.yaml') -> dict:
591
+ modules = [ADMIN, ACCOUNTING]
592
+ params = data_load_from_yaml_file(appfile)[PARAMS]
593
+ if params:
594
+ mx = params.get('modules')
595
+ if type(mx) is str: mx = list(map(str.strip, mx.split(',')))
596
+ if type(mx) is list: modules = merge_list(modules, mx)
597
+ try:
598
+ return {module: get_module(module, path) for module in modules}
599
+ except:
600
+ return {}
601
+
602
+ def update_module(Module, vals: dict) -> bool:
603
+ try:
604
+ setattrs(Module, [x for x in vals], vals)
605
+ return True
606
+ except:
607
+ return False
608
+
609
+ ## FRONTEND FORMATTINGS
610
+
611
+ def load_yaml_temp (filepath):
612
+ return data_load_from_yaml_file(filepath).get(PARAMS)
613
+
614
+ def load_yaml_account_temp (filename, folder='temp/yaml/account/'):
615
+ return load_yaml_temp(f'{folder}{filename}')
616
+
617
+ def load_yaml_subaccount_temp (filename='subaccount.yaml'):
618
+ return load_yaml_account_temp(filename).get('subaccount_data')
619
+
620
+ def load_yaml_detailaccount_temp (filename='detailaccount.yaml'):
621
+ return load_yaml_account_temp(filename).get('detailaccount_data')
622
+
623
+ def load_csv_temp (filepath):
624
+ return data_load_from_csv_file(filepath).get(PARAMS)
625
+
626
+ def load_csv_account_temp (filename, folder='temp/csv/account/'):
627
+ return load_csv_temp(f'{folder}{filename}')
628
+
629
+ def load_csv_subaccount_temp (filename='subaccount.csv'):
630
+ return load_csv_account_temp(filename)
631
+
632
+ def load_csv_detailaccount_temp (filename='detailaccount.csv'):
633
+ return load_csv_account_temp(filename)
634
+
635
+ class Napas:
636
+ QRORDER = 'form.method.account.currency.amount.country'
637
+
638
+ def __init__(self, napas: dict, dcopy: dict, banks: list):
639
+ self.napas = napas
640
+ self.dcopy = dcopy
641
+ self.banks = banks
642
+
643
+ def dget(self):
644
+ return self.dcopy
645
+
646
+ def concat(self, path: str) -> str:
647
+ item = dict_extract(self.dcopy, path)
648
+ if type(item)!=dict or not dict_contain(item, extract('id.length.value')):
649
+ raise ValueError('Invalid values assigned')
650
+ return f"{item['id']}{item['length']}{item['value']}"
651
+
652
+ def dset(self,
653
+ config: str,
654
+ bankcode: str,
655
+ account: str,
656
+ currency: str,
657
+ country: str,
658
+ amount: float=0,
659
+ data: dict=None):
660
+ method = dict_extract(self.napas, f'method.value.{config.lower()}')
661
+ if not method:
662
+ raise ValueError('Invalid initiation method')
663
+ dict_assign(self.dcopy, 'method.value', method)
664
+ service = dict_extract(self.napas, f'account.value.service.value.{config.lower()}')
665
+ if not service:
666
+ raise ValueError('Invalid service code')
667
+ dict_assign(self.dcopy, 'account.value.service.value', service)
668
+ bankbin = find_field_in_list_of_dict(self.banks, {'code': bankcode.upper()}, 'bin')
669
+ if not bankbin:
670
+ raise ValueError('Invalid bank code')
671
+ dict_assign(self.dcopy, 'account.value.bank.value.acquirer.value', bankbin)
672
+ if not account:
673
+ raise ValueError('Invalid beneficiary account')
674
+ accountlength = len(account)
675
+ len_merchant = add_zero(accountlength)
676
+ len_bank = add_zero(accountlength + 14)
677
+ len_account = add_zero(accountlength + 44)
678
+ dict_assign(self.dcopy, 'account.value.bank.value.merchant.value', account)
679
+ dict_assign(self.dcopy, 'account.value.bank.value.merchant.length', len_merchant)
680
+ dict_assign(self.dcopy, 'account.value.bank.length', len_bank)
681
+ dict_assign(self.dcopy, 'account.length', len_account)
682
+ currencycode = dict_extract(self.napas, f'currency.value.{currency.upper()}.code')
683
+ if not currencycode:
684
+ raise ValueError('Invalid currency code')
685
+ dict_assign(self.dcopy, 'currency.value', currencycode)
686
+ if float(amount)<0:
687
+ raise ValueError('Invalid billing amount')
688
+ amount = str(amount)
689
+ amountlength = len(amount)
690
+ len_amount = add_zero(amountlength)
691
+ dict_assign(self.dcopy, 'amount.value', amount)
692
+ dict_assign(self.dcopy, 'amount.length', len_amount)
693
+ countrycode = dict_extract(self.napas, f'country.value.{country.upper()}.code')
694
+ if not countrycode:
695
+ raise ValueError('Invalid country code')
696
+ dict_assign(self.dcopy, 'country.value', countrycode)
697
+ guidstring = self.concat('account.value.guid')
698
+ acquirerstring = self.concat('account.value.bank.value.acquirer')
699
+ merchantstring = self.concat('account.value.bank.value.merchant')
700
+ dict_assign(self.dcopy, 'account.value.bank.value', acquirerstring+merchantstring)
701
+ bankstring = self.concat('account.value.bank')
702
+ servicestring = self.concat('account.value.service')
703
+ accountstring = f'{guidstring}{bankstring}{servicestring}'
704
+ dict_assign(self.dcopy, 'account.value', accountstring)
705
+ beforedatastring = ''
706
+ for i in extract(self.QRORDER):
707
+ beforedatastring += self.concat(i)
708
+ napasdata = dict_extract(self.napas, 'data.value.id')
709
+ if data and dict_contain(napasdata, data):
710
+ more = ''
711
+ for key in data:
712
+ more += f"{napasdata[key]['code']}{add_zero(len(data[key]))}{data[key]}"
713
+ morelength = len(more)
714
+ dict_assign(self.dcopy, 'data.value', more)
715
+ dict_assign(self.dcopy, 'data.length', add_zero(morelength))
716
+ datastring = self.concat('data')
717
+ else:
718
+ del self.dcopy['data']
719
+ datastring = ''
720
+ beforecrcstring = f'{beforedatastring}{datastring}6304'
721
+ crcstring = crc16(beforecrcstring)
722
+ dict_assign(self.dcopy, 'crc.value', crcstring)
723
+ return f'{beforecrcstring}{crcstring}'
724
+
725
+ def qextract(self, qstr: str, qmodel=None, idcol='id') -> dict:
726
+ join = lambda d: ''.join(d.values())
727
+ take = lambda s: {'id': s[:2], 'length': s[2:4], 'value': s[4:4+int(s[2:4])]}
728
+ section = lambda qmodel, id: next(key for key in qmodel if qmodel[key][idcol]==id)
729
+ if qmodel is None: qmodel = self.napas
730
+ qdata = {}
731
+ try:
732
+ while len(qstr)>0:
733
+ d = take(qstr)
734
+ qdata[section(qmodel, d['id'])] = d
735
+ qstr = qstr[len(join(d)):]
736
+ return {'error': None, 'result': qdata}
737
+ except Exception as e:
738
+ return {'error': f'IOError: {e}', 'result': None}
739
+
740
+ def qextract2(self, qstr: str) -> dict:
741
+ qext = self.qextract(qstr)
742
+ if qext['error']: return qext
743
+ qdata = qext['result']
744
+ try:
745
+ qaccount = qdata['account']
746
+ account = self.qextract(qaccount['value'], dict_extract(self.napas, 'account.value'))['result']
747
+ bank = self.qextract(dict_extract(account, 'bank.value'), dict_extract(self.napas, 'account.value.bank.value'))['result']
748
+ service = account['service']
749
+ guid = account['guid']
750
+ qdata['account']['value'] = {'guid': guid, 'bank': bank, 'service': service}
751
+ dataval = dict_extract(qdata, 'data.value')
752
+ if dataval: qdata['data']['value'] = self.qextract(dataval, dict_extract(self.napas, 'data.value.id'), idcol='code')['result']
753
+ return {'error': None, 'result': qdata}
754
+ except Exception as e:
755
+ return {'error': f'IOError: {e}', 'result': None}
756
+
757
+ write = dset
758
+ read = qextract2
759
+
760
+ def mailvalid(address: str) -> bool:
761
+ records = dns.resolver.resolve('gmail.com', 'MX')
762
+ mxRecord = records[0].exchange
763
+ mxRecord = str(mxRecord)
764
+ host = socket.gethostname()
765
+ server = smtplib.SMTP()
766
+ server.set_debuglevel(0)
767
+ server.connect(mxRecord)
768
+ server.helo(host)
769
+ server.mail('me@domain.com')
770
+ code, message = server.rcpt(str(address))
771
+ server.quit()
772
+ return True if code==250 else False
773
+
774
+ def print_qrcode(data: str, path: str) -> bool:
775
+ import qrcode
776
+ try:
777
+ img = qrcode.make(data)
778
+ img.save(f'{path}.png')
779
+ return True
780
+ except:
781
+ return False
782
+
783
+ def popup_image(filepath, title: str='Image Popup', errfile: str='File not found or bad', errload: str='Error loading file'):
784
+ popup = tk.Toplevel()
785
+ popup.title(title)
786
+ try:
787
+ img = Image.open(filepath)
788
+ tkimg = ImageTk.PhotoImage(img)
789
+ label = tk.Label(popup, image=tkimg)
790
+ label.image = tkimg
791
+ label.pack()
792
+ except FileNotFoundError:
793
+ label = tk.Label(popup, text=f'FileError: {errfile}')
794
+ label.pack()
795
+ except Exception as e:
796
+ label = tk.Label(popup, text=f'LoadError: {errload}: {e}')
797
+ label.pack()
798
+
799
+ def window_image(filepath: str, title: str='Image Window'):
800
+ root = tk.Tk()
801
+ root.title(title)
802
+ tkimg = ImageTk.PhotoImage(Image.open(filepath))
803
+ label = tk.Label(root, image=tkimg)
804
+ label.pack()
805
+ root.mainloop()
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: formatize
3
+ Version: 1.0.0
4
+ Summary: Literp Formatting Lib
5
+ Home-page: https://github.com/asinerum/formatize
6
+ Author: Asinerum Conlang Project
7
+ Author-email: asinerum.com@gmail.com
8
+ License: MIT
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.7
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: license-file
16
+
17
+ Detailed tips, tricks, and examples, can be found at project's repository
18
+ https://github.com/asinerum/formatize
19
+
20
+ (C) 2026 Asinerum Conlang Project
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.cfg
5
+ src/formatize/__init__.py
6
+ src/formatize/form.py
7
+ src/formatize.egg-info/PKG-INFO
8
+ src/formatize.egg-info/SOURCES.txt
9
+ src/formatize.egg-info/dependency_links.txt
10
+ src/formatize.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ formatize