ominfra 0.0.0.dev193__py3-none-any.whl → 0.0.0.dev195__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -36,6 +36,7 @@ import abc
36
36
  import base64
37
37
  import collections
38
38
  import collections.abc
39
+ import configparser
39
40
  import contextlib
40
41
  import contextvars
41
42
  import ctypes as ct
@@ -79,7 +80,7 @@ import types
79
80
  import typing as ta
80
81
  import uuid
81
82
  import warnings
82
- import weakref # noqa
83
+ import weakref
83
84
 
84
85
 
85
86
  ########################################
@@ -96,6 +97,12 @@ if sys.version_info < (3, 8):
96
97
  K = ta.TypeVar('K')
97
98
  V = ta.TypeVar('V')
98
99
 
100
+ # ../../omlish/configs/types.py
101
+ ConfigMap = ta.Mapping[str, ta.Any]
102
+
103
+ # ../../omlish/formats/ini/sections.py
104
+ IniSectionSettingsMap = ta.Mapping[str, ta.Mapping[str, ta.Union[str, ta.Sequence[str]]]] # ta.TypeAlias
105
+
99
106
  # ../../omlish/formats/toml/parser.py
100
107
  TomlParseFloat = ta.Callable[[str], ta.Any]
101
108
  TomlKey = ta.Tuple[str, ...]
@@ -125,6 +132,9 @@ SocketAddress = ta.Any
125
132
  EventCallback = ta.Callable[['Event'], None]
126
133
  ProcessOutputChannel = ta.Literal['stdout', 'stderr'] # ta.TypeAlias
127
134
 
135
+ # ../../omlish/configs/formats.py
136
+ ConfigDataT = ta.TypeVar('ConfigDataT', bound='ConfigData')
137
+
128
138
  # ../../omlish/http/parsing.py
129
139
  HttpHeaders = http.client.HTTPMessage # ta.TypeAlias
130
140
 
@@ -141,9 +151,6 @@ InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
141
151
  # ../../omlish/sockets/handlers.py
142
152
  SocketHandlerFactory = ta.Callable[[SocketAddress, ta.BinaryIO, ta.BinaryIO], 'SocketHandler']
143
153
 
144
- # ../configs.py
145
- ConfigMapping = ta.Mapping[str, ta.Any]
146
-
147
154
  # ../../omlish/http/handlers.py
148
155
  HttpHandler = ta.Callable[['HttpHandlerRequest'], 'HttpHandlerResponse'] # ta.TypeAlias
149
156
 
@@ -621,6 +628,54 @@ def parse_octal(arg: ta.Union[str, int]) -> int:
621
628
  raise ValueError(f'{arg} can not be converted to an octal type') # noqa
622
629
 
623
630
 
631
+ ########################################
632
+ # ../../../omlish/configs/types.py
633
+
634
+
635
+ #
636
+
637
+
638
+ ########################################
639
+ # ../../../omlish/formats/ini/sections.py
640
+
641
+
642
+ ##
643
+
644
+
645
+ def extract_ini_sections(cp: configparser.ConfigParser) -> IniSectionSettingsMap:
646
+ config_dct: ta.Dict[str, ta.Any] = {}
647
+ for sec in cp.sections():
648
+ cd = config_dct
649
+ for k in sec.split('.'):
650
+ cd = cd.setdefault(k, {})
651
+ cd.update(cp.items(sec))
652
+ return config_dct
653
+
654
+
655
+ ##
656
+
657
+
658
+ def render_ini_sections(
659
+ settings_by_section: IniSectionSettingsMap,
660
+ ) -> str:
661
+ out = io.StringIO()
662
+
663
+ for i, (section, settings) in enumerate(settings_by_section.items()):
664
+ if i:
665
+ out.write('\n')
666
+
667
+ out.write(f'[{section}]\n')
668
+
669
+ for k, v in settings.items():
670
+ if isinstance(v, str):
671
+ out.write(f'{k}={v}\n')
672
+ else:
673
+ for vv in v:
674
+ out.write(f'{k}={vv}\n')
675
+
676
+ return out.getvalue()
677
+
678
+
624
679
  ########################################
625
680
  # ../../../omlish/formats/toml/parser.py
626
681
  # SPDX-License-Identifier: MIT
@@ -1439,6 +1494,129 @@ def toml_make_safe_parse_float(parse_float: TomlParseFloat) -> TomlParseFloat:
1439
1494
  return safe_parse_float
1440
1495
 
1441
1496
 
1497
+ ########################################
1498
+ # ../../../omlish/formats/toml/writer.py
1499
+
1500
+
1501
+ class TomlWriter:
1502
+ @dc.dataclass(frozen=True)
1503
+ class Literal:
1504
+ s: str
1505
+
1506
+ def __init__(self, out: ta.TextIO) -> None:
1507
+ super().__init__()
1508
+ self._out = out
1509
+
1510
+ self._indent = 0
1511
+ self._wrote_indent = False
1512
+
1513
+ #
1514
+
1515
+ def _w(self, s: str) -> None:
1516
+ if not self._wrote_indent:
1517
+ self._out.write(' ' * self._indent)
1518
+ self._wrote_indent = True
1519
+ self._out.write(s)
1520
+
1521
+ def _nl(self) -> None:
1522
+ self._out.write('\n')
1523
+ self._wrote_indent = False
1524
+
1525
+ def _needs_quote(self, s: str) -> bool:
1526
+ return (
1527
+ not s or
1528
+ any(c in s for c in '\'"\n') or
1529
+ s[0] not in string.ascii_letters
1530
+ )
1531
+
1532
+ def _maybe_quote(self, s: str) -> str:
1533
+ if self._needs_quote(s):
1534
+ return repr(s)
1535
+ else:
1536
+ return s
1537
+
1538
+ #
1539
+
1540
+ def write_root(self, obj: ta.Mapping) -> None:
1541
+ for i, (k, v) in enumerate(obj.items()):
1542
+ if i:
1543
+ self._nl()
1544
+ self._w('[')
1545
+ self._w(self._maybe_quote(k))
1546
+ self._w(']')
1547
+ self._nl()
1548
+ self.write_table_contents(v)
1549
+
1550
+ def write_table_contents(self, obj: ta.Mapping) -> None:
1551
+ for k, v in obj.items():
1552
+ self.write_key(k)
1553
+ self._w(' = ')
1554
+ self.write_value(v)
1555
+ self._nl()
1556
+
1557
+ def write_array(self, obj: ta.Sequence) -> None:
1558
+ self._w('[')
1559
+ self._nl()
1560
+ self._indent += 1
1561
+ for e in obj:
1562
+ self.write_value(e)
1563
+ self._w(',')
1564
+ self._nl()
1565
+ self._indent -= 1
1566
+ self._w(']')
1567
+
1568
+ def write_inline_table(self, obj: ta.Mapping) -> None:
1569
+ self._w('{')
1570
+ for i, (k, v) in enumerate(obj.items()):
1571
+ if i:
1572
+ self._w(', ')
1573
+ self.write_key(k)
1574
+ self._w(' = ')
1575
+ self.write_value(v)
1576
+ self._w('}')
1577
+
1578
+ def write_inline_array(self, obj: ta.Sequence) -> None:
1579
+ self._w('[')
1580
+ for i, e in enumerate(obj):
1581
+ if i:
1582
+ self._w(', ')
1583
+ self.write_value(e)
1584
+ self._w(']')
1585
+
1586
+ def write_key(self, obj: ta.Any) -> None:
1587
+ if isinstance(obj, TomlWriter.Literal):
1588
+ self._w(obj.s)
1589
+ elif isinstance(obj, str):
1590
+ self._w(self._maybe_quote(obj.replace('_', '-')))
1591
+ elif isinstance(obj, int):
1592
+ self._w(repr(str(obj)))
1593
+ else:
1594
+ raise TypeError(obj)
1595
+
1596
+ def write_value(self, obj: ta.Any) -> None:
1597
+ if isinstance(obj, bool):
1598
+ self._w(str(obj).lower())
1599
+ elif isinstance(obj, (str, int, float)):
1600
+ self._w(repr(obj))
1601
+ elif isinstance(obj, ta.Mapping):
1602
+ self.write_inline_table(obj)
1603
+ elif isinstance(obj, ta.Sequence):
1604
+ if not obj:
1605
+ self.write_inline_array(obj)
1606
+ else:
1607
+ self.write_array(obj)
1608
+ else:
1609
+ raise TypeError(obj)
1610
+
1611
+ #
1612
+
1613
+ @classmethod
1614
+ def write_str(cls, obj: ta.Any) -> str:
1615
+ out = io.StringIO()
1616
+ cls(out).write_value(obj)
1617
+ return out.getvalue()
1618
+
1619
+
1442
1620
  ########################################
1443
1621
  # ../../../omlish/http/versions.py
1444
1622
 
@@ -3125,6 +3303,283 @@ def get_user(name: str) -> User:
3125
3303
  )
3126
3304
 
3127
3305
 
