ominfra 0.0.0.dev193__py3-none-any.whl → 0.0.0.dev195__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.
@@ -5,9 +5,9 @@ import dataclasses as dc
5
5
  import os.path
6
6
  import sys
7
7
 
8
+ from omlish.lite.configs import load_config_file_obj
8
9
  from omlish.logs.standard import configure_standard_logging
9
10
 
10
- from ....configs import read_config_file
11
11
  from .driver import JournalctlToAwsDriver
12
12
 
13
13
 
@@ -36,7 +36,10 @@ def _main() -> None:
36
36
 
37
37
  config: JournalctlToAwsDriver.Config
38
38
  if args.config_file:
39
- config = read_config_file(os.path.expanduser(args.config_file), JournalctlToAwsDriver.Config)
39
+ config = load_config_file_obj(
40
+ os.path.expanduser(args.config_file),
41
+ JournalctlToAwsDriver.Config,
42
+ )
40
43
  else:
41
44
  config = JournalctlToAwsDriver.Config()
42
45
 
@@ -26,8 +26,8 @@ from omlish.lite.json import json_dumps_pretty
26
26
  from omlish.lite.strings import strip_with_newline
27
27
  from omlish.os.paths import is_path_in_dir
28
28
  from omlish.os.paths import relative_symlink
29
- from omserv.nginx.configs import NginxConfigItems
30
- from omserv.nginx.configs import render_nginx_config_str
29
+ from omlish.configs.nginx import NginxConfigItems
30
+ from omlish.configs.nginx import render_nginx_config_str
31
31
 
32
32
  from omlish.formats.ini.sections import render_ini_sections
33
33
  from ..paths.paths import DeployPath
ominfra/manage/main.py CHANGED
@@ -17,12 +17,12 @@ from omlish.argparse.cli import argparse_arg
17
17
  from omlish.argparse.cli import argparse_cmd
18
18
  from omlish.lite.cached import cached_nullary
19
19
  from omlish.lite.check import check
20
+ from omlish.lite.configs import load_config_file_obj
20
21
  from omlish.lite.logs import log # noqa
21
22
  from omlish.lite.marshal import ObjMarshalerManager
22
23
  from omlish.lite.marshal import ObjMarshalOptions
23
24
  from omlish.lite.pycharm import PycharmRemoteDebug
24
25
 
25
- from ..configs import read_config_file
26
26
  from .bootstrap import MainBootstrap
27
27
  from .bootstrap_ import main_bootstrap
28
28
  from .commands.base import Command
@@ -51,7 +51,7 @@ class MainCli(ArgparseCli):
51
51
  if cf is None:
52
52
  return ManageConfig()
53
53
  else:
54
- return read_config_file(cf, ManageConfig)
54
+ return load_config_file_obj(cf, ManageConfig)
55
55
 
56
56
  #
57
57
 
@@ -122,7 +122,7 @@ class MainCli(ArgparseCli):
122
122
  cmds.append(cmd)
123
123
 
124
124
  for cf in self.args.command_file or []:
125
- cmd = read_config_file(cf, Command, msh=msh)
125
+ cmd = load_config_file_obj(cf, Command, msh=msh)
126
126
  cmds.append(cmd)
127
127
 
128
128
  #
@@ -9,6 +9,7 @@ import argparse
9
9
  import base64
10
10
  import collections
11
11
  import collections.abc
12
+ import configparser
12
13
  import contextlib
13
14
  import dataclasses as dc
14
15
  import datetime
@@ -52,6 +53,12 @@ if sys.version_info < (3, 8):
52
53
  ########################################
53
54
 
54
55
 
56
+ # ../../../../omlish/configs/types.py
57
+ ConfigMap = ta.Mapping[str, ta.Any]
58
+
59
+ # ../../../../omlish/formats/ini/sections.py
60
+ IniSectionSettingsMap = ta.Mapping[str, ta.Mapping[str, ta.Union[str, ta.Sequence[str]]]] # ta.TypeAlias
61
+
55
62
  # ../../../../omlish/formats/toml/parser.py
56
63
  TomlParseFloat = ta.Callable[[str], ta.Any]
57
64
  TomlKey = ta.Tuple[str, ...]
