encommon 0.13.1__py3-none-any.whl → 0.14.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 (46) hide show
  1. encommon/__init__.py +2 -7
  2. encommon/colors/__init__.py +14 -0
  3. encommon/colors/colors.py +518 -0
  4. encommon/colors/test/__init__.py +6 -0
  5. encommon/colors/test/test_colors.py +189 -0
  6. encommon/config/config.py +73 -20
  7. encommon/config/files.py +3 -0
  8. encommon/config/logger.py +5 -0
  9. encommon/config/params.py +1 -2
  10. encommon/config/paths.py +3 -0
  11. encommon/config/test/test_config.py +5 -1
  12. encommon/config/test/test_logger.py +13 -8
  13. encommon/config/test/test_paths.py +1 -1
  14. encommon/config/utils.py +7 -1
  15. encommon/conftest.py +33 -22
  16. encommon/crypts/params.py +23 -4
  17. encommon/times/__init__.py +3 -1
  18. encommon/times/common.py +2 -1
  19. encommon/times/params.py +38 -8
  20. encommon/times/parse.py +37 -10
  21. encommon/times/test/test_parse.py +16 -9
  22. encommon/times/test/test_times.py +35 -2
  23. encommon/times/test/test_unitime.py +23 -0
  24. encommon/times/times.py +71 -13
  25. encommon/times/unitime.py +48 -0
  26. encommon/types/__init__.py +20 -2
  27. encommon/types/classes.py +97 -0
  28. encommon/types/lists.py +27 -0
  29. encommon/types/strings.py +29 -4
  30. encommon/types/test/test_classes.py +74 -0
  31. encommon/types/test/test_lists.py +23 -0
  32. encommon/types/test/test_strings.py +15 -3
  33. encommon/types/types.py +20 -0
  34. encommon/utils/__init__.py +4 -0
  35. encommon/utils/paths.py +5 -6
  36. encommon/utils/sample.py +117 -41
  37. encommon/utils/stdout.py +51 -5
  38. encommon/utils/test/test_sample.py +127 -28
  39. encommon/utils/test/test_stdout.py +91 -27
  40. encommon/version.txt +1 -1
  41. {encommon-0.13.1.dist-info → encommon-0.14.0.dist-info}/METADATA +1 -1
  42. encommon-0.14.0.dist-info/RECORD +84 -0
  43. {encommon-0.13.1.dist-info → encommon-0.14.0.dist-info}/WHEEL +1 -1
  44. encommon-0.13.1.dist-info/RECORD +0 -73
  45. {encommon-0.13.1.dist-info → encommon-0.14.0.dist-info}/LICENSE +0 -0
  46. {encommon-0.13.1.dist-info → encommon-0.14.0.dist-info}/top_level.txt +0 -0
encommon/types/strings.py CHANGED
@@ -21,22 +21,24 @@ SPACED = ' '
21
21
 
22
22
 
23
23
 