3306
+ ########################################
3307
+ # ../../../omlish/configs/formats.py
3308
+ """
3309
+ Notes:
3310
+ - necessarily string-oriented
3311
+ - single file, as this is intended to be amalg'd and thus all included anyway
3312
+
3313
+ TODO:
3314
+ - ConfigDataMapper? to_map -> ConfigMap?
3315
+ - nginx ?
3316
+ - raw ?
3317
+ """
3318
+
3319
+
3320
+ ##
3321
+
3322
+
3323
+ @dc.dataclass(frozen=True)
3324
+ class ConfigData(abc.ABC): # noqa
3325
+ @abc.abstractmethod
3326
+ def as_map(self) -> ConfigMap:
3327
+ raise NotImplementedError
3328
+
3329
+
3330
+ #
3331
+
3332
+
3333
+ class ConfigLoader(abc.ABC, ta.Generic[ConfigDataT]):
3334
+ @property
3335
+ def file_exts(self) -> ta.Sequence[str]:
3336
+ return ()
3337
+
3338
+ def match_file(self, n: str) -> bool:
3339
+ return '.' in n and n.split('.')[-1] in check.not_isinstance(self.file_exts, str)
3340
+
3341
+ #
3342
+
3343
+ def load_file(self, p: str) -> ConfigDataT:
3344
+ with open(p) as f:
3345
+ return self.load_str(f.read())
3346
+
3347
+ @abc.abstractmethod
3348
+ def load_str(self, s: str) -> ConfigDataT:
3349
+ raise NotImplementedError
3350
+
3351
+
3352
+ #
3353
+
3354
+
3355
+ class ConfigRenderer(abc.ABC, ta.Generic[ConfigDataT]):
3356
+ @property
3357
+ @abc.abstractmethod
3358
+ def data_cls(self) -> ta.Type[ConfigDataT]:
3359
+ raise NotImplementedError
3360
+
3361
+ def match_data(self, d: ConfigDataT) -> bool:
3362
+ return isinstance(d, self.data_cls)
3363
+
3364
+ #
3365
+
3366
+ @abc.abstractmethod
3367
+ def render(self, d: ConfigDataT) -> str:
3368
+ raise NotImplementedError
3369
+
3370
+
3371
+ ##
3372
+
3373
+
3374
+ @dc.dataclass(frozen=True)
3375
+ class ObjConfigData(ConfigData, abc.ABC):
3376
+ obj: ta.Any
3377
+
3378
+ def as_map(self) -> ConfigMap:
3379
+ return check.isinstance(self.obj, collections.abc.Mapping)
3380
+
3381
+
3382
+ ##
3383
+
3384
+
3385
+ @dc.dataclass(frozen=True)
3386
+ class JsonConfigData(ObjConfigData):
3387
+ pass
3388
+
3389
+
3390
+ class JsonConfigLoader(ConfigLoader[JsonConfigData]):
3391
+ file_exts = ('json',)
3392
+
3393
+ def load_str(self, s: str) -> JsonConfigData:
3394
+ return JsonConfigData(json.loads(s))
3395
+
3396
+
3397
+ class JsonConfigRenderer(ConfigRenderer[JsonConfigData]):
3398
+ data_cls = JsonConfigData
3399
+
3400
+ def render(self, d: JsonConfigData) -> str:
3401
+ return json_dumps_pretty(d.obj)
3402
+
3403
+
3404
+ ##
3405
+
3406
+
3407
+ @dc.dataclass(frozen=True)
3408
+ class TomlConfigData(ObjConfigData):
3409
+ pass
3410
+
3411
+
3412
+ class TomlConfigLoader(ConfigLoader[TomlConfigData]):
3413
+ file_exts = ('toml',)
3414
+
3415
+ def load_str(self, s: str) -> TomlConfigData:
3416
+ return TomlConfigData(toml_loads(s))
3417
+
3418
+
3419
+ class TomlConfigRenderer(ConfigRenderer[TomlConfigData]):
3420
+ data_cls = TomlConfigData
3421
+
3422
+ def render(self, d: TomlConfigData) -> str:
3423
+ return TomlWriter.write_str(d.obj)
3424
+
3425
+
3426
+ ##
3427
+
3428
+
3429
+ @dc.dataclass(frozen=True)
3430
+ class YamlConfigData(ObjConfigData):
3431
+ pass
3432
+
3433
+
3434
+ class YamlConfigLoader(ConfigLoader[YamlConfigData]):
3435
+ file_exts = ('yaml', 'yml')
3436
+
3437
+ def load_str(self, s: str) -> YamlConfigData:
3438
+ return YamlConfigData(__import__('yaml').safe_load(s))
3439
+
3440
+
3441
+ class YamlConfigRenderer(ConfigRenderer[YamlConfigData]):
3442
+ data_cls = YamlConfigData
3443
+
3444
+ def render(self, d: YamlConfigData) -> str:
3445
+ return __import__('yaml').safe_dump(d.obj)
3446
+
3447
+
3448
+ ##
3449
+
3450
+
3451
+ @dc.dataclass(frozen=True)
3452
+ class IniConfigData(ConfigData):
3453
+ sections: IniSectionSettingsMap
3454
+
3455
+ def as_map(self) -> ConfigMap:
3456
+ return self.sections
3457
+
3458
+
3459
+ class IniConfigLoader(ConfigLoader[IniConfigData]):
3460
+ file_exts = ('ini',)
3461
+
3462
+ def load_str(self, s: str) -> IniConfigData:
3463
+ cp = configparser.ConfigParser()
3464
+ cp.read_string(s)
3465
+ return IniConfigData(extract_ini_sections(cp))
3466
+
3467
+
3468
+ class IniConfigRenderer(ConfigRenderer[IniConfigData]):
3469
+ data_cls = IniConfigData
3470
+
3471
+ def render(self, d: IniConfigData) -> str:
3472
+ return render_ini_sections(d.sections)
3473
+
3474
+
3475
+ ##
3476
+
3477
+
3478
+ @dc.dataclass(frozen=True)
3479
+ class SwitchedConfigFileLoader:
3480
+ loaders: ta.Sequence[ConfigLoader]
3481
+ default: ta.Optional[ConfigLoader] = None
3482
+
3483
+ def load_file(self, p: str) -> ConfigData:
3484
+ n = os.path.basename(p)
3485
+
3486
+ for l in self.loaders:
3487
+ if l.match_file(n):
3488
+ return l.load_file(p)
3489
+
3490
+ if (d := self.default) is not None:
3491
+ return d.load_file(p)
3492
+
3493
+ raise NameError(n)
3494
+
3495
+
3496
+ DEFAULT_CONFIG_LOADERS: ta.Sequence[ConfigLoader] = [
3497
+ JsonConfigLoader(),
3498
+ TomlConfigLoader(),
3499
+ YamlConfigLoader(),
3500
+ IniConfigLoader(),
3501
+ ]
3502
+
3503
+ DEFAULT_CONFIG_LOADER: ConfigLoader = JsonConfigLoader()
3504
+
3505
+ DEFAULT_CONFIG_FILE_LOADER = SwitchedConfigFileLoader(
3506
+ loaders=DEFAULT_CONFIG_LOADERS,
3507
+ default=DEFAULT_CONFIG_LOADER,
3508
+ )
3509
+
3510
+
3511
+ ##
3512
+
3513
+
3514
+ @dc.dataclass(frozen=True)
3515
+ class SwitchedConfigRenderer:
3516
+ renderers: ta.Sequence[ConfigRenderer]
3517
+
3518
+ def render(self, d: ConfigData) -> str:
3519
+ for r in self.renderers:
3520
+ if r.match_data(d):
3521
+ return r.render(d)
3522
+ raise TypeError(d)
3523
+
3524
+
3525
+ DEFAULT_CONFIG_RENDERERS: ta.Sequence[ConfigRenderer] = [
3526
+ JsonConfigRenderer(),
3527
+ TomlConfigRenderer(),
3528
+ YamlConfigRenderer(),
3529
+ IniConfigRenderer(),
3530
+ ]
3531
+
3532
+ DEFAULT_CONFIG_RENDERER = SwitchedConfigRenderer(DEFAULT_CONFIG_RENDERERS)
3533
+
3534
+
3535
+ ########################################
3536
+ # ../../../omlish/configs/processing/names.py
3537
+ """
3538
+ usecase: supervisor process groups
3539
+ """
3540
+
3541
+
3542
+ ##
3543
+
3544
+
3545
+ def build_config_named_children(
3546
+ o: ta.Union[
3547
+ ta.Sequence[ConfigMap],
3548
+ ta.Mapping[str, ConfigMap],
3549
+ None,
3550
+ ],
3551
+ *,
3552
+ name_key: str = 'name',
3553
+ ) -> ta.Optional[ta.Sequence[ConfigMap]]:
3554
+ if o is None:
3555
+ return None
3556
+
3557
+ lst: ta.List[ConfigMap] = []
3558
+ if isinstance(o, ta.Mapping):
3559
+ for k, v in o.items():
3560
+ check.isinstance(v, ta.Mapping)
3561
+ if name_key in v:
3562
+ n = v[name_key]
3563
+ if k != n:
3564
+ raise KeyError(f'Given names do not match: {n} != {k}')
3565
+ lst.append(v)
3566
+ else:
3567
+ lst.append({name_key: k, **v})
3568
+
3569
+ else:
3570
+ check.not_isinstance(o, str)
3571
+ lst.extend(o)
3572
+
3573
+ seen = set()
3574
+ for d in lst:
3575
+ n = d['name']
3576
+ if n in d:
3577
+ raise KeyError(f'Duplicate name: {n}')
3578
+ seen.add(n)
3579
+
3580
+ return lst
3581
+
3582
+
3128
3583
  ########################################
3129
3584
  # ../../../omlish/http/parsing.py
3130
3585
  # PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
@@ -5858,94 +6313,302 @@ class SocketHandler(abc.ABC):
5858
6313
 