@@ -69,12 +76,12 @@ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
69
76
  CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
70
77
  CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
71
78
 
79
+ # ../../../../omlish/configs/formats.py
80
+ ConfigDataT = ta.TypeVar('ConfigDataT', bound='ConfigData')
81
+
72
82
  # ../../../../omlish/lite/contextmanagers.py
73
83
  ExitStackedT = ta.TypeVar('ExitStackedT', bound='ExitStacked')
74
84
 
75
- # ../../../configs.py
76
- ConfigMapping = ta.Mapping[str, ta.Any]
77
-
78
85
  # ../../../threadworkers.py
79
86
  ThreadWorkerT = ta.TypeVar('ThreadWorkerT', bound='ThreadWorker')
80
87
 
@@ -82,6 +89,54 @@ ThreadWorkerT = ta.TypeVar('ThreadWorkerT', bound='ThreadWorker')
82
89
  SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
83
90
 
84
91
 
92
+ ########################################
93
+ # ../../../../../omlish/configs/types.py
94
+
95
+
96
+ #
97
+
98
+
99
+ ########################################
100
+ # ../../../../../omlish/formats/ini/sections.py
101
+
102
+
103
+ ##
104
+
105
+
106
+ def extract_ini_sections(cp: configparser.ConfigParser) -> IniSectionSettingsMap:
107
+ config_dct: ta.Dict[str, ta.Any] = {}
108
+ for sec in cp.sections():
109
+ cd = config_dct
110
+ for k in sec.split('.'):
111
+ cd = cd.setdefault(k, {})
112
+ cd.update(cp.items(sec))
113
+ return config_dct
114
+
115
+
116
+ ##
117
+
118
+
119
+ def render_ini_sections(
120
+ settings_by_section: IniSectionSettingsMap,
121
+ ) -> str:
122
+ out = io.StringIO()
123
+
124
+ for i, (section, settings) in enumerate(settings_by_section.items()):
125
+ if i:
126
+ out.write('\n')
127
+
128
+ out.write(f'[{section}]\n')
129
+
130
+ for k, v in settings.items():
131
+ if isinstance(v, str):
132
+ out.write(f'{k}={v}\n')
133
+ else:
134
+ for vv in v:
135
+ out.write(f'{k}={vv}\n')
136
+
137
+ return out.getvalue()
138
+
139
+
85
140
  ########################################
86
141
  # ../../../../../omlish/formats/toml/parser.py
87
142
  # SPDX-License-Identifier: MIT
@@ -900,6 +955,129 @@ def toml_make_safe_parse_float(parse_float: TomlParseFloat) -> TomlParseFloat:
900
955
  return safe_parse_float
901
956
 
902
957
 
