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.
- iad/core/__init__.py +9 -0
- iad/core/array.py +1961 -0
- iad/core/binary.py +377 -0
- iad/core/cache.py +903 -0
- iad/core/codetools.py +203 -0
- iad/core/datatools.py +671 -0
- iad/core/docs/locators.ipynb +754 -0
- iad/core/dotstyle.py +99 -0
- iad/core/env.py +271 -0
- iad/core/events.py +650 -0
- iad/core/filesproc.py +1046 -0
- iad/core/fnctools.py +390 -0
- iad/core/label.py +240 -0
- iad/core/logs.py +182 -0
- iad/core/nptools.py +449 -0
- iad/core/one_dark.puml +881 -0
- iad/core/param/__init__.py +17 -0
- iad/core/param/confargparse.py +55 -0
- iad/core/param/paramaze.py +339 -0
- iad/core/param/tbox.py +277 -0
- iad/core/paths.py +563 -0
- iad/core/pdtools.py +2570 -0
- iad/core/pydantools/__init__.py +5 -0
- iad/core/pydantools/fixed_pydantic_yaml/__init__.py +32 -0
- iad/core/pydantools/fixed_pydantic_yaml/compat/__init__.py +0 -0
- iad/core/pydantools/fixed_pydantic_yaml/compat/hacks.py +76 -0
- iad/core/pydantools/fixed_pydantic_yaml/compat/old_enums.py +37 -0
- iad/core/pydantools/fixed_pydantic_yaml/compat/representers.py +92 -0
- iad/core/pydantools/fixed_pydantic_yaml/compat/types.py +122 -0
- iad/core/pydantools/fixed_pydantic_yaml/compat/yaml_lib.py +104 -0
- iad/core/pydantools/fixed_pydantic_yaml/ext/__init__.py +1 -0
- iad/core/pydantools/fixed_pydantic_yaml/ext/semver.py +152 -0
- iad/core/pydantools/fixed_pydantic_yaml/ext/versioned_model.py +113 -0
- iad/core/pydantools/fixed_pydantic_yaml/main.py +30 -0
- iad/core/pydantools/fixed_pydantic_yaml/mixin.py +281 -0
- iad/core/pydantools/fixed_pydantic_yaml/model.py +20 -0
- iad/core/pydantools/fixed_pydantic_yaml/py.typed +1 -0
- iad/core/pydantools/fixed_pydantic_yaml/version.py +1 -0
- iad/core/pydantools/models.py +560 -0
- iad/core/regexp.py +348 -0
- iad/core/short.py +308 -0
- iad/core/strings.py +635 -0
- iad/core/unc_panda.py +270 -0
- iad/core/units.py +58 -0
- iad/core/wrap.py +420 -0
- ialdev_core-0.1.0.dist-info/METADATA +73 -0
- ialdev_core-0.1.0.dist-info/RECORD +48 -0
- 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
|