5859
6314
 
5860
6315
  ########################################
5861
- # ../../configs.py
6316
+ # ../configs.py
5862
6317
 
5863
6318
 
5864
6319
  ##
5865
6320
 
5866
6321
 
5867
- def parse_config_file(
5868
- name: str,
5869
- f: ta.TextIO,
5870
- ) -> ConfigMapping:
5871
- if name.endswith('.toml'):
5872
- return toml_loads(f.read())
5873
-
5874
- elif any(name.endswith(e) for e in ('.yml', '.yaml')):
5875
- yaml = __import__('yaml')
5876
- return yaml.safe_load(f)
5877
-
5878
- elif name.endswith('.ini'):
5879
- import configparser
5880
- cp = configparser.ConfigParser()
5881
- cp.read_file(f)
5882
- config_dct: ta.Dict[str, ta.Any] = {}
5883
- for sec in cp.sections():
5884
- cd = config_dct
5885
- for k in sec.split('.'):
5886
- cd = cd.setdefault(k, {})
5887
- cd.update(cp.items(sec))
5888
- return config_dct
5889
-
5890
- else:
5891
- return json.loads(f.read())
5892
-
5893
-
5894
- def read_config_file(
5895
- path: str,
5896
- cls: ta.Type[T],
5897
- *,
5898
- prepare: ta.Optional[ta.Callable[[ConfigMapping], ConfigMapping]] = None,
5899
- msh: ObjMarshalerManager = OBJ_MARSHALER_MANAGER,
5900
- ) -> T:
5901
- with open(path) as cf:
5902
- config_dct = parse_config_file(os.path.basename(path), cf)
6322
+ class RestartWhenExitUnexpected:
6323
+ pass
5903
6324
 
5904
- if prepare is not None:
5905
- config_dct = prepare(config_dct)
5906
6325
 
5907
- return msh.unmarshal_obj(config_dct, cls)
6326
+ class RestartUnconditionally:
6327
+ pass
5908
6328
 
5909
6329
 
5910
6330
  ##
5911
6331
 
5912
6332
 
5913
- def build_config_named_children(
5914
- o: ta.Union[
5915
- ta.Sequence[ConfigMapping],
5916
- ta.Mapping[str, ConfigMapping],
5917
- None,
5918
- ],
5919
- *,
5920
- name_key: str = 'name',
5921
- ) -> ta.Optional[ta.Sequence[ConfigMapping]]:
5922
- if o is None:
5923
- return None
6333
+ @dc.dataclass(frozen=True)
6334
+ class ProcessConfig:
6335
+ # A Python string expression that is used to compose the supervisor process name for this process. You usually don't
6336
+ # need to worry about setting this unless you change numprocs. The string expression is evaluated against a
6337
+ # dictionary that includes group_name, host_node_name, process_num, program_name, and here (the directory of the
6338
+ # supervisord config file).
6339
+ name: str
5924
6340
 
5925
- lst: ta.List[ConfigMapping] = []
5926
- if isinstance(o, ta.Mapping):
5927
- for k, v in o.items():
5928
- check.isinstance(v, ta.Mapping)
5929
- if name_key in v:
5930
- n = v[name_key]
5931
- if k != n:
5932
- raise KeyError(f'Given names do not match: {n} != {k}')
5933
- lst.append(v)
5934
- else:
5935
- lst.append({name_key: k, **v})
6341
+ # The command that will be run when this program is started. The command can be either absolute (e.g.
6342
+ # /path/to/programname) or relative (e.g. programname). If it is relative, the supervisord's environment $PATH will
6343
+ # be searched for the executable. Programs can accept arguments, e.g. /path/to/program foo bar. The command line can
6344
+ # use double quotes to group arguments with spaces in them to pass to the program, e.g. /path/to/program/name -p
6345
+ # "foo bar". Note that the value of command may include Python string expressions, e.g. /path/to/programname
6346
+ # --port=80%(process_num)02d might expand to /path/to/programname --port=8000 at runtime. String expressions are
6347
+ # evaluated against a dictionary containing the keys group_name, host_node_name, program_name, process_num,
6348
+ # numprocs, here (the directory of the supervisord config file), and all supervisord's environment variables
6349
+ # prefixed with ENV_. Controlled programs should themselves not be daemons, as supervisord assumes it is responsible
6350
+ # for daemonizing its subprocesses
6351
+ command: str
5936
6352
 
5937
- else:
5938
- check.not_isinstance(o, str)
5939
- lst.extend(o)
6353
+ #
5940
6354
 
5941
- seen = set()
5942
- for d in lst:
5943
- n = d['name']
5944
- if n in d:
5945
- raise KeyError(f'Duplicate name: {n}')
5946
- seen.add(n)
6355
+ # Supervisor will start as many instances of this program as named by numprocs. Note that if numprocs > 1, the
6356
+ # process_name expression must include %(process_num)s (or any other valid Python string expression that includes
6357
+ # process_num) within it.
6358
+ num_procs: int = 1
5947
6359
 