958
+ ########################################
959
+ # ../../../../../omlish/formats/toml/writer.py
960
+
961
+
962
+ class TomlWriter:
963
+ @dc.dataclass(frozen=True)
964
+ class Literal:
965
+ s: str
966
+
967
+ def __init__(self, out: ta.TextIO) -> None:
968
+ super().__init__()
969
+ self._out = out
970
+
971
+ self._indent = 0
972
+ self._wrote_indent = False
973
+
974
+ #
975
+
976
+ def _w(self, s: str) -> None:
977
+ if not self._wrote_indent:
978
+ self._out.write(' ' * self._indent)
979
+ self._wrote_indent = True
980
+ self._out.write(s)
981
+
982
+ def _nl(self) -> None:
983
+ self._out.write('\n')
984
+ self._wrote_indent = False
985
+
986
+ def _needs_quote(self, s: str) -> bool:
987
+ return (
988
+ not s or
989
+ any(c in s for c in '\'"\n') or
990
+ s[0] not in string.ascii_letters
991
+ )
992
+
993
+ def _maybe_quote(self, s: str) -> str:
994
+ if self._needs_quote(s):
995
+ return repr(s)
996
+ else:
997
+ return s
998
+
999
+ #
1000
+
1001
+ def write_root(self, obj: ta.Mapping) -> None:
1002
+ for i, (k, v) in enumerate(obj.items()):
1003
+ if i:
1004
+ self._nl()
1005
+ self._w('[')
1006
+ self._w(self._maybe_quote(k))
1007
+ self._w(']')
1008
+ self._nl()
1009
+ self.write_table_contents(v)
1010
+
1011
+ def write_table_contents(self, obj: ta.Mapping) -> None:
1012
+ for k, v in obj.items():
1013
+ self.write_key(k)
1014
+ self._w(' = ')
1015
+ self.write_value(v)
1016
+ self._nl()
1017
+
1018
+ def write_array(self, obj: ta.Sequence) -> None:
1019
+ self._w('[')
1020
+ self._nl()
1021
+ self._indent += 1
1022
+ for e in obj:
1023
+ self.write_value(e)
1024
+ self._w(',')
1025
+ self._nl()
1026
+ self._indent -= 1
1027
+ self._w(']')
1028
+
1029
+ def write_inline_table(self, obj: ta.Mapping) -> None:
1030
+ self._w('{')
1031
+ for i, (k, v) in enumerate(obj.items()):
1032
+ if i:
1033
+ self._w(', ')
1034
+ self.write_key(k)
1035
+ self._w(' = ')
1036
+ self.write_value(v)
1037
+ self._w('}')
1038
+
1039
+ def write_inline_array(self, obj: ta.Sequence) -> None:
1040
+ self._w('[')
1041
+ for i, e in enumerate(obj):
1042
+ if i:
1043
+ self._w(', ')
1044
+ self.write_value(e)
1045
+ self._w(']')
1046
+
1047
+ def write_key(self, obj: ta.Any) -> None:
1048
+ if isinstance(obj, TomlWriter.Literal):
1049
+ self._w(obj.s)
1050
+ elif isinstance(obj, str):
1051
+ self._w(self._maybe_quote(obj.replace('_', '-')))
1052
+ elif isinstance(obj, int):
1053
+ self._w(repr(str(obj)))
1054
+ else:
1055
+ raise TypeError(obj)
1056
+
1057
+ def write_value(self, obj: ta.Any) -> None:
1058
+ if isinstance(obj, bool):
1059
+ self._w(str(obj).lower())
1060
+ elif isinstance(obj, (str, int, float)):
1061
+ self._w(repr(obj))
1062
+ elif isinstance(obj, ta.Mapping):
1063
+ self.write_inline_table(obj)
1064
+ elif isinstance(obj, ta.Sequence):
1065
+ if not obj:
1066
+ self.write_inline_array(obj)
1067
+ else:
1068
+ self.write_array(obj)
1069
+ else:
1070
+ raise TypeError(obj)
1071
+
1072
+ #
1073
+
1074
+ @classmethod
1075
+ def write_str(cls, obj: ta.Any) -> str:
1076
+ out = io.StringIO()
1077
+ cls(out).write_value(obj)
1078
+ return out.getvalue()
1079
+
1080
+
903
1081
  ########################################
904
1082
  # ../../../../../omlish/lite/cached.py
905
1083
 
@@ -2217,6 +2395,235 @@ class JournalctlToAwsCursor:
2217
2395
  os.rename(ncf, cf)
2218
2396
 
2219
2397
 
