ominfra 0.0.0.dev142__py3-none-any.whl → 0.0.0.dev143__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.
- ominfra/manage/__init__.py +10 -0
- ominfra/manage/__main__.py +4 -0
- ominfra/manage/bootstrap.py +11 -0
- ominfra/manage/bootstrap_.py +18 -0
- ominfra/manage/commands/base.py +133 -1
- ominfra/manage/commands/execution.py +23 -0
- ominfra/manage/commands/inject.py +122 -0
- ominfra/manage/commands/marshal.py +26 -0
- ominfra/manage/config.py +10 -0
- ominfra/manage/inject.py +55 -0
- ominfra/manage/main.py +49 -89
- ominfra/manage/marshal.py +12 -0
- ominfra/manage/remote/__init__.py +0 -0
- ominfra/manage/{protocol.py → remote/channel.py} +9 -2
- ominfra/manage/remote/config.py +12 -0
- ominfra/manage/remote/execution.py +193 -0
- ominfra/manage/remote/inject.py +29 -0
- ominfra/manage/{payload.py → remote/payload.py} +7 -1
- ominfra/manage/{spawning.py → remote/spawning.py} +29 -29
- ominfra/pyremote.py +40 -3
- ominfra/scripts/journald2aws.py +98 -50
- ominfra/scripts/manage.py +1854 -169
- ominfra/scripts/supervisor.py +99 -50
- ominfra/supervisor/inject.py +2 -1
- {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev143.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev143.dist-info}/RECORD +30 -17
- {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev143.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev143.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev143.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev143.dist-info}/top_level.txt +0 -0
ominfra/scripts/manage.py
CHANGED
@@ -31,6 +31,7 @@ import subprocess
|
|
31
31
|
import sys
|
32
32
|
import threading
|
33
33
|
import time
|
34
|
+
import traceback
|
34
35
|
import types
|
35
36
|
import typing as ta
|
36
37
|
import uuid
|
@@ -48,10 +49,6 @@ if sys.version_info < (3, 8):
|
|
48
49
|
########################################
|
49
50
|
|
50
51
|
|
51
|
-
# commands/base.py
|
52
|
-
CommandT = ta.TypeVar('CommandT', bound='Command')
|
53
|
-
CommandOutputT = ta.TypeVar('CommandOutputT', bound='Command.Output')
|
54
|
-
|
55
52
|
# ../../omlish/lite/cached.py
|
56
53
|
T = ta.TypeVar('T')
|
57
54
|
CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
|
@@ -59,37 +56,39 @@ CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
|
|
59
56
|
# ../../omlish/lite/check.py
|
60
57
|
SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
|
61
58
|
|
59
|
+
# commands/base.py
|
60
|
+
CommandT = ta.TypeVar('CommandT', bound='Command')
|
61
|
+
CommandOutputT = ta.TypeVar('CommandOutputT', bound='Command.Output')
|
62
|
+
|
63
|
+
# ../../omlish/lite/inject.py
|
64
|
+
U = ta.TypeVar('U')
|
65
|
+
InjectorKeyCls = ta.Union[type, ta.NewType]
|
66
|
+
InjectorProviderFn = ta.Callable[['Injector'], ta.Any]
|
67
|
+
InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
|
68
|
+
InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
|
69
|
+
|
62
70
|
# ../../omlish/lite/subprocesses.py
|
63
71
|
SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull']
|
64
72
|
|
65
73
|
|
66
74
|
########################################
|
67
|
-
# ../
|
68
|
-
|
69
|
-
|
70
|
-
##
|
75
|
+
# ../config.py
|
71
76
|
|
72
77
|
|
73
78
|
@dc.dataclass(frozen=True)
|
74
|
-
class
|
75
|
-
|
76
|
-
class Output(abc.ABC): # noqa
|
77
|
-
pass
|
78
|
-
|
79
|
-
|
80
|
-
##
|
79
|
+
class MainConfig:
|
80
|
+
log_level: ta.Optional[str] = 'INFO'
|
81
81
|
|
82
|
-
|
83
|
-
class CommandExecutor(abc.ABC, ta.Generic[CommandT, CommandOutputT]):
|
84
|
-
@abc.abstractmethod
|
85
|
-
def execute(self, i: CommandT) -> CommandOutputT:
|
86
|
-
raise NotImplementedError
|
82
|
+
debug: bool = False
|
87
83
|
|
88
84
|
|
89
85
|
########################################
|
90
86
|
# ../../pyremote.py
|
91
87
|
"""
|
92
88
|
Basically this: https://mitogen.networkgenomics.com/howitworks.html
|
89
|
+
|
90
|
+
TODO:
|
91
|
+
- log: ta.Optional[logging.Logger] = None + log.debug's
|
93
92
|
"""
|
94
93
|
|
95
94
|
|
@@ -100,6 +99,9 @@ Basically this: https://mitogen.networkgenomics.com/howitworks.html
|
|
100
99
|
class PyremoteBootstrapOptions:
|
101
100
|
debug: bool = False
|
102
101
|
|
102
|
+
DEFAULT_MAIN_NAME_OVERRIDE: ta.ClassVar[str] = '__pyremote__'
|
103
|
+
main_name_override: ta.Optional[str] = DEFAULT_MAIN_NAME_OVERRIDE
|
104
|
+
|
103
105
|
|
104
106
|
##
|
105
107
|
|
@@ -416,6 +418,10 @@ def pyremote_bootstrap_finalize() -> PyremotePayloadRuntime:
|
|
416
418
|
os.dup2(nfd := os.open('/dev/null', os.O_WRONLY), 1)
|
417
419
|
os.close(nfd)
|
418
420
|
|
421
|
+
if (mn := options.main_name_override) is not None:
|
422
|
+
# Inspections like typing.get_type_hints need an entry in sys.modules.
|
423
|
+
sys.modules[mn] = sys.modules['__main__']
|
424
|
+
|
419
425
|
# Write fourth ack
|
420
426
|
output.write(_PYREMOTE_BOOTSTRAP_ACK3)
|
421
427
|
|
@@ -434,14 +440,41 @@ def pyremote_bootstrap_finalize() -> PyremotePayloadRuntime:
|
|
434
440
|
|
435
441
|
|
436
442
|
class PyremoteBootstrapDriver:
|
437
|
-
def __init__(
|
443
|
+
def __init__(
|
444
|
+
self,
|
445
|
+
main_src: ta.Union[str, ta.Sequence[str]],
|
446
|
+
options: PyremoteBootstrapOptions = PyremoteBootstrapOptions(),
|
447
|
+
) -> None:
|
438
448
|
super().__init__()
|
439
449
|
|
440
450
|
self._main_src = main_src
|
441
|
-
self._main_z = zlib.compress(main_src.encode('utf-8'))
|
442
|
-
|
443
451
|
self._options = options
|
452
|
+
|
453
|
+
self._prepared_main_src = self._prepare_main_src(main_src, options)
|
454
|
+
self._main_z = zlib.compress(self._prepared_main_src.encode('utf-8'))
|
455
|
+
|
444
456
|
self._options_json = json.dumps(dc.asdict(options), indent=None, separators=(',', ':')).encode('utf-8') # noqa
|
457
|
+
#
|
458
|
+
|
459
|
+
@classmethod
|
460
|
+
def _prepare_main_src(
|
461
|
+
cls,
|
462
|
+
main_src: ta.Union[str, ta.Sequence[str]],
|
463
|
+
options: PyremoteBootstrapOptions,
|
464
|
+
) -> str:
|
465
|
+
parts: ta.List[str]
|
466
|
+
if isinstance(main_src, str):
|
467
|
+
parts = [main_src]
|
468
|
+
else:
|
469
|
+
parts = list(main_src)
|
470
|
+
|
471
|
+
if (mn := options.main_name_override) is not None:
|
472
|
+
parts.insert(0, f'__name__ = {mn!r}')
|
473
|
+
|
474
|
+
if len(parts) == 1:
|
475
|
+
return parts[0]
|
476
|
+
else:
|
477
|
+
return '\n\n'.join(parts)
|
445
478
|
|
446
479
|
#
|
447
480
|
|
@@ -702,6 +735,99 @@ json_dump_compact: ta.Callable[..., bytes] = functools.partial(json.dump, **JSON
|
|
702
735
|
json_dumps_compact: ta.Callable[..., str] = functools.partial(json.dumps, **JSON_COMPACT_KWARGS)
|
703
736
|
|
704
737
|
|
738
|
+
########################################
|
739
|
+
# ../../../omlish/lite/maybes.py
|
740
|
+
|
741
|
+
|
742
|
+
class Maybe(ta.Generic[T]):
|
743
|
+
@property
|
744
|
+
@abc.abstractmethod
|
745
|
+
def present(self) -> bool:
|
746
|
+
raise NotImplementedError
|
747
|
+
|
748
|
+
@abc.abstractmethod
|
749
|
+
def must(self) -> T:
|
750
|
+
raise NotImplementedError
|
751
|
+
|
752
|
+
@classmethod
|
753
|
+
def just(cls, v: T) -> 'Maybe[T]':
|
754
|
+
return tuple.__new__(_Maybe, (v,)) # noqa
|
755
|
+
|
756
|
+
_empty: ta.ClassVar['Maybe']
|
757
|
+
|
758
|
+
@classmethod
|
759
|
+
def empty(cls) -> 'Maybe[T]':
|
760
|
+
return Maybe._empty
|
761
|
+
|
762
|
+
|
763
|
+
class _Maybe(Maybe[T], tuple):
|
764
|
+
__slots__ = ()
|
765
|
+
|
766
|
+
def __init_subclass__(cls, **kwargs):
|
767
|
+
raise TypeError
|
768
|
+
|
769
|
+
@property
|
770
|
+
def present(self) -> bool:
|
771
|
+
return bool(self)
|
772
|
+
|
773
|
+
def must(self) -> T:
|
774
|
+
if not self:
|
775
|
+
raise ValueError
|
776
|
+
return self[0]
|
777
|
+
|
778
|
+
|
779
|
+
Maybe._empty = tuple.__new__(_Maybe, ()) # noqa
|
780
|
+
|
781
|
+
|
782
|
+
########################################
|
783
|
+
# ../../../omlish/lite/pycharm.py
|
784
|
+
|
785
|
+
|
786
|
+
DEFAULT_PYCHARM_VERSION = '242.23726.102'
|
787
|
+
|
788
|
+
|
789
|
+
@dc.dataclass(frozen=True)
|
790
|
+
class PycharmRemoteDebug:
|
791
|
+
port: int
|
792
|
+
host: ta.Optional[str] = 'localhost'
|
793
|
+
install_version: ta.Optional[str] = DEFAULT_PYCHARM_VERSION
|
794
|
+
|
795
|
+
|
796
|
+
def pycharm_debug_connect(prd: PycharmRemoteDebug) -> None:
|
797
|
+
if prd.install_version is not None:
|
798
|
+
import subprocess
|
799
|
+
import sys
|
800
|
+
subprocess.check_call([
|
801
|
+
sys.executable,
|
802
|
+
'-mpip',
|
803
|
+
'install',
|
804
|
+
f'pydevd-pycharm~={prd.install_version}',
|
805
|
+
])
|
806
|
+
|
807
|
+
pydevd_pycharm = __import__('pydevd_pycharm') # noqa
|
808
|
+
pydevd_pycharm.settrace(
|
809
|
+
prd.host,
|
810
|
+
port=prd.port,
|
811
|
+
stdoutToServer=True,
|
812
|
+
stderrToServer=True,
|
813
|
+
)
|
814
|
+
|
815
|
+
|
816
|
+
def pycharm_debug_preamble(prd: PycharmRemoteDebug) -> str:
|
817
|
+
import inspect
|
818
|
+
import textwrap
|
819
|
+
|
820
|
+
return textwrap.dedent(f"""
|
821
|
+
{inspect.getsource(pycharm_debug_connect)}
|
822
|
+
|
823
|
+
pycharm_debug_connect(PycharmRemoteDebug(
|
824
|
+
{prd.port!r},
|
825
|
+
host={prd.host!r},
|
826
|
+
install_version={prd.install_version!r},
|
827
|
+
))
|
828
|
+
""")
|
829
|
+
|
830
|
+
|
705
831
|
########################################
|
706
832
|
# ../../../omlish/lite/reflect.py
|
707
833
|
|
@@ -758,7 +884,238 @@ def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
|
|
758
884
|
|
759
885
|
|
760
886
|
########################################
|
761
|
-
#
|
887
|
+
# ../../../omlish/lite/strings.py
|
888
|
+
|
889
|
+
|
890
|
+
##
|
891
|
+
|
892
|
+
|
893
|
+
def camel_case(name: str, lower: bool = False) -> str:
|
894
|
+
if not name:
|
895
|
+
return ''
|
896
|
+
s = ''.join(map(str.capitalize, name.split('_'))) # noqa
|
897
|
+
if lower:
|
898
|
+
s = s[0].lower() + s[1:]
|
899
|
+
return s
|
900
|
+
|
901
|
+
|
902
|
+
def snake_case(name: str) -> str:
|
903
|
+
uppers: list[int | None] = [i for i, c in enumerate(name) if c.isupper()]
|
904
|
+
return '_'.join([name[l:r].lower() for l, r in zip([None, *uppers], [*uppers, None])]).strip('_')
|
905
|
+
|
906
|
+
|
907
|
+
##
|
908
|
+
|
909
|
+
|
910
|
+
def is_dunder(name: str) -> bool:
|
911
|
+
return (
|
912
|
+
name[:2] == name[-2:] == '__' and
|
913
|
+
name[2:3] != '_' and
|
914
|
+
name[-3:-2] != '_' and
|
915
|
+
len(name) > 4
|
916
|
+
)
|
917
|
+
|
918
|
+
|
919
|
+
def is_sunder(name: str) -> bool:
|
920
|
+
return (
|
921
|
+
name[0] == name[-1] == '_' and
|
922
|
+
name[1:2] != '_' and
|
923
|
+
name[-2:-1] != '_' and
|
924
|
+
len(name) > 2
|
925
|
+
)
|
926
|
+
|
927
|
+
|
928
|
+
##
|
929
|
+
|
930
|
+
|
931
|
+
def attr_repr(obj: ta.Any, *attrs: str) -> str:
|
932
|
+
return f'{type(obj).__name__}({", ".join(f"{attr}={getattr(obj, attr)!r}" for attr in attrs)})'
|
933
|
+
|
934
|
+
|
935
|
+
##
|
936
|
+
|
937
|
+
|
938
|
+
FORMAT_NUM_BYTES_SUFFIXES: ta.Sequence[str] = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB']
|
939
|
+
|
940
|
+
|
941
|
+
def format_num_bytes(num_bytes: int) -> str:
|
942
|
+
for i, suffix in enumerate(FORMAT_NUM_BYTES_SUFFIXES):
|
943
|
+
value = num_bytes / 1024 ** i
|
944
|
+
if num_bytes < 1024 ** (i + 1):
|
945
|
+
if value.is_integer():
|
946
|
+
return f'{int(value)}{suffix}'
|
947
|
+
else:
|
948
|
+
return f'{value:.2f}{suffix}'
|
949
|
+
|
950
|
+
return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
|
951
|
+
|
952
|
+
|
953
|
+
########################################
|
954
|
+
# ../commands/base.py
|
955
|
+
|
956
|
+
|
957
|
+
##
|
958
|
+
|
959
|
+
|
960
|
+
@dc.dataclass(frozen=True)
|
961
|
+
class Command(abc.ABC, ta.Generic[CommandOutputT]):
|
962
|
+
@dc.dataclass(frozen=True)
|
963
|
+
class Output(abc.ABC): # noqa
|
964
|
+
pass
|
965
|
+
|
966
|
+
@ta.final
|
967
|
+
def execute(self, executor: 'CommandExecutor') -> CommandOutputT:
|
968
|
+
return check_isinstance(executor.execute(self), self.Output) # type: ignore[return-value]
|
969
|
+
|
970
|
+
|
971
|
+
##
|
972
|
+
|
973
|
+
|
974
|
+
@dc.dataclass(frozen=True)
|
975
|
+
class CommandException:
|
976
|
+
name: str
|
977
|
+
repr: str
|
978
|
+
|
979
|
+
traceback: ta.Optional[str] = None
|
980
|
+
|
981
|
+
exc: ta.Optional[ta.Any] = None # Exception
|
982
|
+
|
983
|
+
cmd: ta.Optional[Command] = None
|
984
|
+
|
985
|
+
@classmethod
|
986
|
+
def of(
|
987
|
+
cls,
|
988
|
+
exc: Exception,
|
989
|
+
*,
|
990
|
+
omit_exc_object: bool = False,
|
991
|
+
|
992
|
+
cmd: ta.Optional[Command] = None,
|
993
|
+
) -> 'CommandException':
|
994
|
+
return CommandException(
|
995
|
+
name=type(exc).__qualname__,
|
996
|
+
repr=repr(exc),
|
997
|
+
|
998
|
+
traceback=(
|
999
|
+
''.join(traceback.format_tb(exc.__traceback__))
|
1000
|
+
if getattr(exc, '__traceback__', None) is not None else None
|
1001
|
+
),
|
1002
|
+
|
1003
|
+
exc=None if omit_exc_object else exc,
|
1004
|
+
|
1005
|
+
cmd=cmd,
|
1006
|
+
)
|
1007
|
+
|
1008
|
+
|
1009
|
+
class CommandOutputOrException(abc.ABC, ta.Generic[CommandOutputT]):
|
1010
|
+
@property
|
1011
|
+
@abc.abstractmethod
|
1012
|
+
def output(self) -> ta.Optional[CommandOutputT]:
|
1013
|
+
raise NotImplementedError
|
1014
|
+
|
1015
|
+
@property
|
1016
|
+
@abc.abstractmethod
|
1017
|
+
def exception(self) -> ta.Optional[CommandException]:
|
1018
|
+
raise NotImplementedError
|
1019
|
+
|
1020
|
+
|
1021
|
+
@dc.dataclass(frozen=True)
|
1022
|
+
class CommandOutputOrExceptionData(CommandOutputOrException):
|
1023
|
+
output: ta.Optional[Command.Output] = None
|
1024
|
+
exception: ta.Optional[CommandException] = None
|
1025
|
+
|
1026
|
+
|
1027
|
+
class CommandExecutor(abc.ABC, ta.Generic[CommandT, CommandOutputT]):
|
1028
|
+
@abc.abstractmethod
|
1029
|
+
def execute(self, cmd: CommandT) -> CommandOutputT:
|
1030
|
+
raise NotImplementedError
|
1031
|
+
|
1032
|
+
def try_execute(
|
1033
|
+
self,
|
1034
|
+
cmd: CommandT,
|
1035
|
+
*,
|
1036
|
+
log: ta.Optional[logging.Logger] = None,
|
1037
|
+
omit_exc_object: bool = False,
|
1038
|
+
) -> CommandOutputOrException[CommandOutputT]:
|
1039
|
+
try:
|
1040
|
+
o = self.execute(cmd)
|
1041
|
+
|
1042
|
+
except Exception as e: # noqa
|
1043
|
+
if log is not None:
|
1044
|
+
log.exception('Exception executing command: %r', type(cmd))
|
1045
|
+
|
1046
|
+
return CommandOutputOrExceptionData(exception=CommandException.of(
|
1047
|
+
e,
|
1048
|
+
omit_exc_object=omit_exc_object,
|
1049
|
+
cmd=cmd,
|
1050
|
+
))
|
1051
|
+
|
1052
|
+
else:
|
1053
|
+
return CommandOutputOrExceptionData(output=o)
|
1054
|
+
|
1055
|
+
|
1056
|
+
##
|
1057
|
+
|
1058
|
+
|
1059
|
+
@dc.dataclass(frozen=True)
|
1060
|
+
class CommandRegistration:
|
1061
|
+
command_cls: ta.Type[Command]
|
1062
|
+
|
1063
|
+
name: ta.Optional[str] = None
|
1064
|
+
|
1065
|
+
@property
|
1066
|
+
def name_or_default(self) -> str:
|
1067
|
+
if not (cls_name := self.command_cls.__name__).endswith('Command'):
|
1068
|
+
raise NameError(cls_name)
|
1069
|
+
return snake_case(cls_name[:-len('Command')])
|
1070
|
+
|
1071
|
+
|
1072
|
+
CommandRegistrations = ta.NewType('CommandRegistrations', ta.Sequence[CommandRegistration])
|
1073
|
+
|
1074
|
+
|
1075
|
+
##
|
1076
|
+
|
1077
|
+
|
1078
|
+
@dc.dataclass(frozen=True)
|
1079
|
+
class CommandExecutorRegistration:
|
1080
|
+
command_cls: ta.Type[Command]
|
1081
|
+
executor_cls: ta.Type[CommandExecutor]
|
1082
|
+
|
1083
|
+
|
1084
|
+
CommandExecutorRegistrations = ta.NewType('CommandExecutorRegistrations', ta.Sequence[CommandExecutorRegistration])
|
1085
|
+
|
1086
|
+
|
1087
|
+
##
|
1088
|
+
|
1089
|
+
|
1090
|
+
CommandNameMap = ta.NewType('CommandNameMap', ta.Mapping[str, ta.Type[Command]])
|
1091
|
+
|
1092
|
+
|
1093
|
+
def build_command_name_map(crs: CommandRegistrations) -> CommandNameMap:
|
1094
|
+
dct: ta.Dict[str, ta.Type[Command]] = {}
|
1095
|
+
cr: CommandRegistration
|
1096
|
+
for cr in crs:
|
1097
|
+
if (name := cr.name_or_default) in dct:
|
1098
|
+
raise NameError(name)
|
1099
|
+
dct[name] = cr.command_cls
|
1100
|
+
return CommandNameMap(dct)
|
1101
|
+
|
1102
|
+
|
1103
|
+
########################################
|
1104
|
+
# ../remote/config.py
|
1105
|
+
|
1106
|
+
|
1107
|
+
@dc.dataclass(frozen=True)
|
1108
|
+
class RemoteConfig:
|
1109
|
+
payload_file: ta.Optional[str] = None
|
1110
|
+
|
1111
|
+
pycharm_remote_debug: ta.Optional[PycharmRemoteDebug] = None
|
1112
|
+
|
1113
|
+
|
1114
|
+
########################################
|
1115
|
+
# ../remote/payload.py
|
1116
|
+
|
1117
|
+
|
1118
|
+
RemoteExecutionPayloadFile = ta.NewType('RemoteExecutionPayloadFile', str)
|
762
1119
|
|
763
1120
|
|
764
1121
|
@cached_nullary
|
@@ -773,21 +1130,917 @@ def _is_src_amalg(src: str) -> bool:
|
|
773
1130
|
return False
|
774
1131
|
|
775
1132
|
|
776
|
-
@cached_nullary
|
777
|
-
def _is_self_amalg() -> bool:
|
778
|
-
return _is_src_amalg(_get_self_src())
|
1133
|
+
@cached_nullary
|
1134
|
+
def _is_self_amalg() -> bool:
|
1135
|
+
return _is_src_amalg(_get_self_src())
|
1136
|
+
|
1137
|
+
|
1138
|
+
def get_remote_payload_src(
|
1139
|
+
*,
|
1140
|
+
file: ta.Optional[RemoteExecutionPayloadFile],
|
1141
|
+
) -> str:
|
1142
|
+
if file is not None:
|
1143
|
+
with open(file) as f:
|
1144
|
+
return f.read()
|
1145
|
+
|
1146
|
+
if _is_self_amalg():
|
1147
|
+
return _get_self_src()
|
1148
|
+
|
1149
|
+
import importlib.resources
|
1150
|
+
return importlib.resources.files(__package__.split('.')[0] + '.scripts').joinpath('manage.py').read_text()
|
1151
|
+
|
1152
|
+
|
1153
|
+
########################################
|
1154
|
+
# ../../../omlish/lite/inject.py
|
1155
|
+
|
1156
|
+
|
1157
|
+
###
|
1158
|
+
# types
|
1159
|
+
|
1160
|
+
|
1161
|
+
@dc.dataclass(frozen=True)
|
1162
|
+
class InjectorKey(ta.Generic[T]):
|
1163
|
+
# Before PEP-560 typing.Generic was a metaclass with a __new__ that takes a 'cls' arg, so instantiating a dataclass
|
1164
|
+
# with kwargs (such as through dc.replace) causes `TypeError: __new__() got multiple values for argument 'cls'`.
|
1165
|
+
# See:
|
1166
|
+
# - https://github.com/python/cpython/commit/d911e40e788fb679723d78b6ea11cabf46caed5a
|
1167
|
+
# - https://gist.github.com/wrmsr/4468b86efe9f373b6b114bfe85b98fd3
|
1168
|
+
cls_: InjectorKeyCls
|
1169
|
+
|
1170
|
+
tag: ta.Any = None
|
1171
|
+
array: bool = False
|
1172
|
+
|
1173
|
+
|
1174
|
+
def is_valid_injector_key_cls(cls: ta.Any) -> bool:
|
1175
|
+
return isinstance(cls, type) or is_new_type(cls)
|
1176
|
+
|
1177
|
+
|
1178
|
+
def check_valid_injector_key_cls(cls: T) -> T:
|
1179
|
+
if not is_valid_injector_key_cls(cls):
|
1180
|
+
raise TypeError(cls)
|
1181
|
+
return cls
|
1182
|
+
|
1183
|
+
|
1184
|
+
##
|
1185
|
+
|
1186
|
+
|
1187
|
+
class InjectorProvider(abc.ABC):
|
1188
|
+
@abc.abstractmethod
|
1189
|
+
def provider_fn(self) -> InjectorProviderFn:
|
1190
|
+
raise NotImplementedError
|
1191
|
+
|
1192
|
+
|
1193
|
+
##
|
1194
|
+
|
1195
|
+
|
1196
|
+
@dc.dataclass(frozen=True)
|
1197
|
+
class InjectorBinding:
|
1198
|
+
key: InjectorKey
|
1199
|
+
provider: InjectorProvider
|
1200
|
+
|
1201
|
+
|
1202
|
+
class InjectorBindings(abc.ABC):
|
1203
|
+
@abc.abstractmethod
|
1204
|
+
def bindings(self) -> ta.Iterator[InjectorBinding]:
|
1205
|
+
raise NotImplementedError
|
1206
|
+
|
1207
|
+
##
|
1208
|
+
|
1209
|
+
|
1210
|
+
class Injector(abc.ABC):
|
1211
|
+
@abc.abstractmethod
|
1212
|
+
def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
|
1213
|
+
raise NotImplementedError
|
1214
|
+
|
1215
|
+
@abc.abstractmethod
|
1216
|
+
def provide(self, key: ta.Any) -> ta.Any:
|
1217
|
+
raise NotImplementedError
|
1218
|
+
|
1219
|
+
@abc.abstractmethod
|
1220
|
+
def provide_kwargs(
|
1221
|
+
self,
|
1222
|
+
obj: ta.Any,
|
1223
|
+
*,
|
1224
|
+
skip_args: int = 0,
|
1225
|
+
skip_kwargs: ta.Optional[ta.Iterable[ta.Any]] = None,
|
1226
|
+
) -> ta.Mapping[str, ta.Any]:
|
1227
|
+
raise NotImplementedError
|
1228
|
+
|
1229
|
+
@abc.abstractmethod
|
1230
|
+
def inject(
|
1231
|
+
self,
|
1232
|
+
obj: ta.Any,
|
1233
|
+
*,
|
1234
|
+
args: ta.Optional[ta.Sequence[ta.Any]] = None,
|
1235
|
+
kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
1236
|
+
) -> ta.Any:
|
1237
|
+
raise NotImplementedError
|
1238
|
+
|
1239
|
+
def __getitem__(
|
1240
|
+
self,
|
1241
|
+
target: ta.Union[InjectorKey[T], ta.Type[T]],
|
1242
|
+
) -> T:
|
1243
|
+
return self.provide(target)
|
1244
|
+
|
1245
|
+
|
1246
|
+
###
|
1247
|
+
# exceptions
|
1248
|
+
|
1249
|
+
|
1250
|
+
class InjectorError(Exception):
|
1251
|
+
pass
|
1252
|
+
|
1253
|
+
|
1254
|
+
@dc.dataclass()
|
1255
|
+
class InjectorKeyError(InjectorError):
|
1256
|
+
key: InjectorKey
|
1257
|
+
|
1258
|
+
source: ta.Any = None
|
1259
|
+
name: ta.Optional[str] = None
|
1260
|
+
|
1261
|
+
|
1262
|
+
class UnboundInjectorKeyError(InjectorKeyError):
|
1263
|
+
pass
|
1264
|
+
|
1265
|
+
|
1266
|
+
class DuplicateInjectorKeyError(InjectorKeyError):
|
1267
|
+
pass
|
1268
|
+
|
1269
|
+
|
1270
|
+
class CyclicDependencyInjectorKeyError(InjectorKeyError):
|
1271
|
+
pass
|
1272
|
+
|
1273
|
+
|
1274
|
+
###
|
1275
|
+
# keys
|
1276
|
+
|
1277
|
+
|
1278
|
+
def as_injector_key(o: ta.Any) -> InjectorKey:
|
1279
|
+
if o is inspect.Parameter.empty:
|
1280
|
+
raise TypeError(o)
|
1281
|
+
if isinstance(o, InjectorKey):
|
1282
|
+
return o
|
1283
|
+
if is_valid_injector_key_cls(o):
|
1284
|
+
return InjectorKey(o)
|
1285
|
+
raise TypeError(o)
|
1286
|
+
|
1287
|
+
|
1288
|
+
###
|
1289
|
+
# providers
|
1290
|
+
|
1291
|
+
|
1292
|
+
@dc.dataclass(frozen=True)
|
1293
|
+
class FnInjectorProvider(InjectorProvider):
|
1294
|
+
fn: ta.Any
|
1295
|
+
|
1296
|
+
def __post_init__(self) -> None:
|
1297
|
+
check_not_isinstance(self.fn, type)
|
1298
|
+
|
1299
|
+
def provider_fn(self) -> InjectorProviderFn:
|
1300
|
+
def pfn(i: Injector) -> ta.Any:
|
1301
|
+
return i.inject(self.fn)
|
1302
|
+
|
1303
|
+
return pfn
|
1304
|
+
|
1305
|
+
|
1306
|
+
@dc.dataclass(frozen=True)
|
1307
|
+
class CtorInjectorProvider(InjectorProvider):
|
1308
|
+
cls_: type
|
1309
|
+
|
1310
|
+
def __post_init__(self) -> None:
|
1311
|
+
check_isinstance(self.cls_, type)
|
1312
|
+
|
1313
|
+
def provider_fn(self) -> InjectorProviderFn:
|
1314
|
+
def pfn(i: Injector) -> ta.Any:
|
1315
|
+
return i.inject(self.cls_)
|
1316
|
+
|
1317
|
+
return pfn
|
1318
|
+
|
1319
|
+
|
1320
|
+
@dc.dataclass(frozen=True)
|
1321
|
+
class ConstInjectorProvider(InjectorProvider):
|
1322
|
+
v: ta.Any
|
1323
|
+
|
1324
|
+
def provider_fn(self) -> InjectorProviderFn:
|
1325
|
+
return lambda _: self.v
|
1326
|
+
|
1327
|
+
|
1328
|
+
@dc.dataclass(frozen=True)
|
1329
|
+
class SingletonInjectorProvider(InjectorProvider):
|
1330
|
+
p: InjectorProvider
|
1331
|
+
|
1332
|
+
def __post_init__(self) -> None:
|
1333
|
+
check_isinstance(self.p, InjectorProvider)
|
1334
|
+
|
1335
|
+
def provider_fn(self) -> InjectorProviderFn:
|
1336
|
+
v = not_set = object()
|
1337
|
+
|
1338
|
+
def pfn(i: Injector) -> ta.Any:
|
1339
|
+
nonlocal v
|
1340
|
+
if v is not_set:
|
1341
|
+
v = ufn(i)
|
1342
|
+
return v
|
1343
|
+
|
1344
|
+
ufn = self.p.provider_fn()
|
1345
|
+
return pfn
|
1346
|
+
|
1347
|
+
|
1348
|
+
@dc.dataclass(frozen=True)
|
1349
|
+
class LinkInjectorProvider(InjectorProvider):
|
1350
|
+
k: InjectorKey
|
1351
|
+
|
1352
|
+
def __post_init__(self) -> None:
|
1353
|
+
check_isinstance(self.k, InjectorKey)
|
1354
|
+
|
1355
|
+
def provider_fn(self) -> InjectorProviderFn:
|
1356
|
+
def pfn(i: Injector) -> ta.Any:
|
1357
|
+
return i.provide(self.k)
|
1358
|
+
|
1359
|
+
return pfn
|
1360
|
+
|
1361
|
+
|
1362
|
+
@dc.dataclass(frozen=True)
|
1363
|
+
class ArrayInjectorProvider(InjectorProvider):
|
1364
|
+
ps: ta.Sequence[InjectorProvider]
|
1365
|
+
|
1366
|
+
def provider_fn(self) -> InjectorProviderFn:
|
1367
|
+
ps = [p.provider_fn() for p in self.ps]
|
1368
|
+
|
1369
|
+
def pfn(i: Injector) -> ta.Any:
|
1370
|
+
rv = []
|
1371
|
+
for ep in ps:
|
1372
|
+
o = ep(i)
|
1373
|
+
rv.append(o)
|
1374
|
+
return rv
|
1375
|
+
|
1376
|
+
return pfn
|
1377
|
+
|
1378
|
+
|
1379
|
+
###
|
1380
|
+
# bindings
|
1381
|
+
|
1382
|
+
|
1383
|
+
@dc.dataclass(frozen=True)
|
1384
|
+
class _InjectorBindings(InjectorBindings):
|
1385
|
+
bs: ta.Optional[ta.Sequence[InjectorBinding]] = None
|
1386
|
+
ps: ta.Optional[ta.Sequence[InjectorBindings]] = None
|
1387
|
+
|
1388
|
+
def bindings(self) -> ta.Iterator[InjectorBinding]:
|
1389
|
+
if self.bs is not None:
|
1390
|
+
yield from self.bs
|
1391
|
+
if self.ps is not None:
|
1392
|
+
for p in self.ps:
|
1393
|
+
yield from p.bindings()
|
1394
|
+
|
1395
|
+
|
1396
|
+
def as_injector_bindings(*args: InjectorBindingOrBindings) -> InjectorBindings:
|
1397
|
+
bs: ta.List[InjectorBinding] = []
|
1398
|
+
ps: ta.List[InjectorBindings] = []
|
1399
|
+
|
1400
|
+
for a in args:
|
1401
|
+
if isinstance(a, InjectorBindings):
|
1402
|
+
ps.append(a)
|
1403
|
+
elif isinstance(a, InjectorBinding):
|
1404
|
+
bs.append(a)
|
1405
|
+
else:
|
1406
|
+
raise TypeError(a)
|
1407
|
+
|
1408
|
+
return _InjectorBindings(
|
1409
|
+
bs or None,
|
1410
|
+
ps or None,
|
1411
|
+
)
|
1412
|
+
|
1413
|
+
|
1414
|
+
##
|
1415
|
+
|
1416
|
+
|
1417
|
+
@dc.dataclass(frozen=True)
|
1418
|
+
class OverridesInjectorBindings(InjectorBindings):
|
1419
|
+
p: InjectorBindings
|
1420
|
+
m: ta.Mapping[InjectorKey, InjectorBinding]
|
1421
|
+
|
1422
|
+
def bindings(self) -> ta.Iterator[InjectorBinding]:
|
1423
|
+
for b in self.p.bindings():
|
1424
|
+
yield self.m.get(b.key, b)
|
1425
|
+
|
1426
|
+
|
1427
|
+
def injector_override(p: InjectorBindings, *args: InjectorBindingOrBindings) -> InjectorBindings:
|
1428
|
+
m: ta.Dict[InjectorKey, InjectorBinding] = {}
|
1429
|
+
|
1430
|
+
for b in as_injector_bindings(*args).bindings():
|
1431
|
+
if b.key in m:
|
1432
|
+
raise DuplicateInjectorKeyError(b.key)
|
1433
|
+
m[b.key] = b
|
1434
|
+
|
1435
|
+
return OverridesInjectorBindings(p, m)
|
1436
|
+
|
1437
|
+
|
1438
|
+
##
|
1439
|
+
|
1440
|
+
|
1441
|
+
def build_injector_provider_map(bs: InjectorBindings) -> ta.Mapping[InjectorKey, InjectorProvider]:
|
1442
|
+
pm: ta.Dict[InjectorKey, InjectorProvider] = {}
|
1443
|
+
am: ta.Dict[InjectorKey, ta.List[InjectorProvider]] = {}
|
1444
|
+
|
1445
|
+
for b in bs.bindings():
|
1446
|
+
if b.key.array:
|
1447
|
+
al = am.setdefault(b.key, [])
|
1448
|
+
if isinstance(b.provider, ArrayInjectorProvider):
|
1449
|
+
al.extend(b.provider.ps)
|
1450
|
+
else:
|
1451
|
+
al.append(b.provider)
|
1452
|
+
else:
|
1453
|
+
if b.key in pm:
|
1454
|
+
raise KeyError(b.key)
|
1455
|
+
pm[b.key] = b.provider
|
1456
|
+
|
1457
|
+
if am:
|
1458
|
+
for k, aps in am.items():
|
1459
|
+
pm[k] = ArrayInjectorProvider(aps)
|
1460
|
+
|
1461
|
+
return pm
|
1462
|
+
|
1463
|
+
|
1464
|
+
###
|
1465
|
+
# inspection
|
1466
|
+
|
1467
|
+
|
1468
|
+
class _InjectionInspection(ta.NamedTuple):
|
1469
|
+
signature: inspect.Signature
|
1470
|
+
type_hints: ta.Mapping[str, ta.Any]
|
1471
|
+
args_offset: int
|
1472
|
+
|
1473
|
+
|
1474
|
+
_INJECTION_INSPECTION_CACHE: ta.MutableMapping[ta.Any, _InjectionInspection] = weakref.WeakKeyDictionary()
|
1475
|
+
|
1476
|
+
|
1477
|
+
def _do_injection_inspect(obj: ta.Any) -> _InjectionInspection:
|
1478
|
+
tgt = obj
|
1479
|
+
if isinstance(tgt, type) and tgt.__init__ is not object.__init__: # type: ignore[misc]
|
1480
|
+
# Python 3.8's inspect.signature can't handle subclasses overriding __new__, always generating *args/**kwargs.
|
1481
|
+
# - https://bugs.python.org/issue40897
|
1482
|
+
# - https://github.com/python/cpython/commit/df7c62980d15acd3125dfbd81546dad359f7add7
|
1483
|
+
tgt = tgt.__init__ # type: ignore[misc]
|
1484
|
+
has_generic_base = True
|
1485
|
+
else:
|
1486
|
+
has_generic_base = False
|
1487
|
+
|
1488
|
+
# inspect.signature(eval_str=True) was added in 3.10 and we have to support 3.8, so we have to get_type_hints to
|
1489
|
+
# eval str annotations *in addition to* getting the signature for parameter information.
|
1490
|
+
uw = tgt
|
1491
|
+
has_partial = False
|
1492
|
+
while True:
|
1493
|
+
if isinstance(uw, functools.partial):
|
1494
|
+
has_partial = True
|
1495
|
+
uw = uw.func
|
1496
|
+
else:
|
1497
|
+
if (uw2 := inspect.unwrap(uw)) is uw:
|
1498
|
+
break
|
1499
|
+
uw = uw2
|
1500
|
+
|
1501
|
+
if has_generic_base and has_partial:
|
1502
|
+
raise InjectorError(
|
1503
|
+
'Injector inspection does not currently support both a typing.Generic base and a functools.partial: '
|
1504
|
+
f'{obj}',
|
1505
|
+
)
|
1506
|
+
|
1507
|
+
return _InjectionInspection(
|
1508
|
+
inspect.signature(tgt),
|
1509
|
+
ta.get_type_hints(uw),
|
1510
|
+
1 if has_generic_base else 0,
|
1511
|
+
)
|
1512
|
+
|
1513
|
+
|
1514
|
+
def _injection_inspect(obj: ta.Any) -> _InjectionInspection:
|
1515
|
+
try:
|
1516
|
+
return _INJECTION_INSPECTION_CACHE[obj]
|
1517
|
+
except TypeError:
|
1518
|
+
return _do_injection_inspect(obj)
|
1519
|
+
except KeyError:
|
1520
|
+
pass
|
1521
|
+
insp = _do_injection_inspect(obj)
|
1522
|
+
_INJECTION_INSPECTION_CACHE[obj] = insp
|
1523
|
+
return insp
|
1524
|
+
|
1525
|
+
|
1526
|
+
class InjectionKwarg(ta.NamedTuple):
|
1527
|
+
name: str
|
1528
|
+
key: InjectorKey
|
1529
|
+
has_default: bool
|
1530
|
+
|
1531
|
+
|
1532
|
+
class InjectionKwargsTarget(ta.NamedTuple):
|
1533
|
+
obj: ta.Any
|
1534
|
+
kwargs: ta.Sequence[InjectionKwarg]
|
1535
|
+
|
1536
|
+
|
1537
|
+
def build_injection_kwargs_target(
|
1538
|
+
obj: ta.Any,
|
1539
|
+
*,
|
1540
|
+
skip_args: int = 0,
|
1541
|
+
skip_kwargs: ta.Optional[ta.Iterable[str]] = None,
|
1542
|
+
raw_optional: bool = False,
|
1543
|
+
) -> InjectionKwargsTarget:
|
1544
|
+
insp = _injection_inspect(obj)
|
1545
|
+
|
1546
|
+
params = list(insp.signature.parameters.values())
|
1547
|
+
|
1548
|
+
skip_names: ta.Set[str] = set()
|
1549
|
+
if skip_kwargs is not None:
|
1550
|
+
skip_names.update(check_not_isinstance(skip_kwargs, str))
|
1551
|
+
|
1552
|
+
seen: ta.Set[InjectorKey] = set()
|
1553
|
+
kws: ta.List[InjectionKwarg] = []
|
1554
|
+
for p in params[insp.args_offset + skip_args:]:
|
1555
|
+
if p.name in skip_names:
|
1556
|
+
continue
|
1557
|
+
|
1558
|
+
if p.annotation is inspect.Signature.empty:
|
1559
|
+
if p.default is not inspect.Parameter.empty:
|
1560
|
+
raise KeyError(f'{obj}, {p.name}')
|
1561
|
+
continue
|
1562
|
+
|
1563
|
+
if p.kind not in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY):
|
1564
|
+
raise TypeError(insp)
|
1565
|
+
|
1566
|
+
# 3.8 inspect.signature doesn't eval_str but typing.get_type_hints does, so prefer that.
|
1567
|
+
ann = insp.type_hints.get(p.name, p.annotation)
|
1568
|
+
if (
|
1569
|
+
not raw_optional and
|
1570
|
+
is_optional_alias(ann)
|
1571
|
+
):
|
1572
|
+
ann = get_optional_alias_arg(ann)
|
1573
|
+
|
1574
|
+
k = as_injector_key(ann)
|
1575
|
+
|
1576
|
+
if k in seen:
|
1577
|
+
raise DuplicateInjectorKeyError(k)
|
1578
|
+
seen.add(k)
|
1579
|
+
|
1580
|
+
kws.append(InjectionKwarg(
|
1581
|
+
p.name,
|
1582
|
+
k,
|
1583
|
+
p.default is not inspect.Parameter.empty,
|
1584
|
+
))
|
1585
|
+
|
1586
|
+
return InjectionKwargsTarget(
|
1587
|
+
obj,
|
1588
|
+
kws,
|
1589
|
+
)
|
1590
|
+
|
1591
|
+
|
1592
|
+
###
|
1593
|
+
# injector
|
1594
|
+
|
1595
|
+
|
1596
|
+
_INJECTOR_INJECTOR_KEY: InjectorKey[Injector] = InjectorKey(Injector)
|
1597
|
+
|
1598
|
+
|
1599
|
+
@dc.dataclass(frozen=True)
|
1600
|
+
class _InjectorEager:
|
1601
|
+
key: InjectorKey
|
1602
|
+
|
1603
|
+
|
1604
|
+
_INJECTOR_EAGER_ARRAY_KEY: InjectorKey[_InjectorEager] = InjectorKey(_InjectorEager, array=True)
|
1605
|
+
|
1606
|
+
|
1607
|
+
class _Injector(Injector):
|
1608
|
+
def __init__(self, bs: InjectorBindings, p: ta.Optional[Injector] = None) -> None:
|
1609
|
+
super().__init__()
|
1610
|
+
|
1611
|
+
self._bs = check_isinstance(bs, InjectorBindings)
|
1612
|
+
self._p: ta.Optional[Injector] = check_isinstance(p, (Injector, type(None)))
|
1613
|
+
|
1614
|
+
self._pfm = {k: v.provider_fn() for k, v in build_injector_provider_map(bs).items()}
|
1615
|
+
|
1616
|
+
if _INJECTOR_INJECTOR_KEY in self._pfm:
|
1617
|
+
raise DuplicateInjectorKeyError(_INJECTOR_INJECTOR_KEY)
|
1618
|
+
|
1619
|
+
self.__cur_req: ta.Optional[_Injector._Request] = None
|
1620
|
+
|
1621
|
+
if _INJECTOR_EAGER_ARRAY_KEY in self._pfm:
|
1622
|
+
for e in self.provide(_INJECTOR_EAGER_ARRAY_KEY):
|
1623
|
+
self.provide(e.key)
|
1624
|
+
|
1625
|
+
class _Request:
|
1626
|
+
def __init__(self, injector: '_Injector') -> None:
|
1627
|
+
super().__init__()
|
1628
|
+
self._injector = injector
|
1629
|
+
self._provisions: ta.Dict[InjectorKey, Maybe] = {}
|
1630
|
+
self._seen_keys: ta.Set[InjectorKey] = set()
|
1631
|
+
|
1632
|
+
def handle_key(self, key: InjectorKey) -> Maybe[Maybe]:
|
1633
|
+
try:
|
1634
|
+
return Maybe.just(self._provisions[key])
|
1635
|
+
except KeyError:
|
1636
|
+
pass
|
1637
|
+
if key in self._seen_keys:
|
1638
|
+
raise CyclicDependencyInjectorKeyError(key)
|
1639
|
+
self._seen_keys.add(key)
|
1640
|
+
return Maybe.empty()
|
1641
|
+
|
1642
|
+
def handle_provision(self, key: InjectorKey, mv: Maybe) -> Maybe:
|
1643
|
+
check_in(key, self._seen_keys)
|
1644
|
+
check_not_in(key, self._provisions)
|
1645
|
+
self._provisions[key] = mv
|
1646
|
+
return mv
|
1647
|
+
|
1648
|
+
@contextlib.contextmanager
|
1649
|
+
def _current_request(self) -> ta.Generator[_Request, None, None]:
|
1650
|
+
if (cr := self.__cur_req) is not None:
|
1651
|
+
yield cr
|
1652
|
+
return
|
1653
|
+
|
1654
|
+
cr = self._Request(self)
|
1655
|
+
try:
|
1656
|
+
self.__cur_req = cr
|
1657
|
+
yield cr
|
1658
|
+
finally:
|
1659
|
+
self.__cur_req = None
|
1660
|
+
|
1661
|
+
def try_provide(self, key: ta.Any) -> Maybe[ta.Any]:
|
1662
|
+
key = as_injector_key(key)
|
1663
|
+
|
1664
|
+
cr: _Injector._Request
|
1665
|
+
with self._current_request() as cr:
|
1666
|
+
if (rv := cr.handle_key(key)).present:
|
1667
|
+
return rv.must()
|
1668
|
+
|
1669
|
+
if key == _INJECTOR_INJECTOR_KEY:
|
1670
|
+
return cr.handle_provision(key, Maybe.just(self))
|
1671
|
+
|
1672
|
+
fn = self._pfm.get(key)
|
1673
|
+
if fn is not None:
|
1674
|
+
return cr.handle_provision(key, Maybe.just(fn(self)))
|
1675
|
+
|
1676
|
+
if self._p is not None:
|
1677
|
+
pv = self._p.try_provide(key)
|
1678
|
+
if pv is not None:
|
1679
|
+
return cr.handle_provision(key, Maybe.empty())
|
1680
|
+
|
1681
|
+
return cr.handle_provision(key, Maybe.empty())
|
1682
|
+
|
1683
|
+
def provide(self, key: ta.Any) -> ta.Any:
|
1684
|
+
v = self.try_provide(key)
|
1685
|
+
if v.present:
|
1686
|
+
return v.must()
|
1687
|
+
raise UnboundInjectorKeyError(key)
|
1688
|
+
|
1689
|
+
def provide_kwargs(
|
1690
|
+
self,
|
1691
|
+
obj: ta.Any,
|
1692
|
+
*,
|
1693
|
+
skip_args: int = 0,
|
1694
|
+
skip_kwargs: ta.Optional[ta.Iterable[ta.Any]] = None,
|
1695
|
+
) -> ta.Mapping[str, ta.Any]:
|
1696
|
+
kt = build_injection_kwargs_target(
|
1697
|
+
obj,
|
1698
|
+
skip_args=skip_args,
|
1699
|
+
skip_kwargs=skip_kwargs,
|
1700
|
+
)
|
1701
|
+
|
1702
|
+
ret: ta.Dict[str, ta.Any] = {}
|
1703
|
+
for kw in kt.kwargs:
|
1704
|
+
if kw.has_default:
|
1705
|
+
if not (mv := self.try_provide(kw.key)).present:
|
1706
|
+
continue
|
1707
|
+
v = mv.must()
|
1708
|
+
else:
|
1709
|
+
v = self.provide(kw.key)
|
1710
|
+
ret[kw.name] = v
|
1711
|
+
return ret
|
1712
|
+
|
1713
|
+
def inject(
|
1714
|
+
self,
|
1715
|
+
obj: ta.Any,
|
1716
|
+
*,
|
1717
|
+
args: ta.Optional[ta.Sequence[ta.Any]] = None,
|
1718
|
+
kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
1719
|
+
) -> ta.Any:
|
1720
|
+
provided = self.provide_kwargs(
|
1721
|
+
obj,
|
1722
|
+
skip_args=len(args) if args is not None else 0,
|
1723
|
+
skip_kwargs=kwargs if kwargs is not None else None,
|
1724
|
+
)
|
1725
|
+
|
1726
|
+
return obj(
|
1727
|
+
*(args if args is not None else ()),
|
1728
|
+
**(kwargs if kwargs is not None else {}),
|
1729
|
+
**provided,
|
1730
|
+
)
|
1731
|
+
|
1732
|
+
|
1733
|
+
###
|
1734
|
+
# binder
|
1735
|
+
|
1736
|
+
|
1737
|
+
class InjectorBinder:
|
1738
|
+
def __new__(cls, *args, **kwargs): # noqa
|
1739
|
+
raise TypeError
|
1740
|
+
|
1741
|
+
_FN_TYPES: ta.Tuple[type, ...] = (
|
1742
|
+
types.FunctionType,
|
1743
|
+
types.MethodType,
|
1744
|
+
|
1745
|
+
classmethod,
|
1746
|
+
staticmethod,
|
1747
|
+
|
1748
|
+
functools.partial,
|
1749
|
+
functools.partialmethod,
|
1750
|
+
)
|
1751
|
+
|
1752
|
+
@classmethod
|
1753
|
+
def _is_fn(cls, obj: ta.Any) -> bool:
|
1754
|
+
return isinstance(obj, cls._FN_TYPES)
|
1755
|
+
|
1756
|
+
@classmethod
|
1757
|
+
def bind_as_fn(cls, icls: ta.Type[T]) -> ta.Type[T]:
|
1758
|
+
check_isinstance(icls, type)
|
1759
|
+
if icls not in cls._FN_TYPES:
|
1760
|
+
cls._FN_TYPES = (*cls._FN_TYPES, icls)
|
1761
|
+
return icls
|
1762
|
+
|
1763
|
+
_BANNED_BIND_TYPES: ta.Tuple[type, ...] = (
|
1764
|
+
InjectorProvider,
|
1765
|
+
)
|
1766
|
+
|
1767
|
+
@classmethod
|
1768
|
+
def bind(
|
1769
|
+
cls,
|
1770
|
+
obj: ta.Any,
|
1771
|
+
*,
|
1772
|
+
key: ta.Any = None,
|
1773
|
+
tag: ta.Any = None,
|
1774
|
+
array: ta.Optional[bool] = None, # noqa
|
1775
|
+
|
1776
|
+
to_fn: ta.Any = None,
|
1777
|
+
to_ctor: ta.Any = None,
|
1778
|
+
to_const: ta.Any = None,
|
1779
|
+
to_key: ta.Any = None,
|
1780
|
+
|
1781
|
+
singleton: bool = False,
|
1782
|
+
|
1783
|
+
eager: bool = False,
|
1784
|
+
) -> InjectorBindingOrBindings:
|
1785
|
+
if obj is None or obj is inspect.Parameter.empty:
|
1786
|
+
raise TypeError(obj)
|
1787
|
+
if isinstance(obj, cls._BANNED_BIND_TYPES):
|
1788
|
+
raise TypeError(obj)
|
1789
|
+
|
1790
|
+
##
|
1791
|
+
|
1792
|
+
if key is not None:
|
1793
|
+
key = as_injector_key(key)
|
1794
|
+
|
1795
|
+
##
|
1796
|
+
|
1797
|
+
has_to = (
|
1798
|
+
to_fn is not None or
|
1799
|
+
to_ctor is not None or
|
1800
|
+
to_const is not None or
|
1801
|
+
to_key is not None
|
1802
|
+
)
|
1803
|
+
if isinstance(obj, InjectorKey):
|
1804
|
+
if key is None:
|
1805
|
+
key = obj
|
1806
|
+
elif isinstance(obj, type):
|
1807
|
+
if not has_to:
|
1808
|
+
to_ctor = obj
|
1809
|
+
if key is None:
|
1810
|
+
key = InjectorKey(obj)
|
1811
|
+
elif cls._is_fn(obj) and not has_to:
|
1812
|
+
to_fn = obj
|
1813
|
+
if key is None:
|
1814
|
+
insp = _injection_inspect(obj)
|
1815
|
+
key_cls: ta.Any = check_valid_injector_key_cls(check_not_none(insp.type_hints.get('return')))
|
1816
|
+
key = InjectorKey(key_cls)
|
1817
|
+
else:
|
1818
|
+
if to_const is not None:
|
1819
|
+
raise TypeError('Cannot bind instance with to_const')
|
1820
|
+
to_const = obj
|
1821
|
+
if key is None:
|
1822
|
+
key = InjectorKey(type(obj))
|
1823
|
+
del has_to
|
1824
|
+
|
1825
|
+
##
|
1826
|
+
|
1827
|
+
if tag is not None:
|
1828
|
+
if key.tag is not None:
|
1829
|
+
raise TypeError('Tag already set')
|
1830
|
+
key = dc.replace(key, tag=tag)
|
1831
|
+
|
1832
|
+
if array is not None:
|
1833
|
+
key = dc.replace(key, array=array)
|
1834
|
+
|
1835
|
+
##
|
1836
|
+
|
1837
|
+
providers: ta.List[InjectorProvider] = []
|
1838
|
+
if to_fn is not None:
|
1839
|
+
providers.append(FnInjectorProvider(to_fn))
|
1840
|
+
if to_ctor is not None:
|
1841
|
+
providers.append(CtorInjectorProvider(to_ctor))
|
1842
|
+
if to_const is not None:
|
1843
|
+
providers.append(ConstInjectorProvider(to_const))
|
1844
|
+
if to_key is not None:
|
1845
|
+
providers.append(LinkInjectorProvider(as_injector_key(to_key)))
|
1846
|
+
if not providers:
|
1847
|
+
raise TypeError('Must specify provider')
|
1848
|
+
if len(providers) > 1:
|
1849
|
+
raise TypeError('May not specify multiple providers')
|
1850
|
+
provider, = providers
|
1851
|
+
|
1852
|
+
##
|
1853
|
+
|
1854
|
+
if singleton:
|
1855
|
+
provider = SingletonInjectorProvider(provider)
|
1856
|
+
|
1857
|
+
binding = InjectorBinding(key, provider)
|
1858
|
+
|
1859
|
+
##
|
1860
|
+
|
1861
|
+
extras: ta.List[InjectorBinding] = []
|
1862
|
+
|
1863
|
+
if eager:
|
1864
|
+
extras.append(bind_injector_eager_key(key))
|
1865
|
+
|
1866
|
+
##
|
1867
|
+
|
1868
|
+
if extras:
|
1869
|
+
return as_injector_bindings(binding, *extras)
|
1870
|
+
else:
|
1871
|
+
return binding
|
1872
|
+
|
1873
|
+
|
1874
|
+
###
|
1875
|
+
# injection helpers
|
1876
|
+
|
1877
|
+
|
1878
|
+
def make_injector_factory(
|
1879
|
+
fn: ta.Callable[..., T],
|
1880
|
+
cls: U,
|
1881
|
+
ann: ta.Any = None,
|
1882
|
+
) -> ta.Callable[..., U]:
|
1883
|
+
if ann is None:
|
1884
|
+
ann = cls
|
1885
|
+
|
1886
|
+
def outer(injector: Injector) -> ann:
|
1887
|
+
def inner(*args, **kwargs):
|
1888
|
+
return injector.inject(fn, args=args, kwargs=kwargs)
|
1889
|
+
return cls(inner) # type: ignore
|
1890
|
+
|
1891
|
+
return outer
|
1892
|
+
|
1893
|
+
|
1894
|
+
def bind_injector_array(
|
1895
|
+
obj: ta.Any = None,
|
1896
|
+
*,
|
1897
|
+
tag: ta.Any = None,
|
1898
|
+
) -> InjectorBindingOrBindings:
|
1899
|
+
key = as_injector_key(obj)
|
1900
|
+
if tag is not None:
|
1901
|
+
if key.tag is not None:
|
1902
|
+
raise ValueError('Must not specify multiple tags')
|
1903
|
+
key = dc.replace(key, tag=tag)
|
1904
|
+
|
1905
|
+
if key.array:
|
1906
|
+
raise ValueError('Key must not be array')
|
1907
|
+
|
1908
|
+
return InjectorBinding(
|
1909
|
+
dc.replace(key, array=True),
|
1910
|
+
ArrayInjectorProvider([]),
|
1911
|
+
)
|
1912
|
+
|
1913
|
+
|
1914
|
+
def make_injector_array_type(
|
1915
|
+
ele: ta.Union[InjectorKey, InjectorKeyCls],
|
1916
|
+
cls: U,
|
1917
|
+
ann: ta.Any = None,
|
1918
|
+
) -> ta.Callable[..., U]:
|
1919
|
+
if isinstance(ele, InjectorKey):
|
1920
|
+
if not ele.array:
|
1921
|
+
raise InjectorError('Provided key must be array', ele)
|
1922
|
+
key = ele
|
1923
|
+
else:
|
1924
|
+
key = dc.replace(as_injector_key(ele), array=True)
|
1925
|
+
|
1926
|
+
if ann is None:
|
1927
|
+
ann = cls
|
1928
|
+
|
1929
|
+
def inner(injector: Injector) -> ann:
|
1930
|
+
return cls(injector.provide(key)) # type: ignore[operator]
|
1931
|
+
|
1932
|
+
return inner
|
1933
|
+
|
1934
|
+
|
1935
|
+
def bind_injector_eager_key(key: ta.Any) -> InjectorBinding:
|
1936
|
+
return InjectorBinding(_INJECTOR_EAGER_ARRAY_KEY, ConstInjectorProvider(_InjectorEager(as_injector_key(key))))
|
1937
|
+
|
1938
|
+
|
1939
|
+
##
|
1940
|
+
|
1941
|
+
|
1942
|
+
class Injection:
|
1943
|
+
def __new__(cls, *args, **kwargs): # noqa
|
1944
|
+
raise TypeError
|
1945
|
+
|
1946
|
+
# keys
|
1947
|
+
|
1948
|
+
@classmethod
|
1949
|
+
def as_key(cls, o: ta.Any) -> InjectorKey:
|
1950
|
+
return as_injector_key(o)
|
1951
|
+
|
1952
|
+
@classmethod
|
1953
|
+
def array(cls, o: ta.Any) -> InjectorKey:
|
1954
|
+
return dc.replace(as_injector_key(o), array=True)
|
1955
|
+
|
1956
|
+
@classmethod
|
1957
|
+
def tag(cls, o: ta.Any, t: ta.Any) -> InjectorKey:
|
1958
|
+
return dc.replace(as_injector_key(o), tag=t)
|
1959
|
+
|
1960
|
+
# bindings
|
1961
|
+
|
1962
|
+
@classmethod
|
1963
|
+
def as_bindings(cls, *args: InjectorBindingOrBindings) -> InjectorBindings:
|
1964
|
+
return as_injector_bindings(*args)
|
1965
|
+
|
1966
|
+
@classmethod
|
1967
|
+
def override(cls, p: InjectorBindings, *args: InjectorBindingOrBindings) -> InjectorBindings:
|
1968
|
+
return injector_override(p, *args)
|
1969
|
+
|
1970
|
+
# injector
|
1971
|
+
|
1972
|
+
@classmethod
|
1973
|
+
def create_injector(cls, *args: InjectorBindingOrBindings, parent: ta.Optional[Injector] = None) -> Injector:
|
1974
|
+
return _Injector(as_injector_bindings(*args), parent)
|
1975
|
+
|
1976
|
+
# binder
|
1977
|
+
|
1978
|
+
@classmethod
|
1979
|
+
def bind(
|
1980
|
+
cls,
|
1981
|
+
obj: ta.Any,
|
1982
|
+
*,
|
1983
|
+
key: ta.Any = None,
|
1984
|
+
tag: ta.Any = None,
|
1985
|
+
array: ta.Optional[bool] = None, # noqa
|
1986
|
+
|
1987
|
+
to_fn: ta.Any = None,
|
1988
|
+
to_ctor: ta.Any = None,
|
1989
|
+
to_const: ta.Any = None,
|
1990
|
+
to_key: ta.Any = None,
|
1991
|
+
|
1992
|
+
singleton: bool = False,
|
1993
|
+
|
1994
|
+
eager: bool = False,
|
1995
|
+
) -> InjectorBindingOrBindings:
|
1996
|
+
return InjectorBinder.bind(
|
1997
|
+
obj,
|
1998
|
+
|
1999
|
+
key=key,
|
2000
|
+
tag=tag,
|
2001
|
+
array=array,
|
2002
|
+
|
2003
|
+
to_fn=to_fn,
|
2004
|
+
to_ctor=to_ctor,
|
2005
|
+
to_const=to_const,
|
2006
|
+
to_key=to_key,
|
2007
|
+
|
2008
|
+
singleton=singleton,
|
779
2009
|
|
2010
|
+
eager=eager,
|
2011
|
+
)
|
780
2012
|
|
781
|
-
|
782
|
-
if file is not None:
|
783
|
-
with open(file) as f:
|
784
|
-
return f.read()
|
2013
|
+
# helpers
|
785
2014
|
|
786
|
-
|
787
|
-
|
2015
|
+
@classmethod
|
2016
|
+
def bind_factory(
|
2017
|
+
cls,
|
2018
|
+
fn: ta.Callable[..., T],
|
2019
|
+
cls_: U,
|
2020
|
+
ann: ta.Any = None,
|
2021
|
+
) -> InjectorBindingOrBindings:
|
2022
|
+
return cls.bind(make_injector_factory(fn, cls_, ann))
|
788
2023
|
|
789
|
-
|
790
|
-
|
2024
|
+
@classmethod
|
2025
|
+
def bind_array(
|
2026
|
+
cls,
|
2027
|
+
obj: ta.Any = None,
|
2028
|
+
*,
|
2029
|
+
tag: ta.Any = None,
|
2030
|
+
) -> InjectorBindingOrBindings:
|
2031
|
+
return bind_injector_array(obj, tag=tag)
|
2032
|
+
|
2033
|
+
@classmethod
|
2034
|
+
def bind_array_type(
|
2035
|
+
cls,
|
2036
|
+
ele: ta.Union[InjectorKey, InjectorKeyCls],
|
2037
|
+
cls_: U,
|
2038
|
+
ann: ta.Any = None,
|
2039
|
+
) -> InjectorBindingOrBindings:
|
2040
|
+
return cls.bind(make_injector_array_type(ele, cls_, ann))
|
2041
|
+
|
2042
|
+
|
2043
|
+
inj = Injection
|
791
2044
|
|
792
2045
|
|
793
2046
|
########################################
|
@@ -1072,21 +2325,26 @@ TODO:
|
|
1072
2325
|
##
|
1073
2326
|
|
1074
2327
|
|
2328
|
+
@dc.dataclass(frozen=True)
|
2329
|
+
class ObjMarshalOptions:
|
2330
|
+
raw_bytes: bool = False
|
2331
|
+
|
2332
|
+
|
1075
2333
|
class ObjMarshaler(abc.ABC):
|
1076
2334
|
@abc.abstractmethod
|
1077
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
2335
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1078
2336
|
raise NotImplementedError
|
1079
2337
|
|
1080
2338
|
@abc.abstractmethod
|
1081
|
-
def unmarshal(self, o: ta.Any) -> ta.Any:
|
2339
|
+
def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1082
2340
|
raise NotImplementedError
|
1083
2341
|
|
1084
2342
|
|
1085
2343
|
class NopObjMarshaler(ObjMarshaler):
|
1086
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
2344
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1087
2345
|
return o
|
1088
2346
|
|
1089
|
-
def unmarshal(self, o: ta.Any) -> ta.Any:
|
2347
|
+
def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1090
2348
|
return o
|
1091
2349
|
|
1092
2350
|
|
@@ -1094,29 +2352,29 @@ class NopObjMarshaler(ObjMarshaler):
|
|
1094
2352
|
class ProxyObjMarshaler(ObjMarshaler):
|
1095
2353
|
m: ta.Optional[ObjMarshaler] = None
|
1096
2354
|
|
1097
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
1098
|
-
return check_not_none(self.m).marshal(o)
|
2355
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
2356
|
+
return check_not_none(self.m).marshal(o, opts)
|
1099
2357
|
|
1100
|
-
def unmarshal(self, o: ta.Any) -> ta.Any:
|
1101
|
-
return check_not_none(self.m).unmarshal(o)
|
2358
|
+
def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
2359
|
+
return check_not_none(self.m).unmarshal(o, opts)
|
1102
2360
|
|
1103
2361
|
|
1104
2362
|
@dc.dataclass(frozen=True)
|
1105
2363
|
class CastObjMarshaler(ObjMarshaler):
|
1106
2364
|
ty: type
|
1107
2365
|
|
1108
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
2366
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1109
2367
|
return o
|
1110
2368
|
|
1111
|
-
def unmarshal(self, o: ta.Any) -> ta.Any:
|
2369
|
+
def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1112
2370
|
return self.ty(o)
|
1113
2371
|
|
1114
2372
|
|
1115
2373
|
class DynamicObjMarshaler(ObjMarshaler):
|
1116
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
2374
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1117
2375
|
return marshal_obj(o)
|
1118
2376
|
|
1119
|
-
def unmarshal(self, o: ta.Any) -> ta.Any:
|
2377
|
+
def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1120
2378
|
return o
|
1121
2379
|
|
1122
2380
|
|
@@ -1124,21 +2382,36 @@ class DynamicObjMarshaler(ObjMarshaler):
|
|
1124
2382
|
class Base64ObjMarshaler(ObjMarshaler):
|
1125
2383
|
ty: type
|
1126
2384
|
|
1127
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
2385
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1128
2386
|
return base64.b64encode(o).decode('ascii')
|
1129
2387
|
|
1130
|
-
def unmarshal(self, o: ta.Any) -> ta.Any:
|
2388
|
+
def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1131
2389
|
return self.ty(base64.b64decode(o))
|
1132
2390
|
|
1133
2391
|
|
2392
|
+
@dc.dataclass(frozen=True)
|
2393
|
+
class BytesSwitchedObjMarshaler(ObjMarshaler):
|
2394
|
+
m: ObjMarshaler
|
2395
|
+
|
2396
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
2397
|
+
if opts.raw_bytes:
|
2398
|
+
return o
|
2399
|
+
return self.m.marshal(o, opts)
|
2400
|
+
|
2401
|
+
def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
2402
|
+
if opts.raw_bytes:
|
2403
|
+
return o
|
2404
|
+
return self.m.unmarshal(o, opts)
|
2405
|
+
|
2406
|
+
|
1134
2407
|
@dc.dataclass(frozen=True)
|
1135
2408
|
class EnumObjMarshaler(ObjMarshaler):
|
1136
2409
|
ty: type
|
1137
2410
|
|
1138
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
2411
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1139
2412
|
return o.name
|
1140
2413
|
|
1141
|
-
def unmarshal(self, o: ta.Any) -> ta.Any:
|
2414
|
+
def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1142
2415
|
return self.ty.__members__[o] # type: ignore
|
1143
2416
|
|
1144
2417
|
|
@@ -1146,15 +2419,15 @@ class EnumObjMarshaler(ObjMarshaler):
|
|
1146
2419
|
class OptionalObjMarshaler(ObjMarshaler):
|
1147
2420
|
item: ObjMarshaler
|
1148
2421
|
|
1149
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
2422
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1150
2423
|
if o is None:
|
1151
2424
|
return None
|
1152
|
-
return self.item.marshal(o)
|
2425
|
+
return self.item.marshal(o, opts)
|
1153
2426
|
|
1154
|
-
def unmarshal(self, o: ta.Any) -> ta.Any:
|
2427
|
+
def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1155
2428
|
if o is None:
|
1156
2429
|
return None
|
1157
|
-
return self.item.unmarshal(o)
|
2430
|
+
return self.item.unmarshal(o, opts)
|
1158
2431
|
|
1159
2432
|
|
1160
2433
|
@dc.dataclass(frozen=True)
|
@@ -1163,11 +2436,11 @@ class MappingObjMarshaler(ObjMarshaler):
|
|
1163
2436
|
km: ObjMarshaler
|
1164
2437
|
vm: ObjMarshaler
|
1165
2438
|
|
1166
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
1167
|
-
return {self.km.marshal(k): self.vm.marshal(v) for k, v in o.items()}
|
2439
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
2440
|
+
return {self.km.marshal(k, opts): self.vm.marshal(v, opts) for k, v in o.items()}
|
1168
2441
|
|
1169
|
-
def unmarshal(self, o: ta.Any) -> ta.Any:
|
1170
|
-
return self.ty((self.km.unmarshal(k), self.vm.unmarshal(v)) for k, v in o.items())
|
2442
|
+
def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
2443
|
+
return self.ty((self.km.unmarshal(k, opts), self.vm.unmarshal(v, opts)) for k, v in o.items())
|
1171
2444
|
|
1172
2445
|
|
1173
2446
|
@dc.dataclass(frozen=True)
|
@@ -1175,11 +2448,11 @@ class IterableObjMarshaler(ObjMarshaler):
|
|
1175
2448
|
ty: type
|
1176
2449
|
item: ObjMarshaler
|
1177
2450
|
|
1178
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
1179
|
-
return [self.item.marshal(e) for e in o]
|
2451
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
2452
|
+
return [self.item.marshal(e, opts) for e in o]
|
1180
2453
|
|
1181
|
-
def unmarshal(self, o: ta.Any) -> ta.Any:
|
1182
|
-
return self.ty(self.item.unmarshal(e) for e in o)
|
2454
|
+
def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
2455
|
+
return self.ty(self.item.unmarshal(e, opts) for e in o)
|
1183
2456
|
|
1184
2457
|
|
1185
2458
|
@dc.dataclass(frozen=True)
|
@@ -1188,11 +2461,11 @@ class DataclassObjMarshaler(ObjMarshaler):
|
|
1188
2461
|
fs: ta.Mapping[str, ObjMarshaler]
|
1189
2462
|
nonstrict: bool = False
|
1190
2463
|
|
1191
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
1192
|
-
return {k: m.marshal(getattr(o, k)) for k, m in self.fs.items()}
|
2464
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
2465
|
+
return {k: m.marshal(getattr(o, k), opts) for k, m in self.fs.items()}
|
1193
2466
|
|
1194
|
-
def unmarshal(self, o: ta.Any) -> ta.Any:
|
1195
|
-
return self.ty(**{k: self.fs[k].unmarshal(v) for k, v in o.items() if not self.nonstrict or k in self.fs})
|
2467
|
+
def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
2468
|
+
return self.ty(**{k: self.fs[k].unmarshal(v, opts) for k, v in o.items() if not self.nonstrict or k in self.fs})
|
1196
2469
|
|
1197
2470
|
|
1198
2471
|
@dc.dataclass(frozen=True)
|
@@ -1212,50 +2485,50 @@ class PolymorphicObjMarshaler(ObjMarshaler):
|
|
1212
2485
|
{i.tag: i for i in impls},
|
1213
2486
|
)
|
1214
2487
|
|
1215
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
2488
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1216
2489
|
impl = self.impls_by_ty[type(o)]
|
1217
|
-
return {impl.tag: impl.m.marshal(o)}
|
2490
|
+
return {impl.tag: impl.m.marshal(o, opts)}
|
1218
2491
|
|
1219
|
-
def unmarshal(self, o: ta.Any) -> ta.Any:
|
2492
|
+
def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1220
2493
|
[(t, v)] = o.items()
|
1221
2494
|
impl = self.impls_by_tag[t]
|
1222
|
-
return impl.m.unmarshal(v)
|
2495
|
+
return impl.m.unmarshal(v, opts)
|
1223
2496
|
|
1224
2497
|
|
1225
2498
|
@dc.dataclass(frozen=True)
|
1226
2499
|
class DatetimeObjMarshaler(ObjMarshaler):
|
1227
2500
|
ty: type
|
1228
2501
|
|
1229
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
2502
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1230
2503
|
return o.isoformat()
|
1231
2504
|
|
1232
|
-
def unmarshal(self, o: ta.Any) -> ta.Any:
|
2505
|
+
def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1233
2506
|
return self.ty.fromisoformat(o) # type: ignore
|
1234
2507
|
|
1235
2508
|
|
1236
2509
|
class DecimalObjMarshaler(ObjMarshaler):
|
1237
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
2510
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1238
2511
|
return str(check_isinstance(o, decimal.Decimal))
|
1239
2512
|
|
1240
|
-
def unmarshal(self, v: ta.Any) -> ta.Any:
|
2513
|
+
def unmarshal(self, v: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1241
2514
|
return decimal.Decimal(check_isinstance(v, str))
|
1242
2515
|
|
1243
2516
|
|
1244
2517
|
class FractionObjMarshaler(ObjMarshaler):
|
1245
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
2518
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1246
2519
|
fr = check_isinstance(o, fractions.Fraction)
|
1247
2520
|
return [fr.numerator, fr.denominator]
|
1248
2521
|
|
1249
|
-
def unmarshal(self, v: ta.Any) -> ta.Any:
|
2522
|
+
def unmarshal(self, v: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1250
2523
|
num, denom = check_isinstance(v, list)
|
1251
2524
|
return fractions.Fraction(num, denom)
|
1252
2525
|
|
1253
2526
|
|
1254
2527
|
class UuidObjMarshaler(ObjMarshaler):
|
1255
|
-
def marshal(self, o: ta.Any) -> ta.Any:
|
2528
|
+
def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1256
2529
|
return str(o)
|
1257
2530
|
|
1258
|
-
def unmarshal(self, o: ta.Any) -> ta.Any:
|
2531
|
+
def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
|
1259
2532
|
return uuid.UUID(o)
|
1260
2533
|
|
1261
2534
|
|
@@ -1265,7 +2538,7 @@ class UuidObjMarshaler(ObjMarshaler):
|
|
1265
2538
|
_DEFAULT_OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = {
|
1266
2539
|
**{t: NopObjMarshaler() for t in (type(None),)},
|
1267
2540
|
**{t: CastObjMarshaler(t) for t in (int, float, str, bool)},
|
1268
|
-
**{t: Base64ObjMarshaler(t) for t in (bytes, bytearray)},
|
2541
|
+
**{t: BytesSwitchedObjMarshaler(Base64ObjMarshaler(t)) for t in (bytes, bytearray)},
|
1269
2542
|
**{t: IterableObjMarshaler(t, DynamicObjMarshaler()) for t in (list, tuple, set, frozenset)},
|
1270
2543
|
**{t: MappingObjMarshaler(t, DynamicObjMarshaler(), DynamicObjMarshaler()) for t in (dict,)},
|
1271
2544
|
|
@@ -1298,12 +2571,16 @@ class ObjMarshalerManager:
|
|
1298
2571
|
def __init__(
|
1299
2572
|
self,
|
1300
2573
|
*,
|
2574
|
+
default_options: ObjMarshalOptions = ObjMarshalOptions(),
|
2575
|
+
|
1301
2576
|
default_obj_marshalers: ta.Dict[ta.Any, ObjMarshaler] = _DEFAULT_OBJ_MARSHALERS, # noqa
|
1302
2577
|
generic_mapping_types: ta.Dict[ta.Any, type] = _OBJ_MARSHALER_GENERIC_MAPPING_TYPES, # noqa
|
1303
2578
|
generic_iterable_types: ta.Dict[ta.Any, type] = _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES, # noqa
|
1304
2579
|
) -> None:
|
1305
2580
|
super().__init__()
|
1306
2581
|
|
2582
|
+
self._default_options = default_options
|
2583
|
+
|
1307
2584
|
self._obj_marshalers = dict(default_obj_marshalers)
|
1308
2585
|
self._generic_mapping_types = generic_mapping_types
|
1309
2586
|
self._generic_iterable_types = generic_iterable_types
|
@@ -1412,11 +2689,35 @@ class ObjMarshalerManager:
|
|
1412
2689
|
|
1413
2690
|
#
|
1414
2691
|
|
1415
|
-
def marshal_obj(
|
1416
|
-
|
1417
|
-
|
1418
|
-
|
1419
|
-
|
2692
|
+
def marshal_obj(
|
2693
|
+
self,
|
2694
|
+
o: ta.Any,
|
2695
|
+
ty: ta.Any = None,
|
2696
|
+
opts: ta.Optional[ObjMarshalOptions] = None,
|
2697
|
+
) -> ta.Any:
|
2698
|
+
m = self.get_obj_marshaler(ty if ty is not None else type(o))
|
2699
|
+
return m.marshal(o, opts or self._default_options)
|
2700
|
+
|
2701
|
+
def unmarshal_obj(
|
2702
|
+
self,
|
2703
|
+
o: ta.Any,
|
2704
|
+
ty: ta.Union[ta.Type[T], ta.Any],
|
2705
|
+
opts: ta.Optional[ObjMarshalOptions] = None,
|
2706
|
+
) -> T:
|
2707
|
+
m = self.get_obj_marshaler(ty)
|
2708
|
+
return m.unmarshal(o, opts or self._default_options)
|
2709
|
+
|
2710
|
+
def roundtrip_obj(
|
2711
|
+
self,
|
2712
|
+
o: ta.Any,
|
2713
|
+
ty: ta.Any = None,
|
2714
|
+
opts: ta.Optional[ObjMarshalOptions] = None,
|
2715
|
+
) -> ta.Any:
|
2716
|
+
if ty is None:
|
2717
|
+
ty = type(o)
|
2718
|
+
m: ta.Any = self.marshal_obj(o, ty, opts)
|
2719
|
+
u: ta.Any = self.unmarshal_obj(m, ty, opts)
|
2720
|
+
return u
|
1420
2721
|
|
1421
2722
|
|
1422
2723
|
##
|
@@ -1449,10 +2750,80 @@ def check_runtime_version() -> None:
|
|
1449
2750
|
|
1450
2751
|
|
1451
2752
|
########################################
|
1452
|
-
# ../
|
2753
|
+
# ../bootstrap.py
|
2754
|
+
|
2755
|
+
|
2756
|
+
@dc.dataclass(frozen=True)
|
2757
|
+
class MainBootstrap:
|
2758
|
+
main_config: MainConfig = MainConfig()
|
2759
|
+
|
2760
|
+
remote_config: RemoteConfig = RemoteConfig()
|
2761
|
+
|
2762
|
+
|
2763
|
+
########################################
|
2764
|
+
# ../commands/execution.py
|
2765
|
+
|
2766
|
+
|
2767
|
+
CommandExecutorMap = ta.NewType('CommandExecutorMap', ta.Mapping[ta.Type[Command], CommandExecutor])
|
2768
|
+
|
2769
|
+
|
2770
|
+
class CommandExecutionService(CommandExecutor):
|
2771
|
+
def __init__(
|
2772
|
+
self,
|
2773
|
+
*,
|
2774
|
+
command_executors: CommandExecutorMap,
|
2775
|
+
) -> None:
|
2776
|
+
super().__init__()
|
2777
|
+
|
2778
|
+
self._command_executors = command_executors
|
2779
|
+
|
2780
|
+
def execute(self, cmd: Command) -> Command.Output:
|
2781
|
+
ce: CommandExecutor = self._command_executors[type(cmd)]
|
2782
|
+
return ce.execute(cmd)
|
2783
|
+
|
2784
|
+
|
2785
|
+
########################################
|
2786
|
+
# ../commands/marshal.py
|
2787
|
+
|
2788
|
+
|
2789
|
+
def install_command_marshaling(
|
2790
|
+
cmds: CommandNameMap,
|
2791
|
+
msh: ObjMarshalerManager,
|
2792
|
+
) -> None:
|
2793
|
+
for fn in [
|
2794
|
+
lambda c: c,
|
2795
|
+
lambda c: c.Output,
|
2796
|
+
]:
|
2797
|
+
msh.register_opj_marshaler(
|
2798
|
+
fn(Command),
|
2799
|
+
PolymorphicObjMarshaler.of([
|
2800
|
+
PolymorphicObjMarshaler.Impl(
|
2801
|
+
fn(cmd),
|
2802
|
+
name,
|
2803
|
+
msh.get_obj_marshaler(fn(cmd)),
|
2804
|
+
)
|
2805
|
+
for name, cmd in cmds.items()
|
2806
|
+
]),
|
2807
|
+
)
|
2808
|
+
|
2809
|
+
|
2810
|
+
########################################
|
2811
|
+
# ../marshal.py
|
2812
|
+
|
2813
|
+
|
2814
|
+
@dc.dataclass(frozen=True)
|
2815
|
+
class ObjMarshalerInstaller:
|
2816
|
+
fn: ta.Callable[[ObjMarshalerManager], None]
|
2817
|
+
|
2818
|
+
|
2819
|
+
ObjMarshalerInstallers = ta.NewType('ObjMarshalerInstallers', ta.Sequence[ObjMarshalerInstaller])
|
2820
|
+
|
2821
|
+
|
2822
|
+
########################################
|
2823
|
+
# ../remote/channel.py
|
1453
2824
|
|
1454
2825
|
|
1455
|
-
class
|
2826
|
+
class RemoteChannel:
|
1456
2827
|
def __init__(
|
1457
2828
|
self,
|
1458
2829
|
input: ta.IO, # noqa
|
@@ -1466,6 +2837,9 @@ class Channel:
|
|
1466
2837
|
self._output = output
|
1467
2838
|
self._msh = msh
|
1468
2839
|
|
2840
|
+
def set_marshaler(self, msh: ObjMarshalerManager) -> None:
|
2841
|
+
self._msh = msh
|
2842
|
+
|
1469
2843
|
def send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
|
1470
2844
|
j = json_dumps_compact(self._msh.marshal_obj(o, ty))
|
1471
2845
|
d = j.encode('utf-8')
|
@@ -1474,7 +2848,7 @@ class Channel:
|
|
1474
2848
|
self._output.write(d)
|
1475
2849
|
self._output.flush()
|
1476
2850
|
|
1477
|
-
def recv_obj(self, ty: ta.
|
2851
|
+
def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
|
1478
2852
|
d = self._input.read(4)
|
1479
2853
|
if not d:
|
1480
2854
|
return None
|
@@ -1695,28 +3069,19 @@ class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCom
|
|
1695
3069
|
|
1696
3070
|
|
1697
3071
|
########################################
|
1698
|
-
# ../spawning.py
|
3072
|
+
# ../remote/spawning.py
|
1699
3073
|
|
1700
3074
|
|
1701
|
-
class
|
1702
|
-
|
3075
|
+
class RemoteSpawning:
|
3076
|
+
@dc.dataclass(frozen=True)
|
3077
|
+
class Target:
|
3078
|
+
shell: ta.Optional[str] = None
|
3079
|
+
shell_quote: bool = False
|
1703
3080
|
|
1704
|
-
|
1705
|
-
|
1706
|
-
src: str,
|
1707
|
-
*,
|
1708
|
-
shell: ta.Optional[str] = None,
|
1709
|
-
shell_quote: bool = False,
|
1710
|
-
python: str = DEFAULT_PYTHON,
|
1711
|
-
stderr: ta.Optional[SubprocessChannelOption] = None,
|
1712
|
-
) -> None:
|
1713
|
-
super().__init__()
|
3081
|
+
DEFAULT_PYTHON: ta.ClassVar[str] = 'python3'
|
3082
|
+
python: str = DEFAULT_PYTHON
|
1714
3083
|
|
1715
|
-
|
1716
|
-
self._shell = shell
|
1717
|
-
self._shell_quote = shell_quote
|
1718
|
-
self._python = python
|
1719
|
-
self._stderr = stderr
|
3084
|
+
stderr: ta.Optional[str] = None # SubprocessChannelOption
|
1720
3085
|
|
1721
3086
|
#
|
1722
3087
|
|
@@ -1724,20 +3089,24 @@ class PySpawner:
|
|
1724
3089
|
cmd: ta.Sequence[str]
|
1725
3090
|
shell: bool
|
1726
3091
|
|
1727
|
-
def _prepare_cmd(
|
1728
|
-
|
1729
|
-
|
1730
|
-
|
3092
|
+
def _prepare_cmd(
|
3093
|
+
self,
|
3094
|
+
tgt: Target,
|
3095
|
+
src: str,
|
3096
|
+
) -> _PreparedCmd:
|
3097
|
+
if tgt.shell is not None:
|
3098
|
+
sh_src = f'{tgt.python} -c {shlex.quote(src)}'
|
3099
|
+
if tgt.shell_quote:
|
1731
3100
|
sh_src = shlex.quote(sh_src)
|
1732
|
-
sh_cmd = f'{
|
1733
|
-
return
|
3101
|
+
sh_cmd = f'{tgt.shell} {sh_src}'
|
3102
|
+
return RemoteSpawning._PreparedCmd(
|
1734
3103
|
cmd=[sh_cmd],
|
1735
3104
|
shell=True,
|
1736
3105
|
)
|
1737
3106
|
|
1738
3107
|
else:
|
1739
|
-
return
|
1740
|
-
cmd=[
|
3108
|
+
return RemoteSpawning._PreparedCmd(
|
3109
|
+
cmd=[tgt.python, '-c', src],
|
1741
3110
|
shell=False,
|
1742
3111
|
)
|
1743
3112
|
|
@@ -1752,23 +3121,28 @@ class PySpawner:
|
|
1752
3121
|
@contextlib.contextmanager
|
1753
3122
|
def spawn(
|
1754
3123
|
self,
|
3124
|
+
tgt: Target,
|
3125
|
+
src: str,
|
1755
3126
|
*,
|
1756
3127
|
timeout: ta.Optional[float] = None,
|
1757
3128
|
) -> ta.Generator[Spawned, None, None]:
|
1758
|
-
pc = self._prepare_cmd()
|
3129
|
+
pc = self._prepare_cmd(tgt, src)
|
1759
3130
|
|
1760
3131
|
with subprocess.Popen(
|
1761
3132
|
subprocess_maybe_shell_wrap_exec(*pc.cmd),
|
1762
3133
|
shell=pc.shell,
|
1763
3134
|
stdin=subprocess.PIPE,
|
1764
3135
|
stdout=subprocess.PIPE,
|
1765
|
-
stderr=
|
3136
|
+
stderr=(
|
3137
|
+
SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, tgt.stderr)]
|
3138
|
+
if tgt.stderr is not None else None
|
3139
|
+
),
|
1766
3140
|
) as proc:
|
1767
3141
|
stdin = check_not_none(proc.stdin)
|
1768
3142
|
stdout = check_not_none(proc.stdout)
|
1769
3143
|
|
1770
3144
|
try:
|
1771
|
-
yield
|
3145
|
+
yield RemoteSpawning.Spawned(
|
1772
3146
|
stdin=stdin,
|
1773
3147
|
stdout=stdout,
|
1774
3148
|
stderr=proc.stderr,
|
@@ -1784,59 +3158,356 @@ class PySpawner:
|
|
1784
3158
|
|
1785
3159
|
|
1786
3160
|
########################################
|
1787
|
-
#
|
3161
|
+
# ../commands/inject.py
|
1788
3162
|
|
1789
3163
|
|
1790
3164
|
##
|
1791
3165
|
|
1792
3166
|
|
1793
|
-
|
1794
|
-
|
1795
|
-
|
3167
|
+
def bind_command(
|
3168
|
+
command_cls: ta.Type[Command],
|
3169
|
+
executor_cls: ta.Optional[ta.Type[CommandExecutor]],
|
3170
|
+
) -> InjectorBindings:
|
3171
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
3172
|
+
inj.bind(CommandRegistration(command_cls), array=True),
|
3173
|
+
]
|
3174
|
+
|
3175
|
+
if executor_cls is not None:
|
3176
|
+
lst.extend([
|
3177
|
+
inj.bind(executor_cls, singleton=True),
|
3178
|
+
inj.bind(CommandExecutorRegistration(command_cls, executor_cls), array=True),
|
3179
|
+
])
|
3180
|
+
|
3181
|
+
return inj.as_bindings(*lst)
|
1796
3182
|
|
1797
3183
|
|
1798
3184
|
##
|
1799
3185
|
|
1800
3186
|
|
1801
|
-
|
1802
|
-
|
1803
|
-
|
1804
|
-
|
3187
|
+
@dc.dataclass(frozen=True)
|
3188
|
+
class _FactoryCommandExecutor(CommandExecutor):
|
3189
|
+
factory: ta.Callable[[], CommandExecutor]
|
3190
|
+
|
3191
|
+
def execute(self, i: Command) -> Command.Output:
|
3192
|
+
return self.factory().execute(i)
|
3193
|
+
|
3194
|
+
|
3195
|
+
##
|
3196
|
+
|
3197
|
+
|
3198
|
+
def bind_commands(
|
3199
|
+
*,
|
3200
|
+
main_config: MainConfig,
|
3201
|
+
) -> InjectorBindings:
|
3202
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
3203
|
+
inj.bind_array(CommandRegistration),
|
3204
|
+
inj.bind_array_type(CommandRegistration, CommandRegistrations),
|
3205
|
+
|
3206
|
+
inj.bind_array(CommandExecutorRegistration),
|
3207
|
+
inj.bind_array_type(CommandExecutorRegistration, CommandExecutorRegistrations),
|
3208
|
+
|
3209
|
+
inj.bind(build_command_name_map, singleton=True),
|
3210
|
+
]
|
3211
|
+
|
3212
|
+
#
|
3213
|
+
|
3214
|
+
def provide_obj_marshaler_installer(cmds: CommandNameMap) -> ObjMarshalerInstaller:
|
3215
|
+
return ObjMarshalerInstaller(functools.partial(install_command_marshaling, cmds))
|
3216
|
+
|
3217
|
+
lst.append(inj.bind(provide_obj_marshaler_installer, array=True))
|
3218
|
+
|
3219
|
+
#
|
3220
|
+
|
3221
|
+
def provide_command_executor_map(
|
3222
|
+
injector: Injector,
|
3223
|
+
crs: CommandExecutorRegistrations,
|
3224
|
+
) -> CommandExecutorMap:
|
3225
|
+
dct: ta.Dict[ta.Type[Command], CommandExecutor] = {}
|
3226
|
+
|
3227
|
+
cr: CommandExecutorRegistration
|
3228
|
+
for cr in crs:
|
3229
|
+
if cr.command_cls in dct:
|
3230
|
+
raise KeyError(cr.command_cls)
|
3231
|
+
|
3232
|
+
factory = functools.partial(injector.provide, cr.executor_cls)
|
3233
|
+
if main_config.debug:
|
3234
|
+
ce = factory()
|
3235
|
+
else:
|
3236
|
+
ce = _FactoryCommandExecutor(factory)
|
3237
|
+
|
3238
|
+
dct[cr.command_cls] = ce
|
3239
|
+
|
3240
|
+
return CommandExecutorMap(dct)
|
3241
|
+
|
3242
|
+
lst.extend([
|
3243
|
+
inj.bind(provide_command_executor_map, singleton=True),
|
3244
|
+
|
3245
|
+
inj.bind(CommandExecutionService, singleton=True, eager=main_config.debug),
|
3246
|
+
inj.bind(CommandExecutor, to_key=CommandExecutionService),
|
3247
|
+
])
|
3248
|
+
|
3249
|
+
#
|
3250
|
+
|
3251
|
+
for command_cls, executor_cls in [
|
3252
|
+
(SubprocessCommand, SubprocessCommandExecutor),
|
1805
3253
|
]:
|
1806
|
-
|
1807
|
-
|
1808
|
-
|
1809
|
-
|
1810
|
-
|
1811
|
-
k,
|
1812
|
-
msh.get_obj_marshaler(fn(cty)),
|
1813
|
-
)
|
1814
|
-
for k, cty in _COMMAND_TYPES.items()
|
1815
|
-
]),
|
1816
|
-
)
|
3254
|
+
lst.append(bind_command(command_cls, executor_cls))
|
3255
|
+
|
3256
|
+
#
|
3257
|
+
|
3258
|
+
return inj.as_bindings(*lst)
|
1817
3259
|
|
1818
3260
|
|
1819
|
-
|
3261
|
+
########################################
|
3262
|
+
# ../remote/execution.py
|
1820
3263
|
|
1821
3264
|
|
1822
3265
|
##
|
1823
3266
|
|
1824
3267
|
|
1825
|
-
def
|
3268
|
+
def _remote_execution_main() -> None:
|
1826
3269
|
rt = pyremote_bootstrap_finalize() # noqa
|
1827
|
-
|
3270
|
+
|
3271
|
+
chan = RemoteChannel(
|
3272
|
+
rt.input,
|
3273
|
+
rt.output,
|
3274
|
+
)
|
3275
|
+
|
3276
|
+
bs = check_not_none(chan.recv_obj(MainBootstrap))
|
3277
|
+
|
3278
|
+
if (prd := bs.remote_config.pycharm_remote_debug) is not None:
|
3279
|
+
pycharm_debug_connect(prd)
|
3280
|
+
|
3281
|
+
injector = main_bootstrap(bs)
|
3282
|
+
|
3283
|
+
chan.set_marshaler(injector[ObjMarshalerManager])
|
3284
|
+
|
3285
|
+
ce = injector[CommandExecutor]
|
1828
3286
|
|
1829
3287
|
while True:
|
1830
3288
|
i = chan.recv_obj(Command)
|
1831
3289
|
if i is None:
|
1832
3290
|
break
|
1833
3291
|
|
1834
|
-
|
1835
|
-
|
3292
|
+
r = ce.try_execute(
|
3293
|
+
i,
|
3294
|
+
log=log,
|
3295
|
+
omit_exc_object=True,
|
3296
|
+
)
|
3297
|
+
|
3298
|
+
chan.send_obj(r)
|
3299
|
+
|
3300
|
+
|
3301
|
+
##
|
3302
|
+
|
3303
|
+
|
3304
|
+
@dc.dataclass()
|
3305
|
+
class RemoteCommandError(Exception):
|
3306
|
+
e: CommandException
|
3307
|
+
|
3308
|
+
|
3309
|
+
class RemoteCommandExecutor(CommandExecutor):
|
3310
|
+
def __init__(self, chan: RemoteChannel) -> None:
|
3311
|
+
super().__init__()
|
3312
|
+
|
3313
|
+
self._chan = chan
|
3314
|
+
|
3315
|
+
def _remote_execute(self, cmd: Command) -> CommandOutputOrException:
|
3316
|
+
self._chan.send_obj(cmd, Command)
|
3317
|
+
|
3318
|
+
if (r := self._chan.recv_obj(CommandOutputOrExceptionData)) is None:
|
3319
|
+
raise EOFError
|
3320
|
+
|
3321
|
+
return r
|
3322
|
+
|
3323
|
+
# @ta.override
|
3324
|
+
def execute(self, cmd: Command) -> Command.Output:
|
3325
|
+
r = self._remote_execute(cmd)
|
3326
|
+
if (e := r.exception) is not None:
|
3327
|
+
raise RemoteCommandError(e)
|
3328
|
+
else:
|
3329
|
+
return check_not_none(r.output)
|
3330
|
+
|
3331
|
+
# @ta.override
|
3332
|
+
def try_execute(
|
3333
|
+
self,
|
3334
|
+
cmd: Command,
|
3335
|
+
*,
|
3336
|
+
log: ta.Optional[logging.Logger] = None,
|
3337
|
+
omit_exc_object: bool = False,
|
3338
|
+
) -> CommandOutputOrException:
|
3339
|
+
try:
|
3340
|
+
r = self._remote_execute(cmd)
|
3341
|
+
|
3342
|
+
except Exception as e: # noqa
|
3343
|
+
if log is not None:
|
3344
|
+
log.exception('Exception executing remote command: %r', type(cmd))
|
3345
|
+
|
3346
|
+
return CommandOutputOrExceptionData(exception=CommandException.of(
|
3347
|
+
e,
|
3348
|
+
omit_exc_object=omit_exc_object,
|
3349
|
+
cmd=cmd,
|
3350
|
+
))
|
3351
|
+
|
1836
3352
|
else:
|
1837
|
-
|
3353
|
+
return r
|
3354
|
+
|
3355
|
+
|
3356
|
+
##
|
3357
|
+
|
3358
|
+
|
3359
|
+
class RemoteExecution:
|
3360
|
+
def __init__(
|
3361
|
+
self,
|
3362
|
+
*,
|
3363
|
+
spawning: RemoteSpawning,
|
3364
|
+
msh: ObjMarshalerManager,
|
3365
|
+
payload_file: ta.Optional[RemoteExecutionPayloadFile] = None,
|
3366
|
+
) -> None:
|
3367
|
+
super().__init__()
|
3368
|
+
|
3369
|
+
self._spawning = spawning
|
3370
|
+
self._msh = msh
|
3371
|
+
self._payload_file = payload_file
|
3372
|
+
|
3373
|
+
#
|
3374
|
+
|
3375
|
+
@cached_nullary
|
3376
|
+
def _payload_src(self) -> str:
|
3377
|
+
return get_remote_payload_src(file=self._payload_file)
|
3378
|
+
|
3379
|
+
@cached_nullary
|
3380
|
+
def _remote_src(self) -> ta.Sequence[str]:
|
3381
|
+
return [
|
3382
|
+
self._payload_src(),
|
3383
|
+
'_remote_execution_main()',
|
3384
|
+
]
|
3385
|
+
|
3386
|
+
@cached_nullary
|
3387
|
+
def _spawn_src(self) -> str:
|
3388
|
+
return pyremote_build_bootstrap_cmd(__package__ or 'manage')
|
3389
|
+
|
3390
|
+
#
|
3391
|
+
|
3392
|
+
@contextlib.contextmanager
|
3393
|
+
def connect(
|
3394
|
+
self,
|
3395
|
+
tgt: RemoteSpawning.Target,
|
3396
|
+
bs: MainBootstrap,
|
3397
|
+
) -> ta.Generator[RemoteCommandExecutor, None, None]:
|
3398
|
+
spawn_src = self._spawn_src()
|
3399
|
+
remote_src = self._remote_src()
|
3400
|
+
|
3401
|
+
with self._spawning.spawn(
|
3402
|
+
tgt,
|
3403
|
+
spawn_src,
|
3404
|
+
) as proc:
|
3405
|
+
res = PyremoteBootstrapDriver( # noqa
|
3406
|
+
remote_src,
|
3407
|
+
PyremoteBootstrapOptions(
|
3408
|
+
debug=bs.main_config.debug,
|
3409
|
+
),
|
3410
|
+
).run(
|
3411
|
+
proc.stdout,
|
3412
|
+
proc.stdin,
|
3413
|
+
)
|
3414
|
+
|
3415
|
+
chan = RemoteChannel(
|
3416
|
+
proc.stdout,
|
3417
|
+
proc.stdin,
|
3418
|
+
msh=self._msh,
|
3419
|
+
)
|
3420
|
+
|
3421
|
+
chan.send_obj(bs)
|
3422
|
+
|
3423
|
+
yield RemoteCommandExecutor(chan)
|
3424
|
+
|
3425
|
+
|
3426
|
+
########################################
|
3427
|
+
# ../remote/inject.py
|
3428
|
+
|
3429
|
+
|
3430
|
+
def bind_remote(
|
3431
|
+
*,
|
3432
|
+
remote_config: RemoteConfig,
|
3433
|
+
) -> InjectorBindings:
|
3434
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
3435
|
+
inj.bind(remote_config),
|
3436
|
+
|
3437
|
+
inj.bind(RemoteSpawning, singleton=True),
|
3438
|
+
|
3439
|
+
inj.bind(RemoteExecution, singleton=True),
|
3440
|
+
]
|
3441
|
+
|
3442
|
+
if (pf := remote_config.payload_file) is not None:
|
3443
|
+
lst.append(inj.bind(pf, to_key=RemoteExecutionPayloadFile))
|
3444
|
+
|
3445
|
+
return inj.as_bindings(*lst)
|
3446
|
+
|
3447
|
+
|
3448
|
+
########################################
|
3449
|
+
# ../inject.py
|
3450
|
+
|
3451
|
+
|
3452
|
+
##
|
3453
|
+
|
3454
|
+
|
3455
|
+
def bind_main(
|
3456
|
+
*,
|
3457
|
+
main_config: MainConfig,
|
3458
|
+
remote_config: RemoteConfig,
|
3459
|
+
) -> InjectorBindings:
|
3460
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
3461
|
+
inj.bind(main_config),
|
3462
|
+
|
3463
|
+
bind_commands(
|
3464
|
+
main_config=main_config,
|
3465
|
+
),
|
3466
|
+
|
3467
|
+
bind_remote(
|
3468
|
+
remote_config=remote_config,
|
3469
|
+
),
|
3470
|
+
]
|
3471
|
+
|
3472
|
+
#
|
3473
|
+
|
3474
|
+
def build_obj_marshaler_manager(insts: ObjMarshalerInstallers) -> ObjMarshalerManager:
|
3475
|
+
msh = ObjMarshalerManager()
|
3476
|
+
inst: ObjMarshalerInstaller
|
3477
|
+
for inst in insts:
|
3478
|
+
inst.fn(msh)
|
3479
|
+
return msh
|
3480
|
+
|
3481
|
+
lst.extend([
|
3482
|
+
inj.bind(build_obj_marshaler_manager, singleton=True),
|
3483
|
+
|
3484
|
+
inj.bind_array(ObjMarshalerInstaller),
|
3485
|
+
inj.bind_array_type(ObjMarshalerInstaller, ObjMarshalerInstallers),
|
3486
|
+
])
|
3487
|
+
|
3488
|
+
#
|
3489
|
+
|
3490
|
+
return inj.as_bindings(*lst)
|
3491
|
+
|
3492
|
+
|
3493
|
+
########################################
|
3494
|
+
# ../bootstrap_.py
|
1838
3495
|
|
1839
|
-
|
3496
|
+
|
3497
|
+
def main_bootstrap(bs: MainBootstrap) -> Injector:
|
3498
|
+
if (log_level := bs.main_config.log_level) is not None:
|
3499
|
+
configure_standard_logging(log_level)
|
3500
|
+
|
3501
|
+
injector = inj.create_injector(bind_main( # noqa
|
3502
|
+
main_config=bs.main_config,
|
3503
|
+
remote_config=bs.remote_config,
|
3504
|
+
))
|
3505
|
+
|
3506
|
+
return injector
|
3507
|
+
|
3508
|
+
|
3509
|
+
########################################
|
3510
|
+
# main.py
|
1840
3511
|
|
1841
3512
|
|
1842
3513
|
##
|
@@ -1853,50 +3524,64 @@ def _main() -> None:
|
|
1853
3524
|
parser.add_argument('-q', '--shell-quote', action='store_true')
|
1854
3525
|
parser.add_argument('--python', default='python3')
|
1855
3526
|
|
3527
|
+
parser.add_argument('--pycharm-debug-port', type=int)
|
3528
|
+
parser.add_argument('--pycharm-debug-host')
|
3529
|
+
parser.add_argument('--pycharm-debug-version')
|
3530
|
+
|
1856
3531
|
parser.add_argument('--debug', action='store_true')
|
1857
3532
|
|
3533
|
+
parser.add_argument('command', nargs='+')
|
3534
|
+
|
1858
3535
|
args = parser.parse_args()
|
1859
3536
|
|
1860
3537
|
#
|
1861
3538
|
|
1862
|
-
|
3539
|
+
bs = MainBootstrap(
|
3540
|
+
main_config=MainConfig(
|
3541
|
+
log_level='DEBUG' if args.debug else 'INFO',
|
1863
3542
|
|
1864
|
-
|
1865
|
-
|
1866
|
-
|
1867
|
-
|
1868
|
-
|
3543
|
+
debug=bool(args.debug),
|
3544
|
+
),
|
3545
|
+
|
3546
|
+
remote_config=RemoteConfig(
|
3547
|
+
payload_file=args._payload_file, # noqa
|
3548
|
+
|
3549
|
+
pycharm_remote_debug=PycharmRemoteDebug(
|
3550
|
+
port=args.pycharm_debug_port,
|
3551
|
+
host=args.pycharm_debug_host,
|
3552
|
+
install_version=args.pycharm_debug_version,
|
3553
|
+
) if args.pycharm_debug_port is not None else None,
|
3554
|
+
),
|
3555
|
+
)
|
3556
|
+
|
3557
|
+
injector = main_bootstrap(
|
3558
|
+
bs,
|
3559
|
+
)
|
3560
|
+
|
3561
|
+
#
|
3562
|
+
|
3563
|
+
cmds = [
|
3564
|
+
SubprocessCommand([c])
|
3565
|
+
for c in args.command
|
3566
|
+
]
|
1869
3567
|
|
1870
3568
|
#
|
1871
3569
|
|
1872
|
-
|
1873
|
-
pyremote_build_bootstrap_cmd(__package__ or 'manage'),
|
3570
|
+
tgt = RemoteSpawning.Target(
|
1874
3571
|
shell=args.shell,
|
1875
3572
|
shell_quote=args.shell_quote,
|
1876
3573
|
python=args.python,
|
1877
3574
|
)
|
1878
3575
|
|
1879
|
-
with
|
1880
|
-
|
1881
|
-
|
1882
|
-
PyremoteBootstrapOptions(
|
1883
|
-
debug=args.debug,
|
1884
|
-
),
|
1885
|
-
).run(proc.stdout, proc.stdin)
|
1886
|
-
|
1887
|
-
chan = Channel(proc.stdout, proc.stdin)
|
1888
|
-
|
1889
|
-
#
|
3576
|
+
with injector[RemoteExecution].connect(tgt, bs) as rce:
|
3577
|
+
for cmd in cmds:
|
3578
|
+
r = rce.try_execute(cmd)
|
1890
3579
|
|
1891
|
-
|
1892
|
-
SubprocessCommand(['python3', '-'], input=b'print(1)\n'),
|
1893
|
-
SubprocessCommand(['uname']),
|
1894
|
-
]:
|
1895
|
-
chan.send_obj(ci, Command)
|
3580
|
+
print(injector[ObjMarshalerManager].marshal_obj(r, opts=ObjMarshalOptions(raw_bytes=True)))
|
1896
3581
|
|
1897
|
-
|
3582
|
+
#
|
1898
3583
|
|
1899
|
-
|
3584
|
+
print('Success')
|
1900
3585
|
|
1901
3586
|
|
1902
3587
|
if __name__ == '__main__':
|