5948
- return lst
6360
+ # An integer offset that is used to compute the number at which process_num starts.
6361
+ num_procs_start: int = 0
6362
+
6363
+ #
6364
+
6365
+ # Instruct supervisord to use this UNIX user account as the account which runs the program. The user can only be
6366
+ # switched if supervisord is run as the root user. If supervisord can't switch to the specified user, the program
6367
+ # will not be started.
6368
+ #
6369
+ # Note: The user will be changed using setuid only. This does not start a login shell and does not change
6370
+ # environment variables like USER or HOME
6371
+ user: ta.Optional[str] = None
6372
+ uid: ta.Optional[int] = None
6373
+
6374
+ # An octal number (e.g. 002, 022) representing the umask of the process.
6375
+ umask: ta.Optional[int] = None
6376
+
6377
+ #
6378
+
6379
+ # A file path representing a directory to which supervisord should temporarily chdir before exec'ing the child.
6380
+ directory: ta.Optional[str] = None
6381
+
6382
+ # A list of key/value pairs in the form KEY="val",KEY2="val2" that will be placed in the child process' environment.
6383
+ # The environment string may contain Python string expressions that will be evaluated against a dictionary
6384
+ # containing group_name, host_node_name, process_num, program_name, and here (the directory of the supervisord
6385
+ # config file). Values containing non-alphanumeric characters should be quoted (e.g. KEY="val:123",KEY2="val,456").
6386
+ # Otherwise, quoting the values is optional but recommended. Note that the subprocess will inherit the environment
6387
+ # variables of the shell used to start “supervisord” except for the ones overridden here.
6388
+ environment: ta.Optional[ta.Mapping[str, str]] = None
6389
+
6390
+ #
6391
+
6392
+ # The relative priority of the program in the start and shutdown ordering. Lower priorities indicate programs that
6393
+ # start first and shut down last at startup and when aggregate commands are used in various clients (e.g. “start
6394
+ # all”/”stop all”). Higher priorities indicate programs that start last and shut down first.
6395
+ priority: int = 999
6396
+
6397
+ # If true, this program will start automatically when supervisord is started.
6398
+ auto_start: bool = True
6399
+
6400
+ # Specifies if supervisord should automatically restart a process if it exits when it is in the RUNNING state. May
6401
+ # be one of false, unexpected, or true. If false, the process will not be autorestarted. If unexpected, the process
6402
+ # will be restarted when the program exits with an exit code that is not one of the exit codes associated with this
6403
+ # process' configuration (see exitcodes). If true, the process will be unconditionally restarted when it exits,
6404
+ # without regard to its exit code.
6405
+ #
6406
+ # Note: autorestart controls whether supervisord will autorestart a program if it exits after it has successfully
6407
+ # started up (the process is in the RUNNING state). supervisord has a different restart mechanism for when the
6408
+ # process is starting up (the process is in the STARTING state). Retries during process startup are controlled by
6409
+ # startsecs and startretries.
6410
+ auto_restart: str = 'unexpected'
6411
+
6412
+ # The total number of seconds which the program needs to stay running after a startup to consider the start
6413
+ # successful (moving the process from the STARTING state to the RUNNING state). Set to 0 to indicate that the
6414
+ # program needn't stay running for any particular amount of time.
6415
+ #
6416
+ # Note: Even if a process exits with an “expected” exit code (see exitcodes), the start will still be considered a
6417
+ # failure if the process exits quicker than startsecs.
6418
+ start_secs: int = 1
6419
+
6420
+ # The number of serial failure attempts that supervisord will allow when attempting to start the program before
6421
+ # giving up and putting the process into an FATAL state.
6422
+ #
6423
+ # Note: After each failed restart, process will be put in BACKOFF state and each retry attempt will take
6424
+ # increasingly more time.
6425
+ start_retries: int = 3
6426
+
6427
+ # The signal used to kill the program when a stop is requested. This can be specified using the signal's name or its
6428
+ # number. It is normally one of: TERM, HUP, INT, QUIT, KILL, USR1, or USR2.
6429
+ stop_signal: int = signal.SIGTERM
6430
+
6431
+ # The number of seconds to wait for the OS to return a SIGCHLD to supervisord after the program has been sent a
6432
+ # stopsignal. If this number of seconds elapses before supervisord receives a SIGCHLD from the process, supervisord
6433
+ # will attempt to kill it with a final SIGKILL.
6434
+ stop_wait_secs: int = 10
6435
+
6436
+ # If true, the flag causes supervisor to send the stop signal to the whole process group and implies killasgroup is
6437
+ # true. This is useful for programs, such as Flask in debug mode, that do not propagate stop signals to their
6438
+ # children, leaving them orphaned.
6439
+ stop_as_group: bool = False
6440
+
6441
+ # If true, when resorting to send SIGKILL to the program to terminate it send it to its whole process group instead,
6442
+ # taking care of its children as well, useful e.g with Python programs using multiprocessing.
6443
+ kill_as_group: bool = False
6444
+
6445
+ # The list of “expected” exit codes for this program used with autorestart. If the autorestart parameter is set to
6446
+ # unexpected, and the process exits in any other way than as a result of a supervisor stop request, supervisord will
6447
+ # restart the process if it exits with an exit code that is not defined in this list.
6448
+ #
6449
+ # Note: In Supervisor versions prior to 4.0, the default was 0,2. In Supervisor 4.0, the default was changed to 0.
6450
+ exitcodes: ta.Sequence[int] = (0,)
6451
+
6452
+ #
6453
+
6454
+ @dc.dataclass(frozen=True)
6455
+ class Log:
6456
+ file: ta.Optional[str] = None
6457
+ capture_max_bytes: ta.Optional[int] = None
6458
+ events_enabled: bool = False
6459
+ syslog: bool = False
6460
+ backups: ta.Optional[int] = None
6461
+ max_bytes: ta.Optional[int] = None
6462
+
6463
+ stdout: Log = Log()
6464
+ stderr: Log = Log()
6465
+
6466
+ # If true, cause the process' stderr output to be sent back to supervisord on its stdout file descriptor (in UNIX
6467
+ # shell terms, this is the equivalent of executing /the/program 2>&1).
6468
+ #
6469
+ # Note: Do not set redirect_stderr=true in an [eventlistener:x] section. Eventlisteners use stdout and stdin to
6470
+ # communicate with supervisord. If stderr is redirected, output from stderr will interfere with the eventlistener
6471
+ # protocol.
6472
+ redirect_stderr: bool = False
6473
+
6474
+
6475
+ @dc.dataclass(frozen=True)
6476
+ class ProcessGroupConfig:
6477
+ name: str
6478
+
6479
+ priority: int = 999
6480
+
6481
+ processes: ta.Optional[ta.Sequence[ProcessConfig]] = None
6482
+
6483
+
6484
+ @dc.dataclass(frozen=True)
6485
+ class ServerConfig:
6486
+ # Instruct supervisord to switch users to this UNIX user account before doing any meaningful processing. The user
6487
+ # can only be switched if supervisord is started as the root user.
6488
+ user: ta.Optional[str] = None
6489
+
6490
+ # If true, supervisord will start in the foreground instead of daemonizing.
6491
+ nodaemon: bool = False
6492
+
6493
+ # The umask of the supervisord process.
6494
+ umask: int = 0o22
6495
+
6496
+ #
6497
+
6498
+ # When supervisord daemonizes, switch to this directory. This option can include the value %(here)s, which expands
6499
+ # to the directory in which the supervisord configuration file was found.
6500
+ directory: ta.Optional[str] = None
6501
+
6502
+ # The location in which supervisord keeps its pid file. This option can include the value %(here)s, which expands to
6503
+ # the directory in which the supervisord configuration file was found.
6504
+ pidfile: str = 'supervisord.pid'
6505
+
6506
+ # The identifier string for this supervisor process, used by the RPC interface.
6507
+ identifier: str = 'supervisor'
6508
+
6509
+ # The minimum number of file descriptors that must be available before supervisord will start successfully.
6510
+ min_fds: int = 1024
6511
+ # The minimum number of process descriptors that must be available before supervisord will start successfully.
6512
+ min_procs: int = 200
6513
+
6514
+ # Prevent supervisord from clearing any existing AUTO child log files at startup time. Useful for debugging
6515
+ nocleanup: bool = False
6516
+
6517
+ # Strip all ANSI escape sequences from child log files.
6518
+ strip_ansi: bool = False
6519
+
6520
+ #
6521
+
6522
+ # The path to the activity log of the supervisord process. This option can include the value %(here)s, which expands
6523
+ # to the directory in which the supervisord configuration file was found.
6524
+ logfile: str = 'supervisord.log'
6525
+
6526
+ # The maximum number of bytes that may be consumed by the activity log file before it is rotated (suffix multipliers
6527
+ # like “KB”, “MB”, and “GB” can be used in the value). Set this value to 0 to indicate an unlimited log size.
6528
+ logfile_max_bytes: int = 50 * 1024 * 1024
6529
+
6530
+ # The number of backups to keep around resulting from activity log file rotation. If set to 0, no backups will be
6531
+ # kept.
6532
+ logfile_backups: int = 10
6533
+
6534
+ # The logging level, dictating what is written to the supervisord activity log. One of critical, error, warn, info,
6535
+ # debug, trace, or blather. Note that at log level debug, the supervisord log file will record the stderr/stdout
6536
+ # output of its child processes and extended info about process state changes, which is useful for debugging a
6537
+ # process which isn't starting properly.
6538
+ loglevel: int = logging.INFO
6539
+
6540
+ # The directory used for AUTO child log files. This option can include the value %(here)s, which expands to the
6541
+ # directory in which the supervisord configuration file was found.
6542
+ child_logdir: str = '/dev/null'
6543
+
6544
+ # If true and not daemonized, logs will not be directed to stdout.
6545
+ silent: bool = False
6546
+
6547
+ #
6548
+
6549
+ groups: ta.Optional[ta.Sequence[ProcessGroupConfig]] = None
6550
+
6551
+ # TODO: implement - make sure to accept broken symlinks
6552
+ group_config_dirs: ta.Optional[ta.Sequence[str]] = None
6553
+
6554
+ #
6555
+
6556
+ http_port: ta.Optional[int] = None
6557
+
6558
+ #
6559
+
6560
+ @classmethod
6561
+ def new(
6562
+ cls,
6563
+ *,
6564
+ umask: ta.Union[int, str] = 0o22,
6565
+ directory: ta.Optional[str] = None,
6566
+ logfile: str = 'supervisord.log',
6567
+ logfile_max_bytes: ta.Union[int, str] = 50 * 1024 * 1024,
6568
+ loglevel: ta.Union[int, str] = logging.INFO,
6569
+ pidfile: str = 'supervisord.pid',
6570
+ child_logdir: ta.Optional[str] = None,
6571
+ **kwargs: ta.Any,
6572
+ ) -> 'ServerConfig':
6573
+ return cls(
6574
+ umask=parse_octal(umask),
6575
+ directory=check_existing_dir(directory) if directory is not None else None,
6576
+ logfile=check_path_with_existing_dir(logfile),
6577
+ logfile_max_bytes=parse_bytes_size(logfile_max_bytes),
6578
+ loglevel=parse_logging_level(loglevel),
6579
+ pidfile=check_path_with_existing_dir(pidfile),
6580
+ child_logdir=child_logdir if child_logdir else tempfile.gettempdir(),
6581
+ **kwargs,
6582
+ )
6583
+
6584
+
6585
+ ##
6586
+
6587
+
6588
+ def prepare_process_group_config(dct: ConfigMap) -> ConfigMap:
6589
+ out = dict(dct)
6590
+ out['processes'] = build_config_named_children(out.get('processes'))
6591
+ return out
6592
+
6593
+
6594
+ def prepare_server_config(dct: ta.Mapping[str, ta.Any]) -> ta.Mapping[str, ta.Any]:
6595
+ out = dict(dct)
6596
+ group_dcts = build_config_named_children(out.get('groups'))
6597
+ out['groups'] = [prepare_process_group_config(group_dct) for group_dct in group_dcts or []]
6598
+ return out
6599
+
6600
+
6601
+ ##
6602
+
6603
+
6604
+ def parse_logging_level(value: ta.Union[str, int]) -> int:
6605
+ if isinstance(value, int):
6606
+ return value
6607
+ s = str(value).lower()
6608
+ level = logging.getLevelNamesMapping().get(s.upper())
6609
+ if level is None:
6610
+ raise ValueError(f'bad logging level name {value!r}')
6611
+ return level
5949
6612
 
5950
6613
 
5951
6614
  ########################################
@@ -6095,6 +6758,38 @@ class UnsupportedMethodHttpHandlerError(Exception):
6095
6758
  pass
6096
6759
 
6097
6760
 
6761
+ ########################################
6762
+ # ../../../omlish/lite/configs.py
6763
+
6764
+
6765
+ ##
6766
+
6767
+
6768
+ def load_config_file_obj(
6769
+ f: str,
6770
+ cls: ta.Type[T],
6771
+ *,
6772
+ prepare: ta.Union[
6773
+ ta.Callable[[ConfigMap], ConfigMap],
6774
+ ta.Iterable[ta.Callable[[ConfigMap], ConfigMap]],
6775
+ ] = (),
6776
+ msh: ObjMarshalerManager = OBJ_MARSHALER_MANAGER,
6777
+ ) -> T:
6778
+ config_data = DEFAULT_CONFIG_FILE_LOADER.load_file(f)
6779
+
6780
+ config_dct = config_data.as_map()
6781
+
6782
+ if prepare is not None:
6783
+ if isinstance(prepare, ta.Iterable):
6784
+ pfs = list(prepare)
6785
+ else:
6786
+ pfs = [prepare]
6787
+ for pf in pfs:
6788
+ config_dct = pf(config_dct)
6789
+
6790
+ return msh.unmarshal_obj(config_dct, cls)
6791
+
6792
+
6098
6793
  ########################################