2398
+ ########################################
2399
+ # ../../../../../omlish/configs/formats.py
2400
+ """
2401
+ Notes:
2402
+ - necessarily string-oriented
2403
+ - single file, as this is intended to be amalg'd and thus all included anyway
2404
+
2405
+ TODO:
2406
+ - ConfigDataMapper? to_map -> ConfigMap?
2407
+ - nginx ?
2408
+ - raw ?
2409
+ """
2410
+
2411
+
2412
+ ##
2413
+
2414
+
2415
+ @dc.dataclass(frozen=True)
2416
+ class ConfigData(abc.ABC): # noqa
2417
+ @abc.abstractmethod
2418
+ def as_map(self) -> ConfigMap:
2419
+ raise NotImplementedError
2420
+
2421
+
2422
+ #
2423
+
2424
+
2425
+ class ConfigLoader(abc.ABC, ta.Generic[ConfigDataT]):
2426
+ @property
2427
+ def file_exts(self) -> ta.Sequence[str]:
2428
+ return ()
2429
+
2430
+ def match_file(self, n: str) -> bool:
2431
+ return '.' in n and n.split('.')[-1] in check.not_isinstance(self.file_exts, str)
2432
+
2433
+ #
2434
+
2435
+ def load_file(self, p: str) -> ConfigDataT:
2436
+ with open(p) as f:
2437
+ return self.load_str(f.read())
2438
+
2439
+ @abc.abstractmethod
2440
+ def load_str(self, s: str) -> ConfigDataT:
2441
+ raise NotImplementedError
2442
+
2443
+
2444
+ #
2445
+
2446
+
2447
+ class ConfigRenderer(abc.ABC, ta.Generic[ConfigDataT]):
2448
+ @property
2449
+ @abc.abstractmethod
2450
+ def data_cls(self) -> ta.Type[ConfigDataT]:
2451
+ raise NotImplementedError
2452
+
2453
+ def match_data(self, d: ConfigDataT) -> bool:
2454
+ return isinstance(d, self.data_cls)
2455
+
2456
+ #
2457
+
2458
+ @abc.abstractmethod
2459
+ def render(self, d: ConfigDataT) -> str:
2460
+ raise NotImplementedError
2461
+
2462
+
2463
+ ##
2464
+
2465
+
2466
+ @dc.dataclass(frozen=True)
2467
+ class ObjConfigData(ConfigData, abc.ABC):
2468
+ obj: ta.Any
2469
+
2470
+ def as_map(self) -> ConfigMap:
2471
+ return check.isinstance(self.obj, collections.abc.Mapping)
2472
+
2473
+
2474
+ ##
2475
+
2476
+
2477
+ @dc.dataclass(frozen=True)
2478
+ class JsonConfigData(ObjConfigData):
2479
+ pass
2480
+
2481
+
2482
+ class JsonConfigLoader(ConfigLoader[JsonConfigData]):
2483
+ file_exts = ('json',)
2484
+
2485
+ def load_str(self, s: str) -> JsonConfigData:
2486
+ return JsonConfigData(json.loads(s))
2487
+
2488
+
2489
+ class JsonConfigRenderer(ConfigRenderer[JsonConfigData]):
2490
+ data_cls = JsonConfigData
2491
+
2492
+ def render(self, d: JsonConfigData) -> str:
2493
+ return json_dumps_pretty(d.obj)
2494
+
2495
+
2496
+ ##
2497
+
2498
+
2499
+ @dc.dataclass(frozen=True)
2500
+ class TomlConfigData(ObjConfigData):
2501
+ pass
2502
+
2503
+
2504
+ class TomlConfigLoader(ConfigLoader[TomlConfigData]):
2505
+ file_exts = ('toml',)
2506
+
2507
+ def load_str(self, s: str) -> TomlConfigData:
2508
+ return TomlConfigData(toml_loads(s))
2509
+
2510
+
2511
+ class TomlConfigRenderer(ConfigRenderer[TomlConfigData]):
2512
+ data_cls = TomlConfigData
2513
+
2514
+ def render(self, d: TomlConfigData) -> str:
2515
+ return TomlWriter.write_str(d.obj)
2516
+
2517
+
2518
+ ##
2519
+
2520
+
2521
+ @dc.dataclass(frozen=True)
2522
+ class YamlConfigData(ObjConfigData):
2523
+ pass
2524
+
2525
+
2526
+ class YamlConfigLoader(ConfigLoader[YamlConfigData]):
2527
+ file_exts = ('yaml', 'yml')
2528
+
2529
+ def load_str(self, s: str) -> YamlConfigData:
2530
+ return YamlConfigData(__import__('yaml').safe_load(s))
2531
+
2532
+
2533
+ class YamlConfigRenderer(ConfigRenderer[YamlConfigData]):
2534
+ data_cls = YamlConfigData
2535
+
2536
+ def render(self, d: YamlConfigData) -> str:
2537
+ return __import__('yaml').safe_dump(d.obj)
2538
+
2539
+
2540
+ ##
2541
+
2542
+
2543
+ @dc.dataclass(frozen=True)
2544
+ class IniConfigData(ConfigData):
2545
+ sections: IniSectionSettingsMap
2546
+
2547
+ def as_map(self) -> ConfigMap:
2548
+ return self.sections
2549
+
2550
+
2551
+ class IniConfigLoader(ConfigLoader[IniConfigData]):
2552
+ file_exts = ('ini',)
2553
+
2554
+ def load_str(self, s: str) -> IniConfigData:
2555
+ cp = configparser.ConfigParser()
2556
+ cp.read_string(s)
2557
+ return IniConfigData(extract_ini_sections(cp))
2558
+
2559
+
2560
+ class IniConfigRenderer(ConfigRenderer[IniConfigData]):
2561
+ data_cls = IniConfigData
2562
+
2563
+ def render(self, d: IniConfigData) -> str:
2564
+ return render_ini_sections(d.sections)
2565
+
2566
+
2567
+ ##
2568
+
2569
+
2570
+ @dc.dataclass(frozen=True)
2571
+ class SwitchedConfigFileLoader:
2572
+ loaders: ta.Sequence[ConfigLoader]
2573
+ default: ta.Optional[ConfigLoader] = None
2574
+
2575
+ def load_file(self, p: str) -> ConfigData:
2576
+ n = os.path.basename(p)
2577
+
2578
+ for l in self.loaders:
2579
+ if l.match_file(n):
2580
+ return l.load_file(p)
2581
+
2582
+ if (d := self.default) is not None:
2583
+ return d.load_file(p)
2584
+
2585
+ raise NameError(n)
2586
+
2587
+
2588
+ DEFAULT_CONFIG_LOADERS: ta.Sequence[ConfigLoader] = [
2589
+ JsonConfigLoader(),
2590
+ TomlConfigLoader(),
2591
+ YamlConfigLoader(),
2592
+ IniConfigLoader(),
2593
+ ]
2594
+
2595
+ DEFAULT_CONFIG_LOADER: ConfigLoader = JsonConfigLoader()
2596
+
2597
+ DEFAULT_CONFIG_FILE_LOADER = SwitchedConfigFileLoader(
2598
+ loaders=DEFAULT_CONFIG_LOADERS,
2599
+ default=DEFAULT_CONFIG_LOADER,
2600
+ )
2601
+
2602
+
2603
+ ##
2604
+
2605
+
2606
+ @dc.dataclass(frozen=True)
2607
+ class SwitchedConfigRenderer:
2608
+ renderers: ta.Sequence[ConfigRenderer]
2609
+
2610
+ def render(self, d: ConfigData) -> str:
2611
+ for r in self.renderers:
2612
+ if r.match_data(d):
2613
+ return r.render(d)
2614
+ raise TypeError(d)
2615
+
2616
+
2617
+ DEFAULT_CONFIG_RENDERERS: ta.Sequence[ConfigRenderer] = [
2618
+ JsonConfigRenderer(),
2619
+ TomlConfigRenderer(),
2620
+ YamlConfigRenderer(),
2621
+ IniConfigRenderer(),
2622
+ ]
2623
+
2624
+ DEFAULT_CONFIG_RENDERER = SwitchedConfigRenderer(DEFAULT_CONFIG_RENDERERS)
2625
+
2626
+
2220
2627
  ########################################
