ialdev-core 0.1.0__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.
Files changed (48) hide show
  1. iad/core/__init__.py +9 -0
  2. iad/core/array.py +1961 -0
  3. iad/core/binary.py +377 -0
  4. iad/core/cache.py +903 -0
  5. iad/core/codetools.py +203 -0
  6. iad/core/datatools.py +671 -0
  7. iad/core/docs/locators.ipynb +754 -0
  8. iad/core/dotstyle.py +99 -0
  9. iad/core/env.py +271 -0
  10. iad/core/events.py +650 -0
  11. iad/core/filesproc.py +1046 -0
  12. iad/core/fnctools.py +390 -0
  13. iad/core/label.py +240 -0
  14. iad/core/logs.py +182 -0
  15. iad/core/nptools.py +449 -0
  16. iad/core/one_dark.puml +881 -0
  17. iad/core/param/__init__.py +17 -0
  18. iad/core/param/confargparse.py +55 -0
  19. iad/core/param/paramaze.py +339 -0
  20. iad/core/param/tbox.py +277 -0
  21. iad/core/paths.py +563 -0
  22. iad/core/pdtools.py +2570 -0
  23. iad/core/pydantools/__init__.py +5 -0
  24. iad/core/pydantools/fixed_pydantic_yaml/__init__.py +32 -0
  25. iad/core/pydantools/fixed_pydantic_yaml/compat/__init__.py +0 -0
  26. iad/core/pydantools/fixed_pydantic_yaml/compat/hacks.py +76 -0
  27. iad/core/pydantools/fixed_pydantic_yaml/compat/old_enums.py +37 -0
  28. iad/core/pydantools/fixed_pydantic_yaml/compat/representers.py +92 -0
  29. iad/core/pydantools/fixed_pydantic_yaml/compat/types.py +122 -0
  30. iad/core/pydantools/fixed_pydantic_yaml/compat/yaml_lib.py +104 -0
  31. iad/core/pydantools/fixed_pydantic_yaml/ext/__init__.py +1 -0
  32. iad/core/pydantools/fixed_pydantic_yaml/ext/semver.py +152 -0
  33. iad/core/pydantools/fixed_pydantic_yaml/ext/versioned_model.py +113 -0
  34. iad/core/pydantools/fixed_pydantic_yaml/main.py +30 -0
  35. iad/core/pydantools/fixed_pydantic_yaml/mixin.py +281 -0
  36. iad/core/pydantools/fixed_pydantic_yaml/model.py +20 -0
  37. iad/core/pydantools/fixed_pydantic_yaml/py.typed +1 -0
  38. iad/core/pydantools/fixed_pydantic_yaml/version.py +1 -0
  39. iad/core/pydantools/models.py +560 -0
  40. iad/core/regexp.py +348 -0
  41. iad/core/short.py +308 -0
  42. iad/core/strings.py +635 -0
  43. iad/core/unc_panda.py +270 -0
  44. iad/core/units.py +58 -0
  45. iad/core/wrap.py +420 -0
  46. ialdev_core-0.1.0.dist-info/METADATA +73 -0
  47. ialdev_core-0.1.0.dist-info/RECORD +48 -0
  48. ialdev_core-0.1.0.dist-info/WHEEL +4 -0