6099
6794
  # ../../../omlish/logs/standard.py
6100
6795
  """
@@ -6217,303 +6912,184 @@ def configure_standard_logging(
6217
6912
  return StandardConfiguredLogHandler(handler)
6218
6913
 
6219
6914
 
6220
- ########################################
6221
- # ../configs.py
6222
-
6223
-
6224
- ##
6225
-
6226
-
6227
- class RestartWhenExitUnexpected:
6228
- pass
6229
-
6230
-
6231
- class RestartUnconditionally:
6232
- pass
6233
-
6234
-
6235
- ##
6236
-
6237
-
6238
- @dc.dataclass(frozen=True)
6239
- class ProcessConfig:
6240
- # A Python string expression that is used to compose the supervisor process name for this process. You usually don't
6241
- # need to worry about setting this unless you change numprocs. The string expression is evaluated against a
6242
- # dictionary that includes group_name, host_node_name, process_num, program_name, and here (the directory of the
6243
- # supervisord config file).
6244
- name: str
6245
-
6246
- # The command that will be run when this program is started. The command can be either absolute (e.g.
6247
- # /path/to/programname) or relative (e.g. programname). If it is relative, the supervisord's environment $PATH will
6248
- # be searched for the executable. Programs can accept arguments, e.g. /path/to/program foo bar. The command line can
6249
- # use double quotes to group arguments with spaces in them to pass to the program, e.g. /path/to/program/name -p
6250
- # "foo bar". Note that the value of command may include Python string expressions, e.g. /path/to/programname
6251
- # --port=80%(process_num)02d might expand to /path/to/programname --port=8000 at runtime. String expressions are
6252
- # evaluated against a dictionary containing the keys group_name, host_node_name, program_name, process_num,
6253
- # numprocs, here (the directory of the supervisord config file), and all supervisord's environment variables
6254
- # prefixed with ENV_. Controlled programs should themselves not be daemons, as supervisord assumes it is responsible
6255
- # for daemonizing its subprocesses
6256
- command: str
6257
-
6258
- #
6259
-
6260
- # Supervisor will start as many instances of this program as named by numprocs. Note that if numprocs > 1, the
6261
- # process_name expression must include %(process_num)s (or any other valid Python string expression that includes
6262
- # process_num) within it.
6263
- num_procs: int = 1
6264
-
6265
- # An integer offset that is used to compute the number at which process_num starts.
6266
- num_procs_start: int = 0
6267
-
6268
- #
6269
-
6270
- # Instruct supervisord to use this UNIX user account as the account which runs the program. The user can only be
6271
- # switched if supervisord is run as the root user. If supervisord can't switch to the specified user, the program
6272
- # will not be started.
6273
- #
6274
- # Note: The user will be changed using setuid only. This does not start a login shell and does not change
6275
- # environment variables like USER or HOME
6276
- user: ta.Optional[str] = None
6277
- uid: ta.Optional[int] = None
6278
-
6279
- # An octal number (e.g. 002, 022) representing the umask of the process.
6280
- umask: ta.Optional[int] = None
6281
-
6282
- #
6283
-
6284
- # A file path representing a directory to which supervisord should temporarily chdir before exec'ing the child.
6285
- directory: ta.Optional[str] = None
6286
-
6287
- # A list of key/value pairs in the form KEY="val",KEY2="val2" that will be placed in the child process' environment.
6288
- # The environment string may contain Python string expressions that will be evaluated against a dictionary
6289
- # containing group_name, host_node_name, process_num, program_name, and here (the directory of the supervisord
6290
- # config file). Values containing non-alphanumeric characters should be quoted (e.g. KEY="val:123",KEY2="val,456").
6291
- # Otherwise, quoting the values is optional but recommended. Note that the subprocess will inherit the environment
6292
- # variables of the shell used to start “supervisord” except for the ones overridden here.
6293
- environment: ta.Optional[ta.Mapping[str, str]] = None
6294
-
6295
- #
6296
-
6297
- # The relative priority of the program in the start and shutdown ordering. Lower priorities indicate programs that
6298
- # start first and shut down last at startup and when aggregate commands are used in various clients (e.g. “start
6299
- # all”/”stop all”). Higher priorities indicate programs that start last and shut down first.
6300
- priority: int = 999
6301
-
6302
- # If true, this program will start automatically when supervisord is started.
6303
- auto_start: bool = True
6304
-
6305
- # Specifies if supervisord should automatically restart a process if it exits when it is in the RUNNING state. May
6306
- # be one of false, unexpected, or true. If false, the process will not be autorestarted. If unexpected, the process
6307
- # will be restarted when the program exits with an exit code that is not one of the exit codes associated with this
6308
- # process' configuration (see exitcodes). If true, the process will be unconditionally restarted when it exits,
6309
- # without regard to its exit code.
6310
- #
6311
- # Note: autorestart controls whether supervisord will autorestart a program if it exits after it has successfully
6312
- # started up (the process is in the RUNNING state). supervisord has a different restart mechanism for when the
6313
- # process is starting up (the process is in the STARTING state). Retries during process startup are controlled by
6314
- # startsecs and startretries.
6315
- auto_restart: str = 'unexpected'
6915
+ ########################################
6916
+ # ../types.py
6316
6917
 
6317
- # The total number of seconds which the program needs to stay running after a startup to consider the start
6318
- # successful (moving the process from the STARTING state to the RUNNING state). Set to 0 to indicate that the
6319
- # program needn't stay running for any particular amount of time.
6320
- #
6321
- # Note: Even if a process exits with an “expected” exit code (see exitcodes), the start will still be considered a
6322
- # failure if the process exits quicker than startsecs.
6323
- start_secs: int = 1
6324
6918
 
6325
- # The number of serial failure attempts that supervisord will allow when attempting to start the program before
6326
- # giving up and putting the process into an FATAL state.
6327
- #
6328
- # Note: After each failed restart, process will be put in BACKOFF state and each retry attempt will take
6329
- # increasingly more time.
6330
- start_retries: int = 3
6919
+ ##
6331
6920
 
6332
- # The signal used to kill the program when a stop is requested. This can be specified using the signal's name or its
6333
- # number. It is normally one of: TERM, HUP, INT, QUIT, KILL, USR1, or USR2.
6334
- stop_signal: int = signal.SIGTERM
6335
6921
 
6336
- # The number of seconds to wait for the OS to return a SIGCHLD to supervisord after the program has been sent a
6337
- # stopsignal. If this number of seconds elapses before supervisord receives a SIGCHLD from the process, supervisord
6338
- # will attempt to kill it with a final SIGKILL.
6339
- stop_wait_secs: int = 10
6922
+ class ExitNow(Exception): # noqa
6923
+ pass
6340
6924
 
6341
- # If true, the flag causes supervisor to send the stop signal to the whole process group and implies killasgroup is
6342
- # true. This is useful for programs, such as Flask in debug mode, that do not propagate stop signals to their
6343
- # children, leaving them orphaned.
6344
- stop_as_group: bool = False
6345
6925
 
6346
- # If true, when resorting to send SIGKILL to the program to terminate it send it to its whole process group instead,
6347
- # taking care of its children as well, useful e.g with Python programs using multiprocessing.
6348
- kill_as_group: bool = False
6926
+ ServerEpoch = ta.NewType('ServerEpoch', int)
6349
6927
 
6350
- # The list of “expected” exit codes for this program used with autorestart. If the autorestart parameter is set to
6351
- # unexpected, and the process exits in any other way than as a result of a supervisor stop request, supervisord will
6352
- # restart the process if it exits with an exit code that is not defined in this list.
6353
- #
6354
- # Note: In Supervisor versions prior to 4.0, the default was 0,2. In Supervisor 4.0, the default was changed to 0.
6355
- exitcodes: ta.Sequence[int] = (0,)
6356
6928
 
6357
- #
6929
+ ##
6358
6930
 
6359
- @dc.dataclass(frozen=True)
6360
- class Log:
6361
- file: ta.Optional[str] = None
6362
- capture_max_bytes: ta.Optional[int] = None
6363
- events_enabled: bool = False
6364
- syslog: bool = False
6365
- backups: ta.Optional[int] = None
6366
- max_bytes: ta.Optional[int] = None
6367
6931
 
6368
- stdout: Log = Log()
6369
- stderr: Log = Log()
6932
+ @functools.total_ordering
6933
+ class ConfigPriorityOrdered(abc.ABC):
6934
+ @property
6935
+ @abc.abstractmethod
6936
+ def config(self) -> ta.Any:
6937
+ raise NotImplementedError
6370
6938
 
6371
- # If true, cause the process' stderr output to be sent back to supervisord on its stdout file descriptor (in UNIX
6372
- # shell terms, this is the equivalent of executing /the/program 2>&1).
6373
- #
6374
- # Note: Do not set redirect_stderr=true in an [eventlistener:x] section. Eventlisteners use stdout and stdin to
6375
- # communicate with supervisord. If stderr is redirected, output from stderr will interfere with the eventlistener
6376
- # protocol.
6377
- redirect_stderr: bool = False
6939
+ def __lt__(self, other):
6940
+ return self.config.priority < other.config.priority
6378
6941
 
6942
+ def __eq__(self, other):
6943
+ return self.config.priority == other.config.priority
6379
6944
 
6380
- @dc.dataclass(frozen=True)
6381
- class ProcessGroupConfig:
6382
- name: str
6383
6945
 
6384
- priority: int = 999
6946
+ ##
6385
6947
 
6386
- processes: ta.Optional[ta.Sequence[ProcessConfig]] = None
6387
6948
 
6949
+ class SupervisorStateManager(abc.ABC):
6950
+ @property
6951
+ @abc.abstractmethod
6952
+ def state(self) -> SupervisorState:
6953
+ raise NotImplementedError
6388
6954
 
6389
- @dc.dataclass(frozen=True)
6390
- class ServerConfig:
6391
- # Instruct supervisord to switch users to this UNIX user account before doing any meaningful processing. The user
6392
- # can only be switched if supervisord is started as the root user.
6393
- user: ta.Optional[str] = None
6955
+ @abc.abstractmethod
6956
+ def set_state(self, state: SupervisorState) -> None:
6957
+ raise NotImplementedError
6394
6958
 
6395
- # If true, supervisord will start in the foreground instead of daemonizing.
6396
- nodaemon: bool = False
6397
6959
 
6398
- # The umask of the supervisord process.
6399
- umask: int = 0o22
6960
+ ##
6400
6961
 
6401
- #
6402
6962
 
6403
- # When supervisord daemonizes, switch to this directory. This option can include the value %(here)s, which expands
6404
- # to the directory in which the supervisord configuration file was found.
6405
- directory: ta.Optional[str] = None
6963
+ class HasDispatchers(abc.ABC):
6964
+ @abc.abstractmethod
6965
+ def get_dispatchers(self) -> 'Dispatchers':
6966
+ raise NotImplementedError
6406
6967
 
6407
- # The location in which supervisord keeps its pid file. This option can include the value %(here)s, which expands to
6408
- # the directory in which the supervisord configuration file was found.
6409
- pidfile: str = 'supervisord.pid'
6410
6968
 
6411
- # The identifier string for this supervisor process, used by the RPC interface.
6412
- identifier: str = 'supervisor'
6969
+ class ProcessDispatcher(FdioHandler, abc.ABC):
6970
+ @property
6971
+ @abc.abstractmethod
6972
+ def channel(self) -> ProcessOutputChannel:
6973
+ raise NotImplementedError
6413
6974
 
6414
- # The minimum number of file descriptors that must be available before supervisord will start successfully.
6415
- min_fds: int = 1024
6416
- # The minimum number of process descriptors that must be available before supervisord will start successfully.
6417
- min_procs: int = 200
6975
+ @property
6976
+ @abc.abstractmethod
6977
+ def process(self) -> 'Process':
6978
+ raise NotImplementedError
6418
6979
 
6419
- # Prevent supervisord from clearing any existing AUTO child log files at startup time. Useful for debugging
6420
- nocleanup: bool = False
6421
6980
 
6422
- # Strip all ANSI escape sequences from child log files.
6423
- strip_ansi: bool = False
6981
+ class ProcessOutputDispatcher(ProcessDispatcher, abc.ABC):
6982
+ @abc.abstractmethod
6983
+ def remove_logs(self) -> None:
6984
+ raise NotImplementedError
6424
6985
 
6425
- #
6986
+ @abc.abstractmethod
6987
+ def reopen_logs(self) -> None:
6988
+ raise NotImplementedError
6426
6989
 
6427
- # The path to the activity log of the supervisord process. This option can include the value %(here)s, which expands
6428
- # to the directory in which the supervisord configuration file was found.
6429
- logfile: str = 'supervisord.log'
6430
6990
 
6431
- # The maximum number of bytes that may be consumed by the activity log file before it is rotated (suffix multipliers
6432
- # like “KB”, “MB”, and “GB” can be used in the value). Set this value to 0 to indicate an unlimited log size.
6433
- logfile_max_bytes: int = 50 * 1024 * 1024
6991
+ class ProcessInputDispatcher(ProcessDispatcher, abc.ABC):
6992
+ @abc.abstractmethod
6993
+ def write(self, chars: ta.Union[bytes, str]) -> None:
6994
+ raise NotImplementedError
6434
6995
 
6435
- # The number of backups to keep around resulting from activity log file rotation. If set to 0, no backups will be
6436
- # kept.
6437
- logfile_backups: int = 10
6996
+ @abc.abstractmethod
6997
+ def flush(self) -> None:
6998
+ raise NotImplementedError
6438
6999
 
6439
- # The logging level, dictating what is written to the supervisord activity log. One of critical, error, warn, info,
6440
- # debug, trace, or blather. Note that at log level debug, the supervisord log file will record the stderr/stdout
6441
- # output of its child processes and extended info about process state changes, which is useful for debugging a
6442
- # process which isn't starting properly.
6443
- loglevel: int = logging.INFO
6444
7000
 
6445
- # The directory used for AUTO child log files. This option can include the value %(here)s, which expands to the
6446
- # directory in which the supervisord configuration file was found.
6447
- child_logdir: str = '/dev/null'
7001
+ ##
6448
7002
 
6449
- # If true and not daemonized, logs will not be directed to stdout.
6450
- silent: bool = False
6451
7003
 
6452
- #
7004
+ class Process(
7005
+ ConfigPriorityOrdered,
7006
+ HasDispatchers,
7007
+ abc.ABC,
7008
+ ):
7009
+ @property
7010
+ @abc.abstractmethod
7011
+ def name(self) -> str:
7012
+ raise NotImplementedError
6453
7013
 
6454
- groups: ta.Optional[ta.Sequence[ProcessGroupConfig]] = None
7014
+ @property
7015
+ @abc.abstractmethod
7016
+ def config(self) -> ProcessConfig:
7017
+ raise NotImplementedError
6455
7018
 
6456
- # TODO: implement - make sure to accept broken symlinks
6457
- group_config_dirs: ta.Optional[ta.Sequence[str]] = None
7019
+ @property
7020
+ @abc.abstractmethod
7021
+ def group(self) -> 'ProcessGroup':
7022
+ raise NotImplementedError
7023
+
7024
+ @property
7025
+ @abc.abstractmethod
7026
+ def pid(self) -> Pid:
7027
+ raise NotImplementedError
6458
7028
 
6459
7029
  #
6460
7030
 
6461
- http_port: ta.Optional[int] = None
7031
+ @abc.abstractmethod
7032
+ def finish(self, sts: Rc) -> None:
7033
+ raise NotImplementedError
6462
7034
 
6463
- #
7035
+ @abc.abstractmethod
7036
+ def stop(self) -> ta.Optional[str]:
7037
+ raise NotImplementedError
6464
7038
 
6465
- @classmethod
6466
- def new(
6467
- cls,
6468
- *,
6469
- umask: ta.Union[int, str] = 0o22,
6470
- directory: ta.Optional[str] = None,
6471
- logfile: str = 'supervisord.log',
6472
- logfile_max_bytes: ta.Union[int, str] = 50 * 1024 * 1024,
6473
- loglevel: ta.Union[int, str] = logging.INFO,
6474
- pidfile: str = 'supervisord.pid',
6475
- child_logdir: ta.Optional[str] = None,
6476
- **kwargs: ta.Any,
6477
- ) -> 'ServerConfig':
6478
- return cls(
6479
- umask=parse_octal(umask),
6480
- directory=check_existing_dir(directory) if directory is not None else None,
6481
- logfile=check_path_with_existing_dir(logfile),
6482
- logfile_max_bytes=parse_bytes_size(logfile_max_bytes),
6483
- loglevel=parse_logging_level(loglevel),
6484
- pidfile=check_path_with_existing_dir(pidfile),
6485
- child_logdir=child_logdir if child_logdir else tempfile.gettempdir(),
6486
- **kwargs,
6487
- )
7039
+ @abc.abstractmethod
7040
+ def give_up(self) -> None:
7041
+ raise NotImplementedError
7042
+
7043
+ @abc.abstractmethod
7044
+ def transition(self) -> None:
7045
+ raise NotImplementedError
7046
+
7047
+ @property
7048
+ @abc.abstractmethod
7049
+ def state(self) -> ProcessState:
7050
+ raise NotImplementedError
7051
+
7052
+ @abc.abstractmethod
7053
+ def after_setuid(self) -> None:
7054
+ raise NotImplementedError
6488
7055
 
6489
7056
 
6490
7057
  ##
6491
7058
 
6492
7059
 
6493
- def prepare_process_group_config(dct: ConfigMapping) -> ConfigMapping:
6494
- out = dict(dct)
6495
- out['processes'] = build_config_named_children(out.get('processes'))
6496
- return out
7060
+ class ProcessGroup(
7061
+ ConfigPriorityOrdered,
7062
+ KeyedCollectionAccessors[str, Process],
7063
+ abc.ABC,
7064
+ ):
7065
+ @property
7066
+ @abc.abstractmethod
7067
+ def name(self) -> str:
7068
+ raise NotImplementedError
6497
7069
 
7070
+ @property
7071
+ @abc.abstractmethod
7072
+ def config(self) -> ProcessGroupConfig:
7073
+ raise NotImplementedError
6498
7074
 
6499
- def prepare_server_config(dct: ta.Mapping[str, ta.Any]) -> ta.Mapping[str, ta.Any]:
6500
- out = dict(dct)
6501
- group_dcts = build_config_named_children(out.get('groups'))
6502
- out['groups'] = [prepare_process_group_config(group_dct) for group_dct in group_dcts or []]
6503
- return out
7075
+ @property
7076
+ @abc.abstractmethod
7077
+ def by_name(self) -> ta.Mapping[str, Process]:
7078
+ raise NotImplementedError
6504
7079
 
7080
+ #
6505
7081
 
6506
- ##
7082
+ @abc.abstractmethod
7083
+ def stop_all(self) -> None:
7084
+ raise NotImplementedError
6507
7085
 
7086
+ @abc.abstractmethod
7087
+ def get_unstopped_processes(self) -> ta.List[Process]:
7088
+ raise NotImplementedError
6508
7089
 
6509
- def parse_logging_level(value: ta.Union[str, int]) -> int:
6510
- if isinstance(value, int):
6511
- return value
6512
- s = str(value).lower()
6513
- level = logging.getLevelNamesMapping().get(s.upper())
6514
- if level is None:
6515
- raise ValueError(f'bad logging level name {value!r}')
6516
- return level
7090
+ @abc.abstractmethod
7091
+ def before_remove(self) -> None:
7092
+ raise NotImplementedError
6517
7093
 
6518
7094
 
6519
7095
  ########################################
@@ -7022,368 +7598,59 @@ class CoroHttpServer:
7022
7598
  ##
7023
7599
 
7024
7600
 
7025
- class CoroHttpServerSocketHandler(SocketHandler):
7026
- def __init__(
7027
- self,
7028
- client_address: SocketAddress,
7029
- rfile: ta.BinaryIO,
7030
- wfile: ta.BinaryIO,
7031
- *,
7032
- server_factory: CoroHttpServerFactory,
7033
- log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
7034
- ) -> None:
7035
- super().__init__(
7036
- client_address,
7037
- rfile,
7038
- wfile,
7039
- )
7040
-
7041
- self._server_factory = server_factory
7042
- self._log_handler = log_handler
7043
-
7044
- def handle(self) -> None:
7045
- server = self._server_factory(self._client_address)
7046
-
7047
- gen = server.coro_handle()
7048
-
7049
- o = next(gen)
7050
- while True:
7051
- if isinstance(o, CoroHttpServer.AnyLogIo):
7052
- i = None
7053
- if self._log_handler is not None:
7054
- self._log_handler(server, o)
7055
-
7056
- elif isinstance(o, CoroHttpServer.ReadIo):
7057
- i = self._rfile.read(o.sz)
7058
-
7059
- elif isinstance(o, CoroHttpServer.ReadLineIo):
7060
- i = self._rfile.readline(o.sz)
7061
-
7062
- elif isinstance(o, CoroHttpServer.WriteIo):
7063
- i = None
7064
- self._wfile.write(o.data)
7065
- self._wfile.flush()
7066
-
7067
- else:
7068
- raise TypeError(o)
7069
-
7070
- try:
7071
- if i is not None:
7072
- o = gen.send(i)
7073
- else:
7074
- o = next(gen)
7075
- except StopIteration:
7076
- break
7077
-
7078
-
7079
- ########################################
7080
- # ../types.py
7081
-
7082
-
7083
- ##
7084
-
7085
-
7086
- class ExitNow(Exception): # noqa
7087
- pass
7088
-
7089
-
7090
- ServerEpoch = ta.NewType('ServerEpoch', int)
7091
-
7092
-
7093
- ##
7094
-
7095
-
7096
- @functools.total_ordering
7097
- class ConfigPriorityOrdered(abc.ABC):
7098
- @property
7099
- @abc.abstractmethod
7100
- def config(self) -> ta.Any:
7101
- raise NotImplementedError
7102
-
7103
- def __lt__(self, other):
7104
- return self.config.priority < other.config.priority
7105
-
7106
- def __eq__(self, other):
7107
- return self.config.priority == other.config.priority
7108
-
7109
-
7110
- ##
7111
-
7112
-
7113
- class SupervisorStateManager(abc.ABC):
7114
- @property
7115
- @abc.abstractmethod
7116
- def state(self) -> SupervisorState:
7117
- raise NotImplementedError
7118
-
7119
- @abc.abstractmethod
7120
- def set_state(self, state: SupervisorState) -> None:
7121
- raise NotImplementedError
7122
-
7123
-
7124
- ##
7125
-
7126
-
7127
- class HasDispatchers(abc.ABC):
7128
- @abc.abstractmethod
7129
- def get_dispatchers(self) -> 'Dispatchers':
7130
- raise NotImplementedError
7131
-
7132
-
7133
- class ProcessDispatcher(FdioHandler, abc.ABC):
7134
- @property
7135
- @abc.abstractmethod
7136
- def channel(self) -> ProcessOutputChannel:
7137
- raise NotImplementedError
7138
-
7139
- @property
7140
- @abc.abstractmethod
7141
- def process(self) -> 'Process':
7142
- raise NotImplementedError
7143
-
7144
-
7145
- class ProcessOutputDispatcher(ProcessDispatcher, abc.ABC):
7146
- @abc.abstractmethod
7147
- def remove_logs(self) -> None:
7148
- raise NotImplementedError
7149
-
7150
- @abc.abstractmethod
7151
- def reopen_logs(self) -> None:
7152
- raise NotImplementedError
7153
-
7154
-
7155
- class ProcessInputDispatcher(ProcessDispatcher, abc.ABC):
7156
- @abc.abstractmethod
7157
- def write(self, chars: ta.Union[bytes, str]) -> None:
7158
- raise NotImplementedError
7159
-
7160
- @abc.abstractmethod
7161
- def flush(self) -> None:
7162
- raise NotImplementedError
7163
-
7164
-
7165
- ##
7166
-
7167
-
7168
- class Process(
7169
- ConfigPriorityOrdered,
7170
- HasDispatchers,
7171
- abc.ABC,
7172
- ):
7173
- @property
7174
- @abc.abstractmethod
7175
- def name(self) -> str:
7176
- raise NotImplementedError
7177
-
7178
- @property
7179
- @abc.abstractmethod
7180
- def config(self) -> ProcessConfig:
7181
- raise NotImplementedError
7182
-
7183
- @property
7184
- @abc.abstractmethod
7185
- def group(self) -> 'ProcessGroup':
7186
- raise NotImplementedError
7187
-
7188
- @property
7189
- @abc.abstractmethod
7190
- def pid(self) -> Pid:
7191
- raise NotImplementedError
7192
-
7193
- #
7194
-
7195
- @abc.abstractmethod
7196
- def finish(self, sts: Rc) -> None:
7197
- raise NotImplementedError
7198
-
7199
- @abc.abstractmethod
7200
- def stop(self) -> ta.Optional[str]:
7201
- raise NotImplementedError
7202
-
7203
- @abc.abstractmethod
7204
- def give_up(self) -> None:
7205
- raise NotImplementedError
7206
-
7207
- @abc.abstractmethod
7208
- def transition(self) -> None:
7209
- raise NotImplementedError
7210
-
7211
- @property
7212
- @abc.abstractmethod
7213
- def state(self) -> ProcessState:
7214
- raise NotImplementedError
7215
-
7216
- @abc.abstractmethod
7217
- def after_setuid(self) -> None:
7218
- raise NotImplementedError
7219
-
7220
-
7221
- ##
7222
-
7223
-
7224
- class ProcessGroup(
7225
- ConfigPriorityOrdered,
7226
- KeyedCollectionAccessors[str, Process],
7227
- abc.ABC,
7228
- ):
7229
- @property
7230
- @abc.abstractmethod
7231
- def name(self) -> str:
7232
- raise NotImplementedError
7233
-
7234
- @property
7235
- @abc.abstractmethod
7236
- def config(self) -> ProcessGroupConfig:
7237
- raise NotImplementedError
7238
-
7239
- @property
7240
- @abc.abstractmethod
7241
- def by_name(self) -> ta.Mapping[str, Process]:
7242
- raise NotImplementedError
7243
-
7244
- #
7245
-
7246
- @abc.abstractmethod
7247
- def stop_all(self) -> None:
7248
- raise NotImplementedError
7249
-
7250
- @abc.abstractmethod
7251
- def get_unstopped_processes(self) -> ta.List[Process]:
7252
- raise NotImplementedError
7253
-
7254
- @abc.abstractmethod
7255
- def before_remove(self) -> None:
7256
- raise NotImplementedError
7257
-
7258
-
7259
- ########################################
7260
- # ../../../omlish/http/coro/fdio.py
7261
-
7262
-
7263
- class CoroHttpServerConnectionFdioHandler(SocketFdioHandler):
7601
+ class CoroHttpServerSocketHandler(SocketHandler):
7264
7602
  def __init__(
7265
7603
  self,
7266
- addr: SocketAddress,
7267
- sock: socket.socket,
7268
- handler: HttpHandler,
7604
+ client_address: SocketAddress,
7605
+ rfile: ta.BinaryIO,
7606
+ wfile: ta.BinaryIO,
7269
7607
  *,
7270
- read_size: int = 0x10000,
7271
- write_size: int = 0x10000,
7608
+ server_factory: CoroHttpServerFactory,
7272
7609
  log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
7273
7610
  ) -> None:
7274
- check.state(not sock.getblocking())
7275
-
7276
- super().__init__(addr, sock)
7277
-
7278
- self._handler = handler
7279
- self._read_size = read_size
7280
- self._write_size = write_size
7281
- self._log_handler = log_handler
7282
-
7283
- self._read_buf = ReadableListBuffer()
7284
- self._write_buf: IncrementalWriteBuffer | None = None
7285
-
7286
- self._coro_srv = CoroHttpServer(
7287
- addr,
7288
- handler=self._handler,
7611
+ super().__init__(
7612
+ client_address,
7613
+ rfile,
7614
+ wfile,
7289
7615
  )
7290
- self._srv_coro: ta.Optional[ta.Generator[CoroHttpServer.Io, ta.Optional[bytes], None]] = self._coro_srv.coro_handle() # noqa
7291
7616
 
7292
- self._cur_io: CoroHttpServer.Io | None = None
7293
- self._next_io()
7617
+ self._server_factory = server_factory
7618
+ self._log_handler = log_handler
7294
7619
 
7295
- #
7620
+ def handle(self) -> None:
7621
+ server = self._server_factory(self._client_address)
7296
7622
 
7297
- def _next_io(self) -> None: # noqa
7298
- coro = check.not_none(self._srv_coro)
7623
+ gen = server.coro_handle()
7299
7624
 
7300
- d: bytes | None = None
7301
- o = self._cur_io
7625
+ o = next(gen)
7302
7626
  while True:
7303
- if o is None:
7304
- try:
7305
- if d is not None:
7306
- o = coro.send(d)
7307
- d = None
7308
- else:
7309
- o = next(coro)
7310
- except StopIteration:
7311
- self.close()
7312
- o = None
7313
- break
7314
-
7315
7627
  if isinstance(o, CoroHttpServer.AnyLogIo):
7628
+ i = None
7316
7629
  if self._log_handler is not None:
7317
- self._log_handler(self._coro_srv, o)
7318
- o = None
7630
+ self._log_handler(server, o)
7319
7631
 
7320
7632
  elif isinstance(o, CoroHttpServer.ReadIo):
7321
- if (d := self._read_buf.read(o.sz)) is None:
7322
- break
7323
- o = None
7633
+ i = self._rfile.read(o.sz)
7324
7634
 
7325
7635
  elif isinstance(o, CoroHttpServer.ReadLineIo):
7326
- if (d := self._read_buf.read_until(b'\n')) is None:
7327
- break
7328
- o = None
7636
+ i = self._rfile.readline(o.sz)
7329
7637
 
7330
7638
  elif isinstance(o, CoroHttpServer.WriteIo):
7331
- check.none(self._write_buf)
7332
- self._write_buf = IncrementalWriteBuffer(o.data, write_size=self._write_size)
7333
- break
7639
+ i = None
7640
+ self._wfile.write(o.data)
7641
+ self._wfile.flush()
7334
7642
 
7335
7643
  else:
7336
7644
  raise TypeError(o)
7337
7645
 
7338
- self._cur_io = o
7339
-
7340
- #
7341
-
7342
- def readable(self) -> bool:
7343
- return True
7344
-
7345
- def writable(self) -> bool:
7346
- return self._write_buf is not None
7347
-
7348
- #
7349
-
7350
- def on_readable(self) -> None:
7351
- try:
7352
- buf = check.not_none(self._sock).recv(self._read_size)
7353
- except BlockingIOError:
7354
- return
7355
- except ConnectionResetError:
7356
- self.close()
7357
- return
7358
- if not buf:
7359
- self.close()
7360
- return
7361
-
7362
- self._read_buf.feed(buf)
7363
-
7364
- if isinstance(self._cur_io, CoroHttpServer.AnyReadIo):
7365
- self._next_io()
7366
-
7367
- def on_writable(self) -> None:
7368
- check.isinstance(self._cur_io, CoroHttpServer.WriteIo)
7369
- wb = check.not_none(self._write_buf)
7370
- while wb.rem > 0:
7371
- def send(d: bytes) -> int:
7372
- try:
7373
- return check.not_none(self._sock).send(d)
7374
- except ConnectionResetError:
7375
- self.close()
7376
- return 0
7377
- except BlockingIOError:
7378
- return 0
7379
- if not wb.write(send):
7646
+ try:
7647
+ if i is not None:
7648
+ o = gen.send(i)
7649
+ else:
7650
+ o = next(gen)
7651
+ except StopIteration:
7380
7652
  break
7381
7653
 
7382
- if wb.rem < 1:
7383
- self._write_buf = None
7384
- self._cur_io = None
7385
- self._next_io()
7386
-
7387
7654
 
7388
7655
  ########################################
7389
7656
  # ../dispatchers.py
@@ -8085,6 +8352,135 @@ class SupervisorSetupImpl(SupervisorSetup):
8085
8352
  os.umask(self._config.umask)
8086
8353
 
8087
8354
 
8355
+ ########################################
8356
+ # ../../../omlish/http/coro/fdio.py
8357
+
8358
+
8359
+ class CoroHttpServerConnectionFdioHandler(SocketFdioHandler):
8360
+ def __init__(
8361
+ self,
8362
+ addr: SocketAddress,
8363
+ sock: socket.socket,
8364
+ handler: HttpHandler,
8365
+ *,
8366
+ read_size: int = 0x10000,
8367
+ write_size: int = 0x10000,
8368
+ log_handler: ta.Optional[ta.Callable[[CoroHttpServer, CoroHttpServer.AnyLogIo], None]] = None,
8369
+ ) -> None:
8370
+ check.state(not sock.getblocking())
8371
+
8372
+ super().__init__(addr, sock)
8373
+
8374
+ self._handler = handler
8375
+ self._read_size = read_size
8376
+ self._write_size = write_size
8377
+ self._log_handler = log_handler
8378
+
8379
+ self._read_buf = ReadableListBuffer()
8380
+ self._write_buf: IncrementalWriteBuffer | None = None
8381
+
8382
+ self._coro_srv = CoroHttpServer(
8383
+ addr,
8384
+ handler=self._handler,
8385
+ )
8386
+ self._srv_coro: ta.Optional[ta.Generator[CoroHttpServer.Io, ta.Optional[bytes], None]] = self._coro_srv.coro_handle() # noqa
8387
+
8388
+ self._cur_io: CoroHttpServer.Io | None = None
8389
+ self._next_io()
8390
+
8391
+ #
8392
+
8393
+ def _next_io(self) -> None: # noqa
8394
+ coro = check.not_none(self._srv_coro)
8395
+
8396
+ d: bytes | None = None
8397
+ o = self._cur_io
8398
+ while True:
8399
+ if o is None:
8400
+ try:
8401
+ if d is not None:
8402
+ o = coro.send(d)
8403
+ d = None
8404
+ else:
8405
+ o = next(coro)
8406
+ except StopIteration:
8407
+ self.close()
8408
+ o = None
8409
+ break
8410
+
8411
+ if isinstance(o, CoroHttpServer.AnyLogIo):
8412
+ if self._log_handler is not None:
8413
+ self._log_handler(self._coro_srv, o)
8414
+ o = None
8415
+
8416
+ elif isinstance(o, CoroHttpServer.ReadIo):
8417
+ if (d := self._read_buf.read(o.sz)) is None:
8418
+ break
8419
+ o = None
8420
+
8421
+ elif isinstance(o, CoroHttpServer.ReadLineIo):
8422
+ if (d := self._read_buf.read_until(b'\n')) is None:
8423
+ break
8424
+ o = None
8425
+
8426
+ elif isinstance(o, CoroHttpServer.WriteIo):
8427
+ check.none(self._write_buf)
8428
+ self._write_buf = IncrementalWriteBuffer(o.data, write_size=self._write_size)
8429
+ break
8430
+
8431
+ else:
8432
+ raise TypeError(o)
8433
+
8434
+ self._cur_io = o
8435
+
8436
+ #
8437
+
8438
+ def readable(self) -> bool:
8439
+ return True
8440
+
8441
+ def writable(self) -> bool:
8442
+ return self._write_buf is not None
8443
+
8444
+ #
8445
+
8446
+ def on_readable(self) -> None:
8447
+ try:
8448
+ buf = check.not_none(self._sock).recv(self._read_size)
8449
+ except BlockingIOError:
8450
+ return
8451
+ except ConnectionResetError:
8452
+ self.close()
8453
+ return
8454
+ if not buf:
8455
+ self.close()
8456
+ return
8457
+
8458
+ self._read_buf.feed(buf)
8459
+
8460
+ if isinstance(self._cur_io, CoroHttpServer.AnyReadIo):
8461
+ self._next_io()
8462
+
8463
+ def on_writable(self) -> None:
8464
+ check.isinstance(self._cur_io, CoroHttpServer.WriteIo)
8465
+ wb = check.not_none(self._write_buf)
8466
+ while wb.rem > 0:
8467
+ def send(d: bytes) -> int:
8468
+ try:
8469
+ return check.not_none(self._sock).send(d)
8470
+ except ConnectionResetError:
8471
+ self.close()
8472
+ return 0
8473
+ except BlockingIOError:
8474
+ return 0
8475
+ if not wb.write(send):
8476
+ break
8477
+
8478
+ if wb.rem < 1:
8479
+ self._write_buf = None
8480
+ self._cur_io = None
8481
+ self._next_io()
8482
+
8483
+
8088
8484
  ########################################
8089
8485
  # ../groups.py
8090
8486
 
@@ -9660,7 +10056,7 @@ def main(
9660
10056
 
9661
10057
  # if we hup, restart by making a new Supervisor()
9662
10058
  for epoch in itertools.count():
9663
- config = read_config_file(
10059
+ config = load_config_file_obj(
9664
10060
  os.path.expanduser(cf),
9665
10061
  ServerConfig,
9666
10062
  prepare=prepare_server_config,