2221
2628
  # ../../../../../omlish/io/buffers.py
2222
2629
 
@@ -3279,97 +3686,6 @@ class AwsLogMessageBuilder:
3279
3686
  return [post]
3280
3687
 
3281
3688
 
3282
- ########################################
3283
- # ../../../../configs.py
3284
-
3285
-
3286
- ##
3287
-
3288
-
3289
- def parse_config_file(
3290
- name: str,
3291
- f: ta.TextIO,
3292
- ) -> ConfigMapping:
3293
- if name.endswith('.toml'):
3294
- return toml_loads(f.read())
3295
-
3296
- elif any(name.endswith(e) for e in ('.yml', '.yaml')):
3297
- yaml = __import__('yaml')
3298
- return yaml.safe_load(f)
3299
-
3300
- elif name.endswith('.ini'):
3301
- import configparser
3302
- cp = configparser.ConfigParser()
3303
- cp.read_file(f)
3304
- config_dct: ta.Dict[str, ta.Any] = {}
3305
- for sec in cp.sections():
3306
- cd = config_dct
3307
- for k in sec.split('.'):
3308
- cd = cd.setdefault(k, {})
3309
- cd.update(cp.items(sec))
3310
- return config_dct
3311
-
3312
- else:
3313
- return json.loads(f.read())
3314
-
3315
-
3316
- def read_config_file(
3317
- path: str,
3318
- cls: ta.Type[T],
3319
- *,
3320
- prepare: ta.Optional[ta.Callable[[ConfigMapping], ConfigMapping]] = None,
3321
- msh: ObjMarshalerManager = OBJ_MARSHALER_MANAGER,
3322
- ) -> T:
3323
- with open(path) as cf:
3324
- config_dct = parse_config_file(os.path.basename(path), cf)
3325
-
3326
- if prepare is not None:
3327
- config_dct = prepare(config_dct)
3328
-
3329
- return msh.unmarshal_obj(config_dct, cls)
3330
-
3331
-
3332
- ##
3333
-
3334
-
3335
- def build_config_named_children(
3336
- o: ta.Union[
3337
- ta.Sequence[ConfigMapping],
3338
- ta.Mapping[str, ConfigMapping],
3339
- None,
3340
- ],
3341
- *,
3342
- name_key: str = 'name',
3343
- ) -> ta.Optional[ta.Sequence[ConfigMapping]]:
3344
- if o is None:
3345
- return None
3346
-
3347
- lst: ta.List[ConfigMapping] = []
3348
- if isinstance(o, ta.Mapping):
3349
- for k, v in o.items():
3350
- check.isinstance(v, ta.Mapping)
3351
- if name_key in v:
3352
- n = v[name_key]
3353
- if k != n:
3354
- raise KeyError(f'Given names do not match: {n} != {k}')
3355
- lst.append(v)
3356
- else:
3357
- lst.append({name_key: k, **v})
3358
-
3359
- else:
3360
- check.not_isinstance(o, str)
3361
- lst.extend(o)
3362
-
3363
- seen = set()
3364
- for d in lst:
3365
- n = d['name']
3366
- if n in d:
3367
- raise KeyError(f'Duplicate name: {n}')
3368
- seen.add(n)
3369
-
3370
- return lst
3371
-
3372
-
3373
3689
  ########################################