24
- def striplower(
25
- value: str,
24
+ def strplwr(
25
+ value: Any, # noqa: ANN401
26
26
  ) -> str:
27
27
  """
28
28
  Return the provided string but stripped and lower cased.
29
29
 
30
30
  Example
31
31
  -------
32
- >>> striplower(' Foo ')
32
+ >>> strplwr(' Foo ')
33
33
  'foo'
34
34
 
35
35
  :param value: String which will be stripped and lowered.
36
36
  :returns: Provided string but stripped and lower cased.
37
37
  """
38
38
 
39
- return value.strip().lower()
39
+ _value = str(value)
40
+
41
+ return _value.strip().lower()
40
42
 
41
43
 
42
44
 
@@ -108,3 +110,26 @@ def instr(
108
110
  """
109
111
 
110
112
  return hasstr(str(haystack), needle)
113
+
114
+
115
+
116
+ def rplstr(
117
+ source: str,
118
+ match: str,
119
+ value: Any, # noqa: ANN401
120
+ ) -> str:
121
+ """
122
+ Return the source string with the match value replaced.
123
+
124
+ :param source: String that to be processed and returned.
125
+ :param match: What will be replaced within the string.
126
+ :param value: Replace value for the string is matched.
127
+ :returns: Source string with the match value replaced.
128
+ """
129
+
130
+ match = str(match)
131
+ value = str(value)
132
+
133
+ return (
134
+ str(source)
135
+ .replace(match, value))
@@ -0,0 +1,74 @@
1
+ """
2
+ Functions and routines associated with Enasis Network Common Library.
3
+
4
+ This file is part of Enasis Network software eco-system. Distribution
5
+ is permitted, for more information consult the project license file.
6
+ """
7
+
8
+
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ from ..classes import clsname
13
+ from ..classes import lattrs
14
+ from ...config import LoggerParams
15
+
16
+ if TYPE_CHECKING:
17
+ from ...config import Config
18
+
19
+
20
+
21
+ def test_BaseModel() -> None:
22
+ """
23
+ Perform various tests associated with relevant routines.
24
+ """
25
+
26
+ params = LoggerParams(
27
+ stdo_level='info')
28
+
29
+ dumped = params.model_dumped
30
+ pruned = params.model_pruned
31
+
32
+ assert dumped == {
33
+ 'file_level': None,
34
+ 'file_path': None,
35
+ 'stdo_level': 'info'}
36
+
37
+ assert pruned == {
38
+ 'stdo_level': 'info'}
39
+
40
+
41
+
42
+ def test_clsname(
43
+ config: 'Config',
44
+ ) -> None:
45
+ """
46
+ Perform various tests associated with relevant routines.
47
+
48
+ :param config: Primary class instance for configuration.
49
+ """
50
+
51
+ assert clsname(config) == 'Config'
52
+
53
+
54
+
55
+ def test_lattrs(
56
+ config: 'Config',
57
+ ) -> None:
58
+ """
59
+ Perform various tests associated with relevant routines.
60
+
61
+ :param config: Primary class instance for configuration.
62
+ """
63
+
64
+ attrs = lattrs(config)
65
+
66
+ assert attrs == [
67
+ '_Config__model',
68
+ '_Config__files',
69
+ '_Config__cargs',
70
+ '_Config__sargs',
71
+ '_Config__params',
72
+ '_Config__paths',
73
+ '_Config__logger',
74
+ '_Config__crypts']
@@ -0,0 +1,23 @@
1
+ """
2
+ Functions and routines associated with Enasis Network Common Library.
3
+
4
+ This file is part of Enasis Network software eco-system. Distribution
5
+ is permitted, for more information consult the project license file.
6
+ """
7
+
8
+
9
+
10
+ from ..lists import inlist
11
+
12
+
13
+
14
+ def test_inlist() -> None:
15
+ """
16
+ Perform various tests associated with relevant routines.
17
+ """
18
+
19
+ needle = 123
20
+
21
+ haystack = [123, 456]
22
+
23
+ assert inlist(needle, haystack)
@@ -10,16 +10,17 @@ is permitted, for more information consult the project license file.
10
10
  from ..strings import hasstr
11
11
  from ..strings import inrepr
12
12
  from ..strings import instr
13
- from ..strings import striplower
13
+ from ..strings import rplstr
14
+ from ..strings import strplwr
14
15
 
15
16
 
16
17
 
17
- def test_striplower() -> None:
18
+ def test_strplwr() -> None:
18
19
  """
19
20
  Perform various tests associated with relevant routines.
20
21
  """
21
22
 
22
- assert striplower(' Foo ') == 'foo'
23
+ assert strplwr(' Foo ') == 'foo'
23
24
 
24
25
 
25
26
 
@@ -59,3 +60,14 @@ def test_instr() -> None:
59
60
  item = MyClass()
60
61
 
61
62
  assert instr('MyClass', item)
63
+
64
+
65
+
66
+ def test_rplstr() -> None:
67
+ """
68
+ Perform various tests associated with relevant routines.
69
+ """
70
+
71
+ string = rplstr('foo', 'o', 'O')
72
+
73
+ assert string == 'fOO'
@@ -0,0 +1,20 @@
1
+ """
2
+ Functions and routines associated with Enasis Network Common Library.
3
+
4
+ This file is part of Enasis Network software eco-system. Distribution
5
+ is permitted, for more information consult the project license file.
6
+ """
7
+
8
+
9
+
10
+ from typing import Any
11
+
12
+
13
+
14
+ DictStrAny = dict[str, Any]
15
+
16
+
17
+
18
+ NCTrue = True
19
+ NCFalse = False
20
+ NCNone = None
@@ -16,6 +16,8 @@ from .paths import resolve_paths
16
16
  from .paths import stats_path
17
17
  from .sample import load_sample
18
18
  from .sample import prep_sample
19
+ from .sample import read_sample
20
+ from .sample import rvrt_sample
19
21
  from .stdout import array_ansi
20
22
  from .stdout import kvpair_ansi
21
23
  from .stdout import make_ansi
@@ -32,7 +34,9 @@ __all__ = [
32
34
  'make_ansi',
33
35
  'prep_sample',
34
36
  'print_ansi',
37
+ 'read_sample',
35
38
  'read_text',
39
+ 'rvrt_sample',
36
40
  'resolve_path',
37
41
  'resolve_paths',
38
42
  'rgxp_match',
encommon/utils/paths.py CHANGED
@@ -13,6 +13,7 @@ from typing import Optional
13
13
  from typing import TYPE_CHECKING
14
14
 
15
15
  from .match import rgxp_match
16
+ from ..types import rplstr
16
17
  from ..types import sort_dict
17
18
 
18
19
  if TYPE_CHECKING:
@@ -50,13 +51,11 @@ def resolve_path(
50
51
 
51
52
  for old, new in items:
52
53
 
53
- if isinstance(old, Path):
54
- old = str(old)
54
+ old = str(old)
55
+ new = str(new)
55
56
 
56
- if isinstance(new, Path):
57
- new = str(new)
58
-
59
- path = path.replace(old, new)
57
+ path = rplstr(
58
+ path, old, new)
60
59
 
61
60
  return Path(path).resolve()
62
61
 
encommon/utils/sample.py CHANGED
@@ -7,54 +7,79 @@ is permitted, for more information consult the project license file.
7
7
 
8
8
 
9
9
 
10
+ from dataclasses import asdict
11
+ from dataclasses import is_dataclass
10
12
  from json import dumps
11
- from json import loads
13
+ from os import environ
12
14
  from pathlib import Path
13
15
  from typing import Any
14
16
  from typing import Callable
15
17
  from typing import Optional
16
- from typing import TYPE_CHECKING
18
+
19
+ from pydantic import BaseModel
17
20
 
18
21
  from .files import read_text
19
22
  from .files import save_text
23
+ from ..types import rplstr
24
+
25
+
26
+
27
+ PREFIX = 'encommon_sample'
20
28
 
21
- if TYPE_CHECKING:
22
- from .common import REPLACE
29
+ ENPYRWS = (
30
+ environ.get('ENPYRWS') == '1')
23
31
 
24
32
 
25
33
 
26
- def prep_sample(
34
+ def prep_sample( # noqa: CFQ004
27
35
  content: Any,
28
36
  *,
29
37
  default: Callable[[Any], str] = str,
30
- replace: Optional['REPLACE'] = None,
31
- ) -> Any:
32
- """
33
- Return the content after processing using JSON functions.
34
-
35
- .. testsetup::
36
- >>> from ..types import Empty
38
+ replace: Optional[dict[str, Any]] = None,
39
+ indent: Optional[int] = 2,
40
+ ) -> str:
41
+ r"""
42
+ Return the content after processing as the sample value.
37
43
 
38
44
  Example
39
45
  -------
40
46
  >>> prep_sample(['one', 'two'])
41
- ['one', 'two']
47
+ '[\n "one",\n "two"\n]'
42
48
 
43
49
  Example
44
50
  -------
51
+ >>> from ..types import Empty
45
52
  >>> prep_sample({'one': Empty})
46
- {'one': 'Empty'}
53
+ '{\n "one": "Empty"\n}'
47
54
 
48
55
  :param content: Content that will be processed as JSON.
49
56
  :param default: Callable used when stringifying values.
50
57
  :param replace: Optional values to replace in the file.
51
- :returns: Content after processing using JSON functions.
58
+ :returns: Content after processing as the sample value.
52
59
  """
53
60
 
54
- content = dumps(
55
- content, default=default)
56
61
 
57
- prefix = 'encommon_sample'
62
+ def _default(
63
+ value: Any, # noqa: ANN401
64
+ ) -> dict[str, Any] | str:
65
+
66
+ if is_dataclass(value):
67
+
68
+ assert not isinstance(
69
+ value, type)
70
+
71
+ return asdict(value)
72
+
73
+ if isinstance(value, BaseModel):
74
+ return value.model_dump()
75
+
76
+ return str(value)
77
+
78
+
79
+ content = dumps(
80
+ content,
81
+ default=_default,
82
+ indent=indent)
58
83
 
59
84
  replace = replace or {}
60
85
 
@@ -62,16 +87,14 @@ def prep_sample(
62
87
 
63
88
  for old, new in items:
64
89
 
65
- if isinstance(old, Path):
66
- old = str(old)
90
+ new = str(new)
67
91
 
68
- if isinstance(new, Path):
69
- new = str(new)
92
+ old = f'_/{PREFIX}/{old}/_'
70
93
 
71
- content = content.replace(
72
- new, f'_/{prefix}/{old}/_')
94
+ content = rplstr(
95
+ content, new, old)
73
96
 
74
- return loads(content)
97
+ return str(content)
75
98
 
76
99
 
77
100
 
@@ -81,9 +104,9 @@ def load_sample(
81
104
  update: bool = False,
82
105
  *,
83
106
  default: Callable[[Any], str] = str,
84
- replace: Optional['REPLACE'] = None,
85
- ) -> Any:
86
- """
107
+ replace: Optional[dict[str, Any]] = None,
108
+ ) -> str:
109
+ r"""
87
110
  Load the sample file and compare using provided content.
88
111
 
89
112
  .. testsetup::
@@ -96,14 +119,14 @@ def load_sample(
96
119
  -------
97
120
  >>> content = {'one': 'two'}
98
121
  >>> load_sample(sample, content)
99
- {'one': 'two'}
122
+ '{\n "one": "two"\n}'
100
123
 
101
124
  Example
102
125
  -------
103
126
  >>> load_sample(sample)
104
- {'one': 'two'}
127
+ '{\n "one": "two"\n}'
105
128
 
106
- :param path: Complete or relative path for the sample.
129
+ :param path: Complete or relative path for sample file.
107
130
  :param update: Determine whether the sample is updated.
108
131
  :param content: Content that will be processed as JSON.
109
132
  :param default: Callable used when stringifying values.
@@ -111,6 +134,7 @@ def load_sample(
111
134
  :returns: Content after processing using JSON functions.
112
135
  """
113
136
 
137
+
114
138
  path = Path(path).resolve()
115
139
 
116
140
  loaded: Optional[Any] = None
@@ -123,18 +147,11 @@ def load_sample(
123
147
 
124
148
 
125
149
  def _save_sample() -> None:
126
-
127
- dumped = dumps(
128
- content, indent=2)
129
-
130
- save_text(path, dumped)
150
+ save_text(path, content)
131
151
 
132
152
 
133
- def _load_sample() -> Any:
134
-
135
- loaded = read_text(path)
136
-
137
- return loads(loaded)
153
+ def _load_sample() -> str:
154
+ return read_text(path)
138
155
 
139
156
 
140
157
  if path.exists():
@@ -150,3 +167,62 @@ def load_sample(
150
167
 
151
168
 
152
169
  return _load_sample()
170
+
171
+
172
+
173
+ def read_sample(
174
+ sample: str,
175
+ *,
176
+ replace: Optional[dict[str, Any]] = None,
177
+ prefix: bool = True,
178
+ ) -> str:
179
+ """
180
+ Return the content after processing as the sample value.
181
+
182
+ :param sample: Content that will be processed as sample.
183
+ :param replace: Optional values to replace in the file.
184
+ :param prefix: Determine whether or not prefix is added.
185
+ :returns: Content after processing as the sample value.
186
+ """
187
+
188
+ replace = replace or {}
189
+
190
+ items = replace.items()
191
+
192
+ for new, old in items:
193
+
194
+ if prefix is True:
195
+ old = f'_/{PREFIX}/{old}/_'
196
+
197
+ sample = rplstr(
198
+ sample, new, old)
199
+
200
+ return str(sample)
201
+
202
+
203
+
204
+ def rvrt_sample(
205
+ sample: str,
206
+ *,
207
+ replace: Optional[dict[str, Any]] = None,
208
+ ) -> str:
209
+ """
210
+ Return the content after processing as the sample value.
211
+
212
+ :param sample: Content that will be processed as sample.
213
+ :param replace: Optional values to replace in the file.
214
+ :returns: Content after processing as the sample value.
215
+ """
216
+
217
+ replace = replace or {}
218
+
219
+ items = replace.items()
220
+
221
+ for new, old in items:
222
+
223
+ new = f'_/{PREFIX}/{new}/_'
224
+
225
+ sample = rplstr(
226
+ sample, new, old)
227
+
228
+ return str(sample)
encommon/utils/stdout.py CHANGED
@@ -7,23 +7,32 @@ is permitted, for more information consult the project license file.
7
7
 
8
8
 
9
9
 
10
+ from dataclasses import asdict
10
11
  from dataclasses import dataclass
12
+ from dataclasses import is_dataclass
11
13
  from re import compile
12
14
  from re import sub as re_sub
13
15
  from sys import stdout
14
16
  from typing import Any
15
17
  from typing import Literal
16
18
  from typing import Optional
19
+ from typing import TYPE_CHECKING
17
20
  from typing import Union
18
21
 
22
+ from pydantic import BaseModel
23
+
19
24
  from .common import JOINABLE
20
25
  from ..times import Duration
21
26
  from ..times import Times
22
27
  from ..types import Empty
28
+ from ..types import clsname
23
29
  from ..types.strings import COMMAD
24
30
  from ..types.strings import NEWLINE
25
31
  from ..types.strings import SEMPTY
26
32
 
33
+ if TYPE_CHECKING:
34
+ from _typeshed import DataclassInstance
35
+
27
36
 
28
37
 
29
38
  ANSICODE = compile(
@@ -34,7 +43,10 @@ ANSIARRAL = Union[
34
43
  tuple[Any, ...],
35
44
  set[Any]]
36
45
 
37
- ANSIARRAD = dict[Any, Any]
46
+ ANSIARRAD = Union[
47
+ dict[Any, Any],
48
+ BaseModel,
49
+ 'DataclassInstance']
38
50
 
39
51
  ANSIARRAY = Union[
40
52
  ANSIARRAL,
@@ -220,14 +232,21 @@ def array_ansi( # noqa: CFQ001, CFQ004
220
232
  'list': list,
221
233
  'tuple': tuple,
222
234
  'dict': dict,
235
+ 'BaseModel': BaseModel,
223
236
  'frozenset': frozenset,
224
237
  'set': set}
225
238
 
226
239
  items = typing.items()
227
240
 
228
- for name, _type in items:
241
+ for name, typed in items:
242
+
243
+ if isinstance(value, BaseModel):
244
+ name = clsname(value)
229
245
 
230
- if not isinstance(value, _type):
246
+ elif is_dataclass(value):
247
+ name = clsname(value)
248
+
249
+ elif not isinstance(value, typed):
231
250
  continue
232
251
 
233
252
  output.append(
@@ -288,6 +307,12 @@ def array_ansi( # noqa: CFQ001, CFQ004
288
307
  refers: Optional[set[int]] = None,
289
308
  ) -> None:
290
309
 
310
+ if isinstance(source, BaseModel):
311
+ source = source.model_dump()
312
+
313
+ if is_dataclass(source):
314
+ source = asdict(source)
315
+
291
316
  assert isinstance(source, dict)
292
317
 
293
318
  items = source.items()
@@ -325,19 +350,40 @@ def array_ansi( # noqa: CFQ001, CFQ004
325
350
 
326
351
 
327
352
  def _process(
328
- source: ANSIARRAD | ANSIARRAL,
353
+ source: ANSIARRAY,
329
354
  **kwargs: Any,
330
355
  ) -> None:
331
356
 
332
357
  if isinstance(source, dict):
333
358
  return _dict(source, **kwargs)
334
359
 
335
- return _list(source, **kwargs)
360
+ if isinstance(source, BaseModel):
361
+ return _dict(source, **kwargs)
362
+
363
+ if is_dataclass(source):
364
+ return _dict(source, **kwargs)
365
+
366
+ assert isinstance(
367
+ source,
368
+ set | list | tuple)
369
+
370
+ _list(source, **kwargs)
371
+
372
+
373
+ if is_dataclass(source):
374
+
375
+ assert not isinstance(
376
+ source, type)
377
+
378
+ source = asdict(source)
336
379
 
337
380
 
338
381
  if isinstance(source, dict):
339
382
  _dict(source, indent)
340
383
 
384
+ elif isinstance(source, BaseModel):
385
+ _dict(source, indent)
386
+
341
387
  elif isinstance(source, list):
342
388
  _list(source, indent)
343
389