iad/core/dotstyle.py ADDED
@@ -0,0 +1,99 @@
1
+ import re
2
+
3
+ from pygraphviz import AGraph
4
+
5
+
6
+ class IGraph(AGraph):
7
+ """ See ./tests/test_dotsyle.py for examples """
8
+
9
+ def __init__(self, thing=None, filename=None, data=None,
10
+ string=None, handle=None, name='', strict=True,
11
+ directed=False, styles=None, **attr, ):
12
+ """
13
+ Add styles kw to AGRaph support DotStyle aliases in the dot format.
14
+ """
15
+ self.styles = {} if styles is None else styles
16
+
17
+ if string:
18
+ for s in self.styles:
19
+ string = re.sub(fr'(\[[^\]]*?){s}\b', fr'\1{styles[s].to_str()}', string)
20
+ # print(string)
21
+ super().__init__(thing=thing, filename=filename, data=data, string=string,
22
+ handle=handle, name=name, strict=strict, directed=directed, **attr)
23
+
24
+ def copy(self):
25
+ from copy import deepcopy
26
+ res = super().copy()
27
+ res.styles = deepcopy(self.styles)
28
+ return res
29
+
30
+ def _repr_svg_(self):
31
+ return str(self.draw(prog='dot', format='svg'))
32
+
33
+ def select_edges(self, chain: str):
34
+ for p in '-,>':
35
+ chain = chain.replace(p, ' ')
36
+ chain = chain.split()
37
+ return [self.get_edge(*e) for e in zip(chain[:-1], chain[1:])]
38
+
39
+ def style_edges(self, edges, attrs):
40
+ if isinstance(edges, str):
41
+ edges = self.select_edges(edges)
42
+ if isinstance(attrs, str):
43
+ attrs = self.styles[attrs]
44
+ for e in edges:
45
+ e.attr.update(attrs)
46
+
47
+ def delete_node_edges(self, node, in_edges=True, out_edges=True):
48
+ """ Delete the node with its edges."""
49
+ if in_edges: self.delete_edges_from(self.in_edges(node))
50
+ if out_edges: self.delete_edges_from(self.out_edges(node))
51
+ self.delete_node(node)
52
+
53
+
54
+ class DotStyle(dict):
55
+ """
56
+ Provides "styles" arithmetic for dot format
57
+ See ./tests/test_dotsyle.py for examples
58
+ """
59
+ def __init__(self, val=None, **kw):
60
+ """
61
+ Dot styles as dict
62
+ """
63
+ val = self.from_str(val) if isinstance(val, str) else {} if val is None else val
64
+ assert isinstance(val, dict)
65
+ val.update(kw)
66
+ super().__init__(val)
67
+
68
+ @staticmethod
69
+ def from_str(val: str):
70
+ import re
71
+
72
+ def parse_val(s, sv):
73
+ if sv:
74
+ for op in (int, float, str):
75
+ try:
76
+ s = op(sv)
77
+ except:
78
+ continue # if failed - continue for the next casting attempt
79
+ break # otherwise - exit with success
80
+ return s # last cast option -> str always succeeds
81
+
82
+ found = re.findall(r'(\w+)\s*=\s*(?:"([^"]+)"|([^\s]+))', val)
83
+ return {key: parse_val(s, sv) for key, s, sv in found}
84
+
85
+ def to_str(self):
86
+ return ' '.join([key + '=' + (f'"{v}"' if isinstance(v, str) and ' ' in v
87
+ else f'{v}') for key, v in self.items()])
88
+
89
+ def __iadd__(self, other):
90
+ self.update(other)
91
+ return self
92
+
93
+ def __add__(self, other):
94
+ res = self.__class__(self)
95
+ res.update(other)
96
+ return res
97
+
98
+ def __radd__(self, other):
99
+ return self.__class__(other) + self
iad/core/env.py ADDED
@@ -0,0 +1,271 @@
1
+ import logging
2
+ import os
3
+ from pathlib import Path
4
+ import dotenv
5
+
6
+ from iad.core.filesproc import Locator
7
+
8
+ env_log = logging.getLogger('env')
9
+
10
+ # make sure WARNING + levels of env log ALSO appear in stderr
11
+ if not env_log.handlers:
12
+ handler = logging.StreamHandler()
13
+ handler.setLevel('WARNING')
14
+ env_log.addHandler(handler)
15
+
16
+ # -------------------- Environment variables names --------------------------
17
+ # Those variables can be set in process environment to
18
+ # change EnvLoc initialization behaviour
19
+
20
+ ENV_FILE_VAR = 'ALG_DOTENV_FILE' # change path to DEFAULT dotenv file (algodev/.env)
21
+ ENV_OVER_VAR = 'ALG_DOTENV_OVER' # IF set to 'true' dotenv file OVERRIDE system variables
22
+ TEMP_VAR = 'TEMP' # if not defined tempfile.gettempdir() is used
23
+ # init temporal folder
24
+ if not os.getenv(TEMP_VAR):
25
+ import tempfile
26
+
27
+ os.environ[TEMP_VAR] = tempfile.gettempdir()
28
+
29
+ # default behaviour if ALG_DOTENV_OVER is not set
30
+ def _env_override_policy(override, default='false'):
31
+ """
32
+ Applies decisions policy for determining dotenv environment override behaviour.
33
+
34
+ Note, that override=True means that .env overrides the system environment
35
+
36
+ - if `True` | `False` - defines the override explicitely
37
+ - if `None`:
38
+ - read override from `ALG_DOTENV_OVER` environment variable if defined
39
+ - use `default` if not defined
40
+
41
+ :param override: the value to start from
42
+ :param default: default value for `ALG_DOTENV_OVER` if not set by environment
43
+ :return:
44
+ """
45
+ # set `ovveride` value from the argument or envar ENV_OVER_VAR
46
+ if override not in (True, False, None):
47
+ raise ValueError(f"Invalid value for {override=}")
48
+
49
+ if override is None:
50
+ override = os.getenv(ENV_OVER_VAR, default).lower()
51
+ if override in {'yes', 'true', '1'}:
52
+ override = True
53
+ elif override in {'no', 'false', '0'}:
54
+ override = False
55
+ else:
56
+ raise ValueError(f"Invalid value of {ENV_OVER_VAR}={override}")
57
+ env_log.debug(f'dotenv {override=} sys env ({ENV_OVER_VAR}={os.getenv(ENV_OVER_VAR)})')
58
+ return override
59
+
60
+
61
+ class EnvLoc:
62
+ """Singleton-style bundle of ``Locator``s for algutils-wide paths.
63
+
64
+ Customizable per working environment using ``reset``.
65
+ """
66
+ # LOCAL_DATA = Locator(envar='ALG_LOCAL_DATA')
67
+ # NET_DATA = Locator(envar='ALG_NET_DATA', alarm=FileExistsError)
68
+ # DATA = Locator(envar='ALG_DATA', alarm=FileExistsError)
69
+ # DATASETS = Locator(envar='ALG_DATASETS', alarm=_log.error)
70
+ # RESOURCES = Locator(envar='ALG_RESOURCES')
71
+
72
+ _last_set = None # dotenv file used to set the current state
73
+ _initial_envars = None # initial envars from os.environment used by locators
74
+
75
+ @classmethod
76
+ def safe_locators(cls, status=None):
77
+ """
78
+ If status is `None` print *safe* attribute of the locators.
79
+
80
+ Otherwise set all the locators to the given `status` which must be bool or 0 < timeout < 10.
81
+
82
+ Individual locators safe attributes can be set directly.
83
+ """
84
+ if status is None:
85
+ for name, loc in cls.locators().items():
86
+ print(f"{name:>20s}\t{loc.safe:>6}")
87
+ elif isinstance(status, bool) or \
88
+ isinstance(status, (int, float)) and 0 < status < 5:
89
+ for loc in cls.locators().values():
90
+ loc.safe = status
91
+ else:
92
+ raise ValueError(f"Invalid {status=}")
93
+
94
+ @classmethod
95
+ def last_set(cls) -> str | None:
96
+ """Return last set environment, ``None`` if never set.
97
+
98
+ Environment is usually set using ``env.setup()`` or ``env.EnvLoc.reset()``
99
+ """
100
+ return cls._last_set
101
+
102
+ @classmethod
103
+ def locators(cls) -> dict[str, Locator]:
104
+ return {name: loc for name, loc in cls.__dict__.items() if isinstance(loc, Locator)}
105
+
106
+ @classmethod
107
+ def repr(cls, existing=False) -> str:
108
+ """
109
+ String representation of the current EnvLoc state
110
+ :return:
111
+ """
112
+ def max_line_len(strings):
113
+ """Caclulate length of mazimal line (\n separated) among all, the strings"""
114
+ lines = (line for s in strings for line in s.split('\n'))
115
+ return len(max(lines, key=len))
116
+
117
+ locs = cls.locators()
118
+ fmt = f'{{:>{max_line_len(locs) + 2}s}}: {{}}'
119
+
120
+ locs = [fmt.format(name, loc.repr(existing)) for name, loc in locs.items()]
121
+ head = f" Current EnvLoc (dotfile: {cls._last_set})"
122
+ locs_len, head_len = max_line_len(locs), len(head)
123
+ if head_len < locs_len:
124
+ head += ' ' * (locs_len - head_len)
125
+ head_len = locs_len
126
+ sep = '─' * head_len
127
+ return '\n'.join(['┌' + sep + '┐', '│' + head + '│', '└' + sep + '┘', *locs])
128
+
129
+ @classmethod
130
+ def validate(cls, alarm: Locator.AlarmTypes | None = None):
131
+ """
132
+ Run validation on all the locators with given alarm
133
+ (``None`` means using their own alarms set in constructor)
134
+ :param alarm:
135
+ :return:
136
+ """
137
+ for loc in cls.locators().values():
138
+ # loc.set_check_opt()
139
+ loc.validate(alarm)
140
+
141
+ @classmethod
142
+ def reset(cls, env_path=None, override=None, validate: bool=True) -> bool:
143
+ """Reset locators after loading from env file described by either:
144
+ - explicit path to the env file
145
+ - folder to start loking from (by climbing up)
146
+ - `None` the try in this order:
147
+ - ``ALG_DOTENV_FILE`` environment variable
148
+ - find starting up from the ``CWD``
149
+ - finally default `.env` under the ``algutils`` package (``src/algutils``)
150
+
151
+ :param env_path: full or relative path, name in the tree above, `None` for default ``.env``
152
+ :param override: ``True`` override envars by .env content.
153
+ ``None`` - follow ``ALG_DOTENV_OVER`` envar.
154
+ :param validate: if `True` check and log if locators point to at least one actual folder
155
+ :return: `True` if environment was reset, `False` if not found
156
+ """
157
+ # optional absolute or relative path to env file - if not given, uses default .env in the project
158
+
159
+ cls.reset_os_environ()
160
+ if not (env_path := cls._locate_dotenv(env_path, fail=validate)):
161
+ return False
162
+
163
+ cls._process_dotenv(env_path, override)
164
+ cls._last_set = str(Path(env_path).absolute()) # keep last set environment
165
+
166
+ for loc in cls.locators().values():
167
+ loc.safe = bool(loc.alarm)
168
+
169
+ if validate:
170
+ cls.validate()
171
+ return True
172
+
173
+ @classmethod
174
+ def _process_dotenv(cls, env_file, override=None):
175
+ from iad.core.filesproc import represents_path, normalize
176
+
177
+ def same_value(v1, v2):
178
+ """Return true if two values are the same.
179
+ If they represent paths normalize before comparing.
180
+ """
181
+ if v1 and v2 and any(map(represents_path, [v1, v2])):
182
+ return normalize(v1) == normalize(v2)
183
+ return v1 == v2
184
+
185
+ override = _env_override_policy(override)
186
+ env_log.warning(f"Set environment from '{env_file}', {override=} vars in sys env")
187
+
188
+ # validate that `env_file` contains only expected vars (defined in `cls`)
189
+ expected = {loc.envar for loc in cls.locators().values()}
190
+ denvars = {n: v for n, v in dotenv.dotenv_values(env_file).items()}
191
+ from_denv = {n for n, v in denvars.items() if v is not None}
192
+ if unknown := from_denv - expected:
193
+ msg = f"Some env vars in {env_file} are {unknown = }"
194
+ env_log.error(msg)
195
+ raise NameError(msg)
196
+
197
+ # find vars defined in both os.environment AND in `env_file`
198
+ # and INFORM about which will be selected according to `override`
199
+ if redefined := expected.intersection(os.environ, from_denv):
200
+ side, act = ('←', 'overridden by') if override else ('→', 'override')
201
+ if redefined := [
202
+ f"{n}: {enval} {side} {denval}" for n in redefined
203
+ if not same_value(enval := os.getenv(n), denval := denvars[n])
204
+ ]:
205
+ env_log.warning(f"Envars {act} dotenv file {env_file}:\n\t{', '.join(redefined)}")
206
+ dotenv.load_dotenv(env_file, verbose=True, override=override)
207
+
208
+ @staticmethod
209
+ def _locate_dotenv(env_path, fail=True):
210
+ """Locate dot env file given folderm file name or full path.
211
+
212
+ :param env_path: file name | full path | folder containing .env
213
+ :return: full path to located file or raise ``FileNotFoundError``
214
+ """
215
+
216
+ def find_dotenv_dir(folder, filename='.env', raise_error_if_not_found=False):
217
+ """Runs find_doten from given folder"""
218
+ cwd = os.getcwd()
219
+ os.chdir(folder)
220
+ found_env = dotenv.find_dotenv(
221
+ filename,
222
+ raise_error_if_not_found=raise_error_if_not_found
223
+ )
224
+ os.chdir(cwd)
225
+ return found_env
226
+
227
+ if env_path: # hint provided: folder | file_name | full_path
228
+ if (_path := Path(env_path)).is_dir():
229
+ env_log.debug('Looking .env in from requested folder {_path}...')
230
+ env_path = find_dotenv_dir(env_path, raise_error_if_not_found=True)
231
+ elif not _path.is_file():
232
+ env_path = dotenv.find_dotenv(env_path, raise_error_if_not_found=True)
233
+ elif env_path := os.getenv(ENV_FILE_VAR, None): # not provided - try to read from environmet
234
+ if not Path(env_path).is_file():
235
+ if not fail: return None
236
+ raise FileNotFoundError(f'Not found dotenv defined in {ENV_FILE_VAR}="{env_path}"')
237
+ env_log.debug(f'dotenv set by the environmnet variable {ENV_FILE_VAR}={env_path}')
238
+ elif not (env_path := dotenv.find_dotenv(usecwd=True)): # try first search from the current dir
239
+ msg = f'Getting default .env after not found at CDW: {os.getcwd()}'
240
+ env_log.error(msg)
241
+ if not fail: return None
242
+ raise FileNotFoundError(msg)
243
+ return env_path
244
+
245
+ @classmethod
246
+ def reset_os_environ(cls, **fixes):
247
+ """
248
+ Ensures that os.environ variables used by locators are reset to their intital state.
249
+
250
+ Requires since locators keep environment variables names, but not values,
251
+ they are reading `os.environ` every time location is requested.
252
+ That makes them sensitive to changes in those values.
253
+
254
+ As a hacking tool, the initial system environment may be changed with `fixes`.
255
+ """
256
+ if cls._initial_envars is None: # save initial values
257
+ cls._initial_envars = {
258
+ loc.envar: os.getenv(loc.envar)
259
+ for loc in cls.locators().values() if loc.envar
260
+ }
261
+ cls._initial_envars.update(fixes) # not really can happen here
262
+ else: # restore
263
+ cls._initial_envars.update(fixes) # fix the "initial" state
264
+ for k, v in cls._initial_envars.items():
265
+ if v is None:
266
+ os.environ.pop(k, None)
267
+ else:
268
+ os.environ[k] = v
269
+
270
+
271
+ # EnvLoc.reset(validate=False) # set by environnet variable or default