3374
3690
  # ../../../../journald/messages.py
3375
3691
 
@@ -3629,6 +3945,38 @@ class ThreadWorkerGroup:
3629
3945
  return dct
3630
3946
 
3631
3947
 
3948
+ ########################################
3949
+ # ../../../../../omlish/lite/configs.py
3950
+
3951
+
3952
+ ##
3953
+
3954
+
3955
+ def load_config_file_obj(
3956
+ f: str,
3957
+ cls: ta.Type[T],
3958
+ *,
3959
+ prepare: ta.Union[
3960
+ ta.Callable[[ConfigMap], ConfigMap],
3961
+ ta.Iterable[ta.Callable[[ConfigMap], ConfigMap]],
3962
+ ] = (),
3963
+ msh: ObjMarshalerManager = OBJ_MARSHALER_MANAGER,
3964
+ ) -> T:
3965
+ config_data = DEFAULT_CONFIG_FILE_LOADER.load_file(f)
3966
+
3967
+ config_dct = config_data.as_map()
3968
+
3969
+ if prepare is not None:
3970
+ if isinstance(prepare, ta.Iterable):
3971
+ pfs = list(prepare)
3972
+ else:
3973
+ pfs = [prepare]
3974
+ for pf in pfs:
3975
+ config_dct = pf(config_dct)
3976
+
3977
+ return msh.unmarshal_obj(config_dct, cls)
3978
+
3979
+
3632
3980
  ########################################
3633
3981
  # ../../../../../omlish/logs/standard.py
3634
3982
  """
@@ -4909,7 +5257,10 @@ def _main() -> None:
4909
5257
 
4910
5258
  config: JournalctlToAwsDriver.Config
4911
5259
  if args.config_file:
4912
- config = read_config_file(os.path.expanduser(args.config_file), JournalctlToAwsDriver.Config)
5260
+ config = load_config_file_obj(
5261
+ os.path.expanduser(args.config_file),
5262
+ JournalctlToAwsDriver.Config,
5263
+ )
4913
5264
  else:
4914
5265
  config = JournalctlToAwsDriver.Config()
4915
5266