ominfra 0.0.0.dev141__py3-none-any.whl → 0.0.0.dev143__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. ominfra/manage/__init__.py +10 -0
  2. ominfra/manage/__main__.py +4 -0
  3. ominfra/manage/bootstrap.py +11 -0
  4. ominfra/manage/bootstrap_.py +18 -0
  5. ominfra/manage/commands/base.py +133 -1
  6. ominfra/manage/commands/execution.py +23 -0
  7. ominfra/manage/commands/inject.py +122 -0
  8. ominfra/manage/commands/marshal.py +26 -0
  9. ominfra/manage/commands/subprocess.py +18 -17
  10. ominfra/manage/config.py +10 -0
  11. ominfra/manage/inject.py +55 -0
  12. ominfra/manage/main.py +54 -132
  13. ominfra/manage/marshal.py +12 -0
  14. ominfra/manage/remote/__init__.py +0 -0
  15. ominfra/manage/remote/channel.py +52 -0
  16. ominfra/manage/remote/config.py +12 -0
  17. ominfra/manage/remote/execution.py +193 -0
  18. ominfra/manage/remote/inject.py +29 -0
  19. ominfra/manage/{payload.py → remote/payload.py} +7 -1
  20. ominfra/manage/{spawning.py → remote/spawning.py} +31 -35
  21. ominfra/pyremote.py +53 -14
  22. ominfra/scripts/journald2aws.py +216 -131
  23. ominfra/scripts/manage.py +2180 -452
  24. ominfra/scripts/supervisor.py +203 -130
  25. ominfra/supervisor/inject.py +2 -1
  26. {ominfra-0.0.0.dev141.dist-info → ominfra-0.0.0.dev143.dist-info}/METADATA +3 -3
  27. {ominfra-0.0.0.dev141.dist-info → ominfra-0.0.0.dev143.dist-info}/RECORD +31 -17
  28. {ominfra-0.0.0.dev141.dist-info → ominfra-0.0.0.dev143.dist-info}/LICENSE +0 -0
  29. {ominfra-0.0.0.dev141.dist-info → ominfra-0.0.0.dev143.dist-info}/WHEEL +0 -0
  30. {ominfra-0.0.0.dev141.dist-info → ominfra-0.0.0.dev143.dist-info}/entry_points.txt +0 -0
  31. {ominfra-0.0.0.dev141.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,34 +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
62
 
63
- ########################################
64
- # ../commands/base.py
65
-
66
-
67
- ##
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']
68
69
 
70
+ # ../../omlish/lite/subprocesses.py
71
+ SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull']
69
72
 
70
- @dc.dataclass(frozen=True)
71
- class Command(abc.ABC, ta.Generic[CommandOutputT]):
72
- @dc.dataclass(frozen=True)
73
- class Output(abc.ABC): # noqa
74
- pass
75
73
 
74
+ ########################################
75
+ # ../config.py
76
76
 
77
- ##
78
77
 
78
+ @dc.dataclass(frozen=True)
79
+ class MainConfig:
80
+ log_level: ta.Optional[str] = 'INFO'
79
81
 
80
- class CommandExecutor(abc.ABC, ta.Generic[CommandT, CommandOutputT]):
81
- @abc.abstractmethod
82
- def execute(self, i: CommandT) -> CommandOutputT:
83
- raise NotImplementedError
82
+ debug: bool = False
84
83
 
85
84
 
86
85
  ########################################
87
86
  # ../../pyremote.py
88
87
  """
89
88
  Basically this: https://mitogen.networkgenomics.com/howitworks.html
89
+
90
+ TODO:
91
+ - log: ta.Optional[logging.Logger] = None + log.debug's
90
92
  """
91
93
 
92
94
 
@@ -97,6 +99,9 @@ Basically this: https://mitogen.networkgenomics.com/howitworks.html
97
99
  class PyremoteBootstrapOptions:
98
100
  debug: bool = False
99
101
 
102
+ DEFAULT_MAIN_NAME_OVERRIDE: ta.ClassVar[str] = '__pyremote__'
103
+ main_name_override: ta.Optional[str] = DEFAULT_MAIN_NAME_OVERRIDE
104
+
100
105
 
101
106
  ##
102
107
 
@@ -251,12 +256,14 @@ def _pyremote_bootstrap_main(context_name: str) -> None:
251
256
  os.close(f)
252
257
 
253
258
  # Save vars
254
- os.environ[_PYREMOTE_BOOTSTRAP_CHILD_PID_VAR] = str(cp)
255
- os.environ[_PYREMOTE_BOOTSTRAP_ARGV0_VAR] = sys.executable
256
- os.environ[_PYREMOTE_BOOTSTRAP_CONTEXT_NAME_VAR] = context_name
259
+ env = os.environ
260
+ exe = sys.executable
261
+ env[_PYREMOTE_BOOTSTRAP_CHILD_PID_VAR] = str(cp)
262
+ env[_PYREMOTE_BOOTSTRAP_ARGV0_VAR] = exe
263
+ env[_PYREMOTE_BOOTSTRAP_CONTEXT_NAME_VAR] = context_name
257
264
 
258
265
  # Start repl reading stdin from r0
259
- os.execl(sys.executable, sys.executable + (_PYREMOTE_BOOTSTRAP_PROC_TITLE_FMT % (context_name,)))
266
+ os.execl(exe, exe + (_PYREMOTE_BOOTSTRAP_PROC_TITLE_FMT % (context_name,)))
260
267
 
261
268
  else:
262
269
  # Child process
@@ -320,12 +327,12 @@ def pyremote_build_bootstrap_cmd(context_name: str) -> str:
320
327
  if cl.strip()
321
328
  )
322
329
 
323
- bs_z = zlib.compress(bs_src.encode('utf-8'))
324
- bs_z64 = base64.encodebytes(bs_z).replace(b'\n', b'')
330
+ bs_z = zlib.compress(bs_src.encode('utf-8'), 9)
331
+ bs_z85 = base64.b85encode(bs_z).replace(b'\n', b'')
325
332
 
326
333
  stmts = [
327
334
  f'import {", ".join(_PYREMOTE_BOOTSTRAP_IMPORTS)}',
328
- f'exec(zlib.decompress(base64.decodebytes({bs_z64!r})))',
335
+ f'exec(zlib.decompress(base64.b85decode({bs_z85!r})))',
329
336
  f'_pyremote_bootstrap_main({context_name!r})',
330
337
  ]
331
338
 
@@ -411,6 +418,10 @@ def pyremote_bootstrap_finalize() -> PyremotePayloadRuntime:
411
418
  os.dup2(nfd := os.open('/dev/null', os.O_WRONLY), 1)
412
419
  os.close(nfd)
413
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
+
414
425
  # Write fourth ack
415
426
  output.write(_PYREMOTE_BOOTSTRAP_ACK3)
416
427
 
@@ -429,14 +440,41 @@ def pyremote_bootstrap_finalize() -> PyremotePayloadRuntime:
429
440
 
430
441
 
431
442
  class PyremoteBootstrapDriver:
432
- def __init__(self, main_src: str, options: PyremoteBootstrapOptions = PyremoteBootstrapOptions()) -> None:
443
+ def __init__(
444
+ self,
445
+ main_src: ta.Union[str, ta.Sequence[str]],
446
+ options: PyremoteBootstrapOptions = PyremoteBootstrapOptions(),
447
+ ) -> None:
433
448
  super().__init__()
434
449
 
435
450
  self._main_src = main_src
436
- self._main_z = zlib.compress(main_src.encode('utf-8'))
437
-
438
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
+
439
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)
440
478
 
441
479
  #
442
480
 
@@ -514,7 +552,7 @@ class PyremoteBootstrapDriver:
514
552
 
515
553
  #
516
554
 
517
- def run(self, stdin: ta.IO, stdout: ta.IO) -> Result:
555
+ def run(self, input: ta.IO, output: ta.IO) -> Result: # noqa
518
556
  gen = self.gen()
519
557
 
520
558
  gi: ta.Optional[bytes] = None
@@ -528,12 +566,12 @@ class PyremoteBootstrapDriver:
528
566
  return e.value
529
567
 
530
568
  if isinstance(go, self.Read):
531
- if len(gi := stdout.read(go.sz)) != go.sz:
569
+ if len(gi := input.read(go.sz)) != go.sz:
532
570
  raise EOFError
533
571
  elif isinstance(go, self.Write):
534
572
  gi = None
535
- stdin.write(go.d)
536
- stdin.flush()
573
+ output.write(go.d)
574
+ output.flush()
537
575
  else:
538
576
  raise TypeError(go)
539
577
 
@@ -697,6 +735,99 @@ json_dump_compact: ta.Callable[..., bytes] = functools.partial(json.dump, **JSON
697
735
  json_dumps_compact: ta.Callable[..., str] = functools.partial(json.dumps, **JSON_COMPACT_KWARGS)
698
736
 
699
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
+
700
831
  ########################################
701
832
  # ../../../omlish/lite/reflect.py
702
833
 
@@ -753,208 +884,1335 @@ def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
753
884
 
754
885
 
755
886
  ########################################
756
- # ../payload.py
887
+ # ../../../omlish/lite/strings.py
757
888
 
758
889
 
759
- @cached_nullary
760
- def _get_self_src() -> str:
761
- return inspect.getsource(sys.modules[__name__])
890
+ ##
762
891
 
763
892
 
764
- def _is_src_amalg(src: str) -> bool:
765
- for l in src.splitlines(): # noqa
766
- if l.startswith('# @omlish-amalg-output '):
767
- return True
768
- return False
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
769
900
 
770
901
 
771
- @cached_nullary
772
- def _is_self_amalg() -> bool:
773
- return _is_src_amalg(_get_self_src())
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('_')
774
905
 
775
906
 
776
- def get_payload_src(*, file: ta.Optional[str]) -> str:
777
- if file is not None:
778
- with open(file) as f:
779
- return f.read()
907
+ ##
780
908
 
781
- if _is_self_amalg():
782
- return _get_self_src()
783
909
 
784
- import importlib.resources
785
- return importlib.resources.files(__package__.split('.')[0] + '.scripts').joinpath('manage.py').read_text()
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
+ )
786
917
 
787
918
 
788
- ########################################
789
- # ../../../omlish/lite/logs.py
790
- """
791
- TODO:
792
- - translate json keys
793
- - debug
794
- """
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
+ )
795
926
 
796
927
 
797
- log = logging.getLogger(__name__)
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)})'
798
933
 
799
934
 
800
935
  ##
801
936
 
802
937
 
803
- class TidLogFilter(logging.Filter):
938
+ FORMAT_NUM_BYTES_SUFFIXES: ta.Sequence[str] = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB']
804
939
 
805
- def filter(self, record):
806
- record.tid = threading.get_native_id()
807
- return True
808
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}'
809
949
 
810
- ##
950
+ return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
811
951
 
812
952
 
813
- class JsonLogFormatter(logging.Formatter):
953
+ ########################################
954
+ # ../commands/base.py
814
955
 
815
- KEYS: ta.Mapping[str, bool] = {
816
- 'name': False,
817
- 'msg': False,
818
- 'args': False,
819
- 'levelname': False,
820
- 'levelno': False,
821
- 'pathname': False,
822
- 'filename': False,
823
- 'module': False,
824
- 'exc_info': True,
825
- 'exc_text': True,
826
- 'stack_info': True,
827
- 'lineno': False,
828
- 'funcName': False,
829
- 'created': False,
830
- 'msecs': False,
831
- 'relativeCreated': False,
832
- 'thread': False,
833
- 'threadName': False,
834
- 'processName': False,
835
- 'process': False,
836
- }
837
956
 
838
- def format(self, record: logging.LogRecord) -> str:
839
- dct = {
840
- k: v
841
- for k, o in self.KEYS.items()
842
- for v in [getattr(record, k)]
843
- if not (o and v is None)
844
- }
845
- return json_dumps_compact(dct)
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]
846
969
 
847
970
 
848
971
  ##
849
972
 
850
973
 
851
- STANDARD_LOG_FORMAT_PARTS = [
852
- ('asctime', '%(asctime)-15s'),
853
- ('process', 'pid=%(process)-6s'),
854
- ('thread', 'tid=%(thread)x'),
855
- ('levelname', '%(levelname)s'),
856
- ('name', '%(name)s'),
857
- ('separator', '::'),
858
- ('message', '%(message)s'),
859
- ]
974
+ @dc.dataclass(frozen=True)
975
+ class CommandException:
976
+ name: str
977
+ repr: str
860
978
 
979
+ traceback: ta.Optional[str] = None
861
980
 
862
- class StandardLogFormatter(logging.Formatter):
981
+ exc: ta.Optional[ta.Any] = None # Exception
863
982
 
864
- @staticmethod
865
- def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
866
- return ' '.join(v for k, v in parts)
983
+ cmd: ta.Optional[Command] = None
867
984
 
868
- converter = datetime.datetime.fromtimestamp # type: ignore
985
+ @classmethod
986
+ def of(
987
+ cls,
988
+ exc: Exception,
989
+ *,
990
+ omit_exc_object: bool = False,
869
991
 
870
- def formatTime(self, record, datefmt=None):
871
- ct = self.converter(record.created) # type: ignore
872
- if datefmt:
873
- return ct.strftime(datefmt) # noqa
874
- else:
875
- t = ct.strftime('%Y-%m-%d %H:%M:%S')
876
- return '%s.%03d' % (t, record.msecs) # noqa
992
+ cmd: ta.Optional[Command] = None,
993
+ ) -> 'CommandException':
994
+ return CommandException(
995
+ name=type(exc).__qualname__,
996
+ repr=repr(exc),
877
997
 
998
+ traceback=(
999
+ ''.join(traceback.format_tb(exc.__traceback__))
1000
+ if getattr(exc, '__traceback__', None) is not None else None
1001
+ ),
878
1002
 
879
- ##
1003
+ exc=None if omit_exc_object else exc,
880
1004
 
1005
+ cmd=cmd,
1006
+ )
881
1007
 
882
- class ProxyLogFilterer(logging.Filterer):
883
- def __init__(self, underlying: logging.Filterer) -> None: # noqa
884
- self._underlying = underlying
885
1008
 
1009
+ class CommandOutputOrException(abc.ABC, ta.Generic[CommandOutputT]):
886
1010
  @property
887
- def underlying(self) -> logging.Filterer:
888
- return self._underlying
1011
+ @abc.abstractmethod
1012
+ def output(self) -> ta.Optional[CommandOutputT]:
1013
+ raise NotImplementedError
889
1014
 
890
1015
  @property
891
- def filters(self):
892
- return self._underlying.filters
1016
+ @abc.abstractmethod
1017
+ def exception(self) -> ta.Optional[CommandException]:
1018
+ raise NotImplementedError
893
1019
 
894
- @filters.setter
895
- def filters(self, filters):
896
- self._underlying.filters = filters
897
1020
 
898
- def addFilter(self, filter): # noqa
899
- self._underlying.addFilter(filter)
1021
+ @dc.dataclass(frozen=True)
1022
+ class CommandOutputOrExceptionData(CommandOutputOrException):
1023
+ output: ta.Optional[Command.Output] = None
1024
+ exception: ta.Optional[CommandException] = None
900
1025
 
901
- def removeFilter(self, filter): # noqa
902
- self._underlying.removeFilter(filter)
903
1026
 
904
- def filter(self, record):
905
- return self._underlying.filter(record)
1027
+ class CommandExecutor(abc.ABC, ta.Generic[CommandT, CommandOutputT]):
1028
+ @abc.abstractmethod
1029
+ def execute(self, cmd: CommandT) -> CommandOutputT:
1030
+ raise NotImplementedError
906
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)
907
1041
 
908
- class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
909
- def __init__(self, underlying: logging.Handler) -> None: # noqa
910
- ProxyLogFilterer.__init__(self, underlying)
1042
+ except Exception as e: # noqa
1043
+ if log is not None:
1044
+ log.exception('Exception executing command: %r', type(cmd))
911
1045
 
912
- _underlying: logging.Handler
1046
+ return CommandOutputOrExceptionData(exception=CommandException.of(
1047
+ e,
1048
+ omit_exc_object=omit_exc_object,
1049
+ cmd=cmd,
1050
+ ))
913
1051
 
914
- @property
915
- def underlying(self) -> logging.Handler:
916
- return self._underlying
1052
+ else:
1053
+ return CommandOutputOrExceptionData(output=o)
917
1054
 
918
- def get_name(self):
919
- return self._underlying.get_name()
920
1055
 
921
- def set_name(self, name):
922
- self._underlying.set_name(name)
1056
+ ##
923
1057
 
924
- @property
925
- def name(self):
926
- return self._underlying.name
927
1058
 
928
- @property
929
- def level(self):
930
- return self._underlying.level
1059
+ @dc.dataclass(frozen=True)
1060
+ class CommandRegistration:
1061
+ command_cls: ta.Type[Command]
931
1062
 
932
- @level.setter
933
- def level(self, level):
934
- self._underlying.level = level
1063
+ name: ta.Optional[str] = None
935
1064
 
936
1065
  @property
937
- def formatter(self):
938
- return self._underlying.formatter
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')])
939
1070
 
940
- @formatter.setter
941
- def formatter(self, formatter):
942
- self._underlying.formatter = formatter
943
1071
 
944
- def createLock(self):
945
- self._underlying.createLock()
1072
+ CommandRegistrations = ta.NewType('CommandRegistrations', ta.Sequence[CommandRegistration])
946
1073
 
947
- def acquire(self):
948
- self._underlying.acquire()
949
1074
 
950
- def release(self):
951
- self._underlying.release()
1075
+ ##
952
1076
 
953
- def setLevel(self, level):
954
- self._underlying.setLevel(level)
955
1077
 
956
- def format(self, record):
957
- return self._underlying.format(record)
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)
1119
+
1120
+
1121
+ @cached_nullary
1122
+ def _get_self_src() -> str:
1123
+ return inspect.getsource(sys.modules[__name__])
1124
+
1125
+
1126
+ def _is_src_amalg(src: str) -> bool:
1127
+ for l in src.splitlines(): # noqa
1128
+ if l.startswith('# @omlish-amalg-output '):
1129
+ return True
1130
+ return False
1131
+
1132
+
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,
2009
+
2010
+ eager=eager,
2011
+ )
2012
+
2013
+ # helpers
2014
+
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))
2023
+
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
2044
+
2045
+
2046
+ ########################################
2047
+ # ../../../omlish/lite/logs.py
2048
+ """
2049
+ TODO:
2050
+ - translate json keys
2051
+ - debug
2052
+ """
2053
+
2054
+
2055
+ log = logging.getLogger(__name__)
2056
+
2057
+
2058
+ ##
2059
+
2060
+
2061
+ class TidLogFilter(logging.Filter):
2062
+
2063
+ def filter(self, record):
2064
+ record.tid = threading.get_native_id()
2065
+ return True
2066
+
2067
+
2068
+ ##
2069
+
2070
+
2071
+ class JsonLogFormatter(logging.Formatter):
2072
+
2073
+ KEYS: ta.Mapping[str, bool] = {
2074
+ 'name': False,
2075
+ 'msg': False,
2076
+ 'args': False,
2077
+ 'levelname': False,
2078
+ 'levelno': False,
2079
+ 'pathname': False,
2080
+ 'filename': False,
2081
+ 'module': False,
2082
+ 'exc_info': True,
2083
+ 'exc_text': True,
2084
+ 'stack_info': True,
2085
+ 'lineno': False,
2086
+ 'funcName': False,
2087
+ 'created': False,
2088
+ 'msecs': False,
2089
+ 'relativeCreated': False,
2090
+ 'thread': False,
2091
+ 'threadName': False,
2092
+ 'processName': False,
2093
+ 'process': False,
2094
+ }
2095
+
2096
+ def format(self, record: logging.LogRecord) -> str:
2097
+ dct = {
2098
+ k: v
2099
+ for k, o in self.KEYS.items()
2100
+ for v in [getattr(record, k)]
2101
+ if not (o and v is None)
2102
+ }
2103
+ return json_dumps_compact(dct)
2104
+
2105
+
2106
+ ##
2107
+
2108
+
2109
+ STANDARD_LOG_FORMAT_PARTS = [
2110
+ ('asctime', '%(asctime)-15s'),
2111
+ ('process', 'pid=%(process)-6s'),
2112
+ ('thread', 'tid=%(thread)x'),
2113
+ ('levelname', '%(levelname)s'),
2114
+ ('name', '%(name)s'),
2115
+ ('separator', '::'),
2116
+ ('message', '%(message)s'),
2117
+ ]
2118
+
2119
+
2120
+ class StandardLogFormatter(logging.Formatter):
2121
+
2122
+ @staticmethod
2123
+ def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
2124
+ return ' '.join(v for k, v in parts)
2125
+
2126
+ converter = datetime.datetime.fromtimestamp # type: ignore
2127
+
2128
+ def formatTime(self, record, datefmt=None):
2129
+ ct = self.converter(record.created) # type: ignore
2130
+ if datefmt:
2131
+ return ct.strftime(datefmt) # noqa
2132
+ else:
2133
+ t = ct.strftime('%Y-%m-%d %H:%M:%S')
2134
+ return '%s.%03d' % (t, record.msecs) # noqa
2135
+
2136
+
2137
+ ##
2138
+
2139
+
2140
+ class ProxyLogFilterer(logging.Filterer):
2141
+ def __init__(self, underlying: logging.Filterer) -> None: # noqa
2142
+ self._underlying = underlying
2143
+
2144
+ @property
2145
+ def underlying(self) -> logging.Filterer:
2146
+ return self._underlying
2147
+
2148
+ @property
2149
+ def filters(self):
2150
+ return self._underlying.filters
2151
+
2152
+ @filters.setter
2153
+ def filters(self, filters):
2154
+ self._underlying.filters = filters
2155
+
2156
+ def addFilter(self, filter): # noqa
2157
+ self._underlying.addFilter(filter)
2158
+
2159
+ def removeFilter(self, filter): # noqa
2160
+ self._underlying.removeFilter(filter)
2161
+
2162
+ def filter(self, record):
2163
+ return self._underlying.filter(record)
2164
+
2165
+
2166
+ class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
2167
+ def __init__(self, underlying: logging.Handler) -> None: # noqa
2168
+ ProxyLogFilterer.__init__(self, underlying)
2169
+
2170
+ _underlying: logging.Handler
2171
+
2172
+ @property
2173
+ def underlying(self) -> logging.Handler:
2174
+ return self._underlying
2175
+
2176
+ def get_name(self):
2177
+ return self._underlying.get_name()
2178
+
2179
+ def set_name(self, name):
2180
+ self._underlying.set_name(name)
2181
+
2182
+ @property
2183
+ def name(self):
2184
+ return self._underlying.name
2185
+
2186
+ @property
2187
+ def level(self):
2188
+ return self._underlying.level
2189
+
2190
+ @level.setter
2191
+ def level(self, level):
2192
+ self._underlying.level = level
2193
+
2194
+ @property
2195
+ def formatter(self):
2196
+ return self._underlying.formatter
2197
+
2198
+ @formatter.setter
2199
+ def formatter(self, formatter):
2200
+ self._underlying.formatter = formatter
2201
+
2202
+ def createLock(self):
2203
+ self._underlying.createLock()
2204
+
2205
+ def acquire(self):
2206
+ self._underlying.acquire()
2207
+
2208
+ def release(self):
2209
+ self._underlying.release()
2210
+
2211
+ def setLevel(self, level):
2212
+ self._underlying.setLevel(level)
2213
+
2214
+ def format(self, record):
2215
+ return self._underlying.format(record)
958
2216
 
959
2217
  def emit(self, record):
960
2218
  self._underlying.emit(record)
@@ -1067,21 +2325,26 @@ TODO:
1067
2325
  ##
1068
2326
 
1069
2327
 
2328
+ @dc.dataclass(frozen=True)
2329
+ class ObjMarshalOptions:
2330
+ raw_bytes: bool = False
2331
+
2332
+
1070
2333
  class ObjMarshaler(abc.ABC):
1071
2334
  @abc.abstractmethod
1072
- def marshal(self, o: ta.Any) -> ta.Any:
2335
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1073
2336
  raise NotImplementedError
1074
2337
 
1075
2338
  @abc.abstractmethod
1076
- def unmarshal(self, o: ta.Any) -> ta.Any:
2339
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1077
2340
  raise NotImplementedError
1078
2341
 
1079
2342
 
1080
2343
  class NopObjMarshaler(ObjMarshaler):
1081
- def marshal(self, o: ta.Any) -> ta.Any:
2344
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1082
2345
  return o
1083
2346
 
1084
- def unmarshal(self, o: ta.Any) -> ta.Any:
2347
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1085
2348
  return o
1086
2349
 
1087
2350
 
@@ -1089,29 +2352,29 @@ class NopObjMarshaler(ObjMarshaler):
1089
2352
  class ProxyObjMarshaler(ObjMarshaler):
1090
2353
  m: ta.Optional[ObjMarshaler] = None
1091
2354
 
1092
- def marshal(self, o: ta.Any) -> ta.Any:
1093
- 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)
1094
2357
 
1095
- def unmarshal(self, o: ta.Any) -> ta.Any:
1096
- 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)
1097
2360
 
1098
2361
 
1099
2362
  @dc.dataclass(frozen=True)
1100
2363
  class CastObjMarshaler(ObjMarshaler):
1101
2364
  ty: type
1102
2365
 
1103
- def marshal(self, o: ta.Any) -> ta.Any:
2366
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1104
2367
  return o
1105
2368
 
1106
- def unmarshal(self, o: ta.Any) -> ta.Any:
2369
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1107
2370
  return self.ty(o)
1108
2371
 
1109
2372
 
1110
2373
  class DynamicObjMarshaler(ObjMarshaler):
1111
- def marshal(self, o: ta.Any) -> ta.Any:
2374
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1112
2375
  return marshal_obj(o)
1113
2376
 
1114
- def unmarshal(self, o: ta.Any) -> ta.Any:
2377
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1115
2378
  return o
1116
2379
 
1117
2380
 
@@ -1119,21 +2382,36 @@ class DynamicObjMarshaler(ObjMarshaler):
1119
2382
  class Base64ObjMarshaler(ObjMarshaler):
1120
2383
  ty: type
1121
2384
 
1122
- def marshal(self, o: ta.Any) -> ta.Any:
2385
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1123
2386
  return base64.b64encode(o).decode('ascii')
1124
2387
 
1125
- def unmarshal(self, o: ta.Any) -> ta.Any:
2388
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1126
2389
  return self.ty(base64.b64decode(o))
1127
2390
 
1128
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
+
1129
2407
  @dc.dataclass(frozen=True)
1130
2408
  class EnumObjMarshaler(ObjMarshaler):
1131
2409
  ty: type
1132
2410
 
1133
- def marshal(self, o: ta.Any) -> ta.Any:
2411
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1134
2412
  return o.name
1135
2413
 
1136
- def unmarshal(self, o: ta.Any) -> ta.Any:
2414
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1137
2415
  return self.ty.__members__[o] # type: ignore
1138
2416
 
1139
2417
 
@@ -1141,15 +2419,15 @@ class EnumObjMarshaler(ObjMarshaler):
1141
2419
  class OptionalObjMarshaler(ObjMarshaler):
1142
2420
  item: ObjMarshaler
1143
2421
 
1144
- def marshal(self, o: ta.Any) -> ta.Any:
2422
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1145
2423
  if o is None:
1146
2424
  return None
1147
- return self.item.marshal(o)
2425
+ return self.item.marshal(o, opts)
1148
2426
 
1149
- def unmarshal(self, o: ta.Any) -> ta.Any:
2427
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1150
2428
  if o is None:
1151
2429
  return None
1152
- return self.item.unmarshal(o)
2430
+ return self.item.unmarshal(o, opts)
1153
2431
 
1154
2432
 
1155
2433
  @dc.dataclass(frozen=True)
@@ -1158,11 +2436,11 @@ class MappingObjMarshaler(ObjMarshaler):
1158
2436
  km: ObjMarshaler
1159
2437
  vm: ObjMarshaler
1160
2438
 
1161
- def marshal(self, o: ta.Any) -> ta.Any:
1162
- 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()}
1163
2441
 
1164
- def unmarshal(self, o: ta.Any) -> ta.Any:
1165
- 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())
1166
2444
 
1167
2445
 
1168
2446
  @dc.dataclass(frozen=True)
@@ -1170,11 +2448,11 @@ class IterableObjMarshaler(ObjMarshaler):
1170
2448
  ty: type
1171
2449
  item: ObjMarshaler
1172
2450
 
1173
- def marshal(self, o: ta.Any) -> ta.Any:
1174
- 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]
1175
2453
 
1176
- def unmarshal(self, o: ta.Any) -> ta.Any:
1177
- 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)
1178
2456
 
1179
2457
 
1180
2458
  @dc.dataclass(frozen=True)
@@ -1183,11 +2461,11 @@ class DataclassObjMarshaler(ObjMarshaler):
1183
2461
  fs: ta.Mapping[str, ObjMarshaler]
1184
2462
  nonstrict: bool = False
1185
2463
 
1186
- def marshal(self, o: ta.Any) -> ta.Any:
1187
- 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()}
1188
2466
 
1189
- def unmarshal(self, o: ta.Any) -> ta.Any:
1190
- 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})
1191
2469
 
1192
2470
 
1193
2471
  @dc.dataclass(frozen=True)
@@ -1207,50 +2485,50 @@ class PolymorphicObjMarshaler(ObjMarshaler):
1207
2485
  {i.tag: i for i in impls},
1208
2486
  )
1209
2487
 
1210
- def marshal(self, o: ta.Any) -> ta.Any:
2488
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1211
2489
  impl = self.impls_by_ty[type(o)]
1212
- return {impl.tag: impl.m.marshal(o)}
2490
+ return {impl.tag: impl.m.marshal(o, opts)}
1213
2491
 
1214
- def unmarshal(self, o: ta.Any) -> ta.Any:
2492
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1215
2493
  [(t, v)] = o.items()
1216
2494
  impl = self.impls_by_tag[t]
1217
- return impl.m.unmarshal(v)
2495
+ return impl.m.unmarshal(v, opts)
1218
2496
 
1219
2497
 
1220
2498
  @dc.dataclass(frozen=True)
1221
2499
  class DatetimeObjMarshaler(ObjMarshaler):
1222
2500
  ty: type
1223
2501
 
1224
- def marshal(self, o: ta.Any) -> ta.Any:
2502
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1225
2503
  return o.isoformat()
1226
2504
 
1227
- def unmarshal(self, o: ta.Any) -> ta.Any:
2505
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1228
2506
  return self.ty.fromisoformat(o) # type: ignore
1229
2507
 
1230
2508
 
1231
2509
  class DecimalObjMarshaler(ObjMarshaler):
1232
- def marshal(self, o: ta.Any) -> ta.Any:
2510
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1233
2511
  return str(check_isinstance(o, decimal.Decimal))
1234
2512
 
1235
- def unmarshal(self, v: ta.Any) -> ta.Any:
2513
+ def unmarshal(self, v: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1236
2514
  return decimal.Decimal(check_isinstance(v, str))
1237
2515
 
1238
2516
 
1239
2517
  class FractionObjMarshaler(ObjMarshaler):
1240
- def marshal(self, o: ta.Any) -> ta.Any:
2518
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1241
2519
  fr = check_isinstance(o, fractions.Fraction)
1242
2520
  return [fr.numerator, fr.denominator]
1243
2521
 
1244
- def unmarshal(self, v: ta.Any) -> ta.Any:
2522
+ def unmarshal(self, v: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1245
2523
  num, denom = check_isinstance(v, list)
1246
2524
  return fractions.Fraction(num, denom)
1247
2525
 
1248
2526
 
1249
2527
  class UuidObjMarshaler(ObjMarshaler):
1250
- def marshal(self, o: ta.Any) -> ta.Any:
2528
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1251
2529
  return str(o)
1252
2530
 
1253
- def unmarshal(self, o: ta.Any) -> ta.Any:
2531
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1254
2532
  return uuid.UUID(o)
1255
2533
 
1256
2534
 
@@ -1260,7 +2538,7 @@ class UuidObjMarshaler(ObjMarshaler):
1260
2538
  _DEFAULT_OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = {
1261
2539
  **{t: NopObjMarshaler() for t in (type(None),)},
1262
2540
  **{t: CastObjMarshaler(t) for t in (int, float, str, bool)},
1263
- **{t: Base64ObjMarshaler(t) for t in (bytes, bytearray)},
2541
+ **{t: BytesSwitchedObjMarshaler(Base64ObjMarshaler(t)) for t in (bytes, bytearray)},
1264
2542
  **{t: IterableObjMarshaler(t, DynamicObjMarshaler()) for t in (list, tuple, set, frozenset)},
1265
2543
  **{t: MappingObjMarshaler(t, DynamicObjMarshaler(), DynamicObjMarshaler()) for t in (dict,)},
1266
2544
 
@@ -1277,146 +2555,313 @@ _OBJ_MARSHALER_GENERIC_MAPPING_TYPES: ta.Dict[ta.Any, type] = {
1277
2555
  **{t: dict for t in (collections.abc.Mapping, collections.abc.MutableMapping)},
1278
2556
  }
1279
2557
 
1280
- _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES: ta.Dict[ta.Any, type] = {
1281
- **{t: t for t in (list, tuple, set, frozenset)},
1282
- collections.abc.Set: frozenset,
1283
- collections.abc.MutableSet: set,
1284
- collections.abc.Sequence: tuple,
1285
- collections.abc.MutableSequence: list,
1286
- }
2558
+ _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES: ta.Dict[ta.Any, type] = {
2559
+ **{t: t for t in (list, tuple, set, frozenset)},
2560
+ collections.abc.Set: frozenset,
2561
+ collections.abc.MutableSet: set,
2562
+ collections.abc.Sequence: tuple,
2563
+ collections.abc.MutableSequence: list,
2564
+ }
2565
+
2566
+
2567
+ ##
2568
+
2569
+
2570
+ class ObjMarshalerManager:
2571
+ def __init__(
2572
+ self,
2573
+ *,
2574
+ default_options: ObjMarshalOptions = ObjMarshalOptions(),
2575
+
2576
+ default_obj_marshalers: ta.Dict[ta.Any, ObjMarshaler] = _DEFAULT_OBJ_MARSHALERS, # noqa
2577
+ generic_mapping_types: ta.Dict[ta.Any, type] = _OBJ_MARSHALER_GENERIC_MAPPING_TYPES, # noqa
2578
+ generic_iterable_types: ta.Dict[ta.Any, type] = _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES, # noqa
2579
+ ) -> None:
2580
+ super().__init__()
2581
+
2582
+ self._default_options = default_options
2583
+
2584
+ self._obj_marshalers = dict(default_obj_marshalers)
2585
+ self._generic_mapping_types = generic_mapping_types
2586
+ self._generic_iterable_types = generic_iterable_types
2587
+
2588
+ self._lock = threading.RLock()
2589
+ self._marshalers: ta.Dict[ta.Any, ObjMarshaler] = dict(_DEFAULT_OBJ_MARSHALERS)
2590
+ self._proxies: ta.Dict[ta.Any, ProxyObjMarshaler] = {}
2591
+
2592
+ #
2593
+
2594
+ def make_obj_marshaler(
2595
+ self,
2596
+ ty: ta.Any,
2597
+ rec: ta.Callable[[ta.Any], ObjMarshaler],
2598
+ *,
2599
+ nonstrict_dataclasses: bool = False,
2600
+ ) -> ObjMarshaler:
2601
+ if isinstance(ty, type):
2602
+ if abc.ABC in ty.__bases__:
2603
+ return PolymorphicObjMarshaler.of([ # type: ignore
2604
+ PolymorphicObjMarshaler.Impl(
2605
+ ity,
2606
+ ity.__qualname__,
2607
+ rec(ity),
2608
+ )
2609
+ for ity in deep_subclasses(ty)
2610
+ if abc.ABC not in ity.__bases__
2611
+ ])
2612
+
2613
+ if issubclass(ty, enum.Enum):
2614
+ return EnumObjMarshaler(ty)
2615
+
2616
+ if dc.is_dataclass(ty):
2617
+ return DataclassObjMarshaler(
2618
+ ty,
2619
+ {f.name: rec(f.type) for f in dc.fields(ty)},
2620
+ nonstrict=nonstrict_dataclasses,
2621
+ )
2622
+
2623
+ if is_generic_alias(ty):
2624
+ try:
2625
+ mt = self._generic_mapping_types[ta.get_origin(ty)]
2626
+ except KeyError:
2627
+ pass
2628
+ else:
2629
+ k, v = ta.get_args(ty)
2630
+ return MappingObjMarshaler(mt, rec(k), rec(v))
2631
+
2632
+ try:
2633
+ st = self._generic_iterable_types[ta.get_origin(ty)]
2634
+ except KeyError:
2635
+ pass
2636
+ else:
2637
+ [e] = ta.get_args(ty)
2638
+ return IterableObjMarshaler(st, rec(e))
2639
+
2640
+ if is_union_alias(ty):
2641
+ return OptionalObjMarshaler(rec(get_optional_alias_arg(ty)))
2642
+
2643
+ raise TypeError(ty)
2644
+
2645
+ #
2646
+
2647
+ def register_opj_marshaler(self, ty: ta.Any, m: ObjMarshaler) -> None:
2648
+ with self._lock:
2649
+ if ty in self._obj_marshalers:
2650
+ raise KeyError(ty)
2651
+ self._obj_marshalers[ty] = m
2652
+
2653
+ def get_obj_marshaler(
2654
+ self,
2655
+ ty: ta.Any,
2656
+ *,
2657
+ no_cache: bool = False,
2658
+ **kwargs: ta.Any,
2659
+ ) -> ObjMarshaler:
2660
+ with self._lock:
2661
+ if not no_cache:
2662
+ try:
2663
+ return self._obj_marshalers[ty]
2664
+ except KeyError:
2665
+ pass
2666
+
2667
+ try:
2668
+ return self._proxies[ty]
2669
+ except KeyError:
2670
+ pass
2671
+
2672
+ rec = functools.partial(
2673
+ self.get_obj_marshaler,
2674
+ no_cache=no_cache,
2675
+ **kwargs,
2676
+ )
2677
+
2678
+ p = ProxyObjMarshaler()
2679
+ self._proxies[ty] = p
2680
+ try:
2681
+ m = self.make_obj_marshaler(ty, rec, **kwargs)
2682
+ finally:
2683
+ del self._proxies[ty]
2684
+ p.m = m
2685
+
2686
+ if not no_cache:
2687
+ self._obj_marshalers[ty] = m
2688
+ return m
2689
+
2690
+ #
2691
+
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
2721
+
2722
+
2723
+ ##
2724
+
2725
+
2726
+ OBJ_MARSHALER_MANAGER = ObjMarshalerManager()
2727
+
2728
+ register_opj_marshaler = OBJ_MARSHALER_MANAGER.register_opj_marshaler
2729
+ get_obj_marshaler = OBJ_MARSHALER_MANAGER.get_obj_marshaler
2730
+
2731
+ marshal_obj = OBJ_MARSHALER_MANAGER.marshal_obj
2732
+ unmarshal_obj = OBJ_MARSHALER_MANAGER.unmarshal_obj
2733
+
2734
+
2735
+ ########################################
2736
+ # ../../../omlish/lite/runtime.py
2737
+
2738
+
2739
+ @cached_nullary
2740
+ def is_debugger_attached() -> bool:
2741
+ return any(frame[1].endswith('pydevd.py') for frame in inspect.stack())
2742
+
2743
+
2744
+ REQUIRED_PYTHON_VERSION = (3, 8)
2745
+
2746
+
2747
+ def check_runtime_version() -> None:
2748
+ if sys.version_info < REQUIRED_PYTHON_VERSION:
2749
+ raise OSError(f'Requires python {REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
1287
2750
 
1288
2751
 
1289
- def _make_obj_marshaler(
1290
- ty: ta.Any,
1291
- rec: ta.Callable[[ta.Any], ObjMarshaler],
1292
- *,
1293
- nonstrict_dataclasses: bool = False,
1294
- ) -> ObjMarshaler:
1295
- if isinstance(ty, type):
1296
- if abc.ABC in ty.__bases__:
1297
- return PolymorphicObjMarshaler.of([ # type: ignore
1298
- PolymorphicObjMarshaler.Impl(
1299
- ity,
1300
- ity.__qualname__,
1301
- rec(ity),
1302
- )
1303
- for ity in deep_subclasses(ty)
1304
- if abc.ABC not in ity.__bases__
1305
- ])
1306
-
1307
- if issubclass(ty, enum.Enum):
1308
- return EnumObjMarshaler(ty)
1309
-
1310
- if dc.is_dataclass(ty):
1311
- return DataclassObjMarshaler(
1312
- ty,
1313
- {f.name: rec(f.type) for f in dc.fields(ty)},
1314
- nonstrict=nonstrict_dataclasses,
1315
- )
2752
+ ########################################
2753
+ # ../bootstrap.py
1316
2754
 
1317
- if is_generic_alias(ty):
1318
- try:
1319
- mt = _OBJ_MARSHALER_GENERIC_MAPPING_TYPES[ta.get_origin(ty)]
1320
- except KeyError:
1321
- pass
1322
- else:
1323
- k, v = ta.get_args(ty)
1324
- return MappingObjMarshaler(mt, rec(k), rec(v))
1325
2755
 
1326
- try:
1327
- st = _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES[ta.get_origin(ty)]
1328
- except KeyError:
1329
- pass
1330
- else:
1331
- [e] = ta.get_args(ty)
1332
- return IterableObjMarshaler(st, rec(e))
2756
+ @dc.dataclass(frozen=True)
2757
+ class MainBootstrap:
2758
+ main_config: MainConfig = MainConfig()
1333
2759
 
1334
- if is_union_alias(ty):
1335
- return OptionalObjMarshaler(rec(get_optional_alias_arg(ty)))
2760
+ remote_config: RemoteConfig = RemoteConfig()
1336
2761
 
1337
- raise TypeError(ty)
1338
2762
 
2763
+ ########################################
2764
+ # ../commands/execution.py
1339
2765
 
1340
- ##
1341
2766
 
2767
+ CommandExecutorMap = ta.NewType('CommandExecutorMap', ta.Mapping[ta.Type[Command], CommandExecutor])
1342
2768
 
1343
- _OBJ_MARSHALERS_LOCK = threading.RLock()
1344
2769
 
1345
- _OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = dict(_DEFAULT_OBJ_MARSHALERS)
2770
+ class CommandExecutionService(CommandExecutor):
2771
+ def __init__(
2772
+ self,
2773
+ *,
2774
+ command_executors: CommandExecutorMap,
2775
+ ) -> None:
2776
+ super().__init__()
1346
2777
 
1347
- _OBJ_MARSHALER_PROXIES: ta.Dict[ta.Any, ProxyObjMarshaler] = {}
2778
+ self._command_executors = command_executors
1348
2779
 
2780
+ def execute(self, cmd: Command) -> Command.Output:
2781
+ ce: CommandExecutor = self._command_executors[type(cmd)]
2782
+ return ce.execute(cmd)
1349
2783
 
1350
- def register_opj_marshaler(ty: ta.Any, m: ObjMarshaler) -> None:
1351
- with _OBJ_MARSHALERS_LOCK:
1352
- if ty in _OBJ_MARSHALERS:
1353
- raise KeyError(ty)
1354
- _OBJ_MARSHALERS[ty] = m
1355
2784
 
2785
+ ########################################
2786
+ # ../commands/marshal.py
1356
2787
 
1357
- def get_obj_marshaler(
1358
- ty: ta.Any,
1359
- *,
1360
- no_cache: bool = False,
1361
- **kwargs: ta.Any,
1362
- ) -> ObjMarshaler:
1363
- with _OBJ_MARSHALERS_LOCK:
1364
- if not no_cache:
1365
- try:
1366
- return _OBJ_MARSHALERS[ty]
1367
- except KeyError:
1368
- pass
1369
2788
 
1370
- try:
1371
- return _OBJ_MARSHALER_PROXIES[ty]
1372
- except KeyError:
1373
- pass
1374
-
1375
- rec = functools.partial(
1376
- get_obj_marshaler,
1377
- no_cache=no_cache,
1378
- **kwargs,
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
+ ]),
1379
2807
  )
1380
2808
 
1381
- p = ProxyObjMarshaler()
1382
- _OBJ_MARSHALER_PROXIES[ty] = p
1383
- try:
1384
- m = _make_obj_marshaler(ty, rec, **kwargs)
1385
- finally:
1386
- del _OBJ_MARSHALER_PROXIES[ty]
1387
- p.m = m
1388
2809
 
1389
- if not no_cache:
1390
- _OBJ_MARSHALERS[ty] = m
1391
- return m
2810
+ ########################################
2811
+ # ../marshal.py
1392
2812
 
1393
2813
 
1394
- ##
2814
+ @dc.dataclass(frozen=True)
2815
+ class ObjMarshalerInstaller:
2816
+ fn: ta.Callable[[ObjMarshalerManager], None]
1395
2817
 
1396
2818
 
1397
- def marshal_obj(o: ta.Any, ty: ta.Any = None) -> ta.Any:
1398
- return get_obj_marshaler(ty if ty is not None else type(o)).marshal(o)
2819
+ ObjMarshalerInstallers = ta.NewType('ObjMarshalerInstallers', ta.Sequence[ObjMarshalerInstaller])
1399
2820
 
1400
2821
 
1401
- def unmarshal_obj(o: ta.Any, ty: ta.Union[ta.Type[T], ta.Any]) -> T:
1402
- return get_obj_marshaler(ty).unmarshal(o)
2822
+ ########################################
2823
+ # ../remote/channel.py
1403
2824
 
1404
2825
 
1405
- ########################################
1406
- # ../../../omlish/lite/runtime.py
2826
+ class RemoteChannel:
2827
+ def __init__(
2828
+ self,
2829
+ input: ta.IO, # noqa
2830
+ output: ta.IO,
2831
+ *,
2832
+ msh: ObjMarshalerManager = OBJ_MARSHALER_MANAGER,
2833
+ ) -> None:
2834
+ super().__init__()
1407
2835
 
2836
+ self._input = input
2837
+ self._output = output
2838
+ self._msh = msh
1408
2839
 
1409
- @cached_nullary
1410
- def is_debugger_attached() -> bool:
1411
- return any(frame[1].endswith('pydevd.py') for frame in inspect.stack())
2840
+ def set_marshaler(self, msh: ObjMarshalerManager) -> None:
2841
+ self._msh = msh
1412
2842
 
2843
+ def send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
2844
+ j = json_dumps_compact(self._msh.marshal_obj(o, ty))
2845
+ d = j.encode('utf-8')
1413
2846
 
1414
- REQUIRED_PYTHON_VERSION = (3, 8)
2847
+ self._output.write(struct.pack('<I', len(d)))
2848
+ self._output.write(d)
2849
+ self._output.flush()
1415
2850
 
2851
+ def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
2852
+ d = self._input.read(4)
2853
+ if not d:
2854
+ return None
2855
+ if len(d) != 4:
2856
+ raise EOFError
1416
2857
 
1417
- def check_runtime_version() -> None:
1418
- if sys.version_info < REQUIRED_PYTHON_VERSION:
1419
- raise OSError(f'Requires python {REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
2858
+ sz = struct.unpack('<I', d)[0]
2859
+ d = self._input.read(sz)
2860
+ if len(d) != sz:
2861
+ raise EOFError
2862
+
2863
+ j = json.loads(d.decode('utf-8'))
2864
+ return self._msh.unmarshal_obj(j, ty)
1420
2865
 
1421
2866
 
1422
2867
  ########################################
@@ -1426,6 +2871,16 @@ def check_runtime_version() -> None:
1426
2871
  ##
1427
2872
 
1428
2873
 
2874
+ SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
2875
+ 'pipe': subprocess.PIPE,
2876
+ 'stdout': subprocess.STDOUT,
2877
+ 'devnull': subprocess.DEVNULL,
2878
+ }
2879
+
2880
+
2881
+ ##
2882
+
2883
+
1429
2884
  _SUBPROCESS_SHELL_WRAP_EXECS = False
1430
2885
 
1431
2886
 
@@ -1552,21 +3007,21 @@ def subprocess_close(
1552
3007
 
1553
3008
  @dc.dataclass(frozen=True)
1554
3009
  class SubprocessCommand(Command['SubprocessCommand.Output']):
1555
- args: ta.Sequence[str]
3010
+ cmd: ta.Sequence[str]
1556
3011
 
1557
3012
  shell: bool = False
1558
3013
  cwd: ta.Optional[str] = None
1559
3014
  env: ta.Optional[ta.Mapping[str, str]] = None
1560
3015
 
1561
- capture_stdout: bool = False
1562
- capture_stderr: bool = False
3016
+ stdout: str = 'pipe' # SubprocessChannelOption
3017
+ stderr: str = 'pipe' # SubprocessChannelOption
1563
3018
 
1564
3019
  input: ta.Optional[bytes] = None
1565
3020
  timeout: ta.Optional[float] = None
1566
3021
 
1567
3022
  def __post_init__(self) -> None:
1568
- if isinstance(self.args, str):
1569
- raise TypeError(self.args)
3023
+ if isinstance(self.cmd, str):
3024
+ raise TypeError(self.cmd)
1570
3025
 
1571
3026
  @dc.dataclass(frozen=True)
1572
3027
  class Output(Command.Output):
@@ -1584,24 +3039,23 @@ class SubprocessCommand(Command['SubprocessCommand.Output']):
1584
3039
 
1585
3040
  class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCommand.Output]):
1586
3041
  def execute(self, inp: SubprocessCommand) -> SubprocessCommand.Output:
1587
- proc = subprocess.Popen(
1588
- subprocess_maybe_shell_wrap_exec(*inp.args),
3042
+ with subprocess.Popen(
3043
+ subprocess_maybe_shell_wrap_exec(*inp.cmd),
1589
3044
 
1590
3045
  shell=inp.shell,
1591
3046
  cwd=inp.cwd,
1592
3047
  env={**os.environ, **(inp.env or {})},
1593
3048
 
1594
3049
  stdin=subprocess.PIPE if inp.input is not None else None,
1595
- stdout=subprocess.PIPE if inp.capture_stdout else None,
1596
- stderr=subprocess.PIPE if inp.capture_stderr else None,
1597
- )
1598
-
1599
- start_time = time.time()
1600
- stdout, stderr = proc.communicate(
1601
- input=inp.input,
1602
- timeout=inp.timeout,
1603
- )
1604
- end_time = time.time()
3050
+ stdout=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, inp.stdout)],
3051
+ stderr=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, inp.stderr)],
3052
+ ) as proc:
3053
+ start_time = time.time()
3054
+ stdout, stderr = proc.communicate(
3055
+ input=inp.input,
3056
+ timeout=inp.timeout,
3057
+ )
3058
+ end_time = time.time()
1605
3059
 
1606
3060
  return SubprocessCommand.Output(
1607
3061
  rc=proc.returncode,
@@ -1615,28 +3069,19 @@ class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCom
1615
3069
 
1616
3070
 
1617
3071
  ########################################
1618
- # ../spawning.py
3072
+ # ../remote/spawning.py
1619
3073
 
1620
3074
 
1621
- class PySpawner:
1622
- DEFAULT_PYTHON = 'python3'
3075
+ class RemoteSpawning:
3076
+ @dc.dataclass(frozen=True)
3077
+ class Target:
3078
+ shell: ta.Optional[str] = None
3079
+ shell_quote: bool = False
1623
3080
 
1624
- def __init__(
1625
- self,
1626
- src: str,
1627
- *,
1628
- shell: ta.Optional[str] = None,
1629
- shell_quote: bool = False,
1630
- python: str = DEFAULT_PYTHON,
1631
- stderr: ta.Optional[ta.Literal['pipe', 'stdout', 'devnull']] = None,
1632
- ) -> None:
1633
- super().__init__()
3081
+ DEFAULT_PYTHON: ta.ClassVar[str] = 'python3'
3082
+ python: str = DEFAULT_PYTHON
1634
3083
 
1635
- self._src = src
1636
- self._shell = shell
1637
- self._shell_quote = shell_quote
1638
- self._python = python
1639
- self._stderr = stderr
3084
+ stderr: ta.Optional[str] = None # SubprocessChannelOption
1640
3085
 
1641
3086
  #
1642
3087
 
@@ -1644,31 +3089,29 @@ class PySpawner:
1644
3089
  cmd: ta.Sequence[str]
1645
3090
  shell: bool
1646
3091
 
1647
- def _prepare_cmd(self) -> _PreparedCmd:
1648
- if self._shell is not None:
1649
- sh_src = f'{self._python} -c {shlex.quote(self._src)}'
1650
- if self._shell_quote:
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:
1651
3100
  sh_src = shlex.quote(sh_src)
1652
- sh_cmd = f'{self._shell} {sh_src}'
1653
- return PySpawner._PreparedCmd(
3101
+ sh_cmd = f'{tgt.shell} {sh_src}'
3102
+ return RemoteSpawning._PreparedCmd(
1654
3103
  cmd=[sh_cmd],
1655
3104
  shell=True,
1656
3105
  )
1657
3106
 
1658
3107
  else:
1659
- return PySpawner._PreparedCmd(
1660
- cmd=[self._python, '-c', self._src],
3108
+ return RemoteSpawning._PreparedCmd(
3109
+ cmd=[tgt.python, '-c', src],
1661
3110
  shell=False,
1662
3111
  )
1663
3112
 
1664
3113
  #
1665
3114
 
1666
- _STDERR_KWARG_MAP: ta.Mapping[str, int] = {
1667
- 'pipe': subprocess.PIPE,
1668
- 'stdout': subprocess.STDOUT,
1669
- 'devnull': subprocess.DEVNULL,
1670
- }
1671
-
1672
3115
  @dc.dataclass(frozen=True)
1673
3116
  class Spawned:
1674
3117
  stdin: ta.IO
@@ -1678,23 +3121,28 @@ class PySpawner:
1678
3121
  @contextlib.contextmanager
1679
3122
  def spawn(
1680
3123
  self,
3124
+ tgt: Target,
3125
+ src: str,
1681
3126
  *,
1682
3127
  timeout: ta.Optional[float] = None,
1683
3128
  ) -> ta.Generator[Spawned, None, None]:
1684
- pc = self._prepare_cmd()
3129
+ pc = self._prepare_cmd(tgt, src)
1685
3130
 
1686
3131
  with subprocess.Popen(
1687
3132
  subprocess_maybe_shell_wrap_exec(*pc.cmd),
1688
3133
  shell=pc.shell,
1689
3134
  stdin=subprocess.PIPE,
1690
3135
  stdout=subprocess.PIPE,
1691
- stderr=self._STDERR_KWARG_MAP[self._stderr] if self._stderr is not None else None,
3136
+ stderr=(
3137
+ SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, tgt.stderr)]
3138
+ if tgt.stderr is not None else None
3139
+ ),
1692
3140
  ) as proc:
1693
3141
  stdin = check_not_none(proc.stdin)
1694
3142
  stdout = check_not_none(proc.stdout)
1695
3143
 
1696
3144
  try:
1697
- yield PySpawner.Spawned(
3145
+ yield RemoteSpawning.Spawned(
1698
3146
  stdin=stdin,
1699
3147
  stdout=stdout,
1700
3148
  stderr=proc.stderr,
@@ -1710,81 +3158,356 @@ class PySpawner:
1710
3158
 
1711
3159
 
1712
3160
  ########################################
1713
- # main.py
3161
+ # ../commands/inject.py
1714
3162
 
1715
3163
 
1716
3164
  ##
1717
3165
 
1718
3166
 
1719
- _COMMAND_TYPES = {
1720
- 'subprocess': SubprocessCommand,
1721
- }
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
+ ]
1722
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
+ ])
1723
3180
 
1724
- @static_init
1725
- def _register_command_marshaling() -> None:
1726
- for fn in [
1727
- lambda c: c,
1728
- lambda c: c.Output,
1729
- ]:
1730
- register_opj_marshaler(
1731
- fn(Command),
1732
- PolymorphicObjMarshaler.of([
1733
- PolymorphicObjMarshaler.Impl(
1734
- fn(cty),
1735
- k,
1736
- get_obj_marshaler(fn(cty)),
1737
- )
1738
- for k, cty in _COMMAND_TYPES.items()
1739
- ]),
1740
- )
3181
+ return inj.as_bindings(*lst)
1741
3182
 
1742
3183
 
1743
3184
  ##
1744
3185
 
1745
3186
 
1746
- def _send_obj(f: ta.IO, o: ta.Any, ty: ta.Any = None) -> None:
1747
- j = json_dumps_compact(marshal_obj(o, ty))
1748
- d = j.encode('utf-8')
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
+
1749
3194
 
1750
- f.write(struct.pack('<I', len(d)))
1751
- f.write(d)
1752
- f.flush()
3195
+ ##
1753
3196
 
1754
3197
 
1755
- def _recv_obj(f: ta.IO, ty: ta.Any) -> ta.Any:
1756
- d = f.read(4)
1757
- if not d:
1758
- return None
1759
- if len(d) != 4:
1760
- raise EOFError
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)
1761
3241
 
1762
- sz = struct.unpack('<I', d)[0]
1763
- d = f.read(sz)
1764
- if len(d) != sz:
1765
- raise EOFError
3242
+ lst.extend([
3243
+ inj.bind(provide_command_executor_map, singleton=True),
1766
3244
 
1767
- j = json.loads(d.decode('utf-8'))
1768
- return unmarshal_obj(j, ty)
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),
3253
+ ]:
3254
+ lst.append(bind_command(command_cls, executor_cls))
3255
+
3256
+ #
3257
+
3258
+ return inj.as_bindings(*lst)
3259
+
3260
+
3261
+ ########################################
3262
+ # ../remote/execution.py
1769
3263
 
1770
3264
 
1771
3265
  ##
1772
3266
 
1773
3267
 
1774
- def _remote_main() -> None:
3268
+ def _remote_execution_main() -> None:
1775
3269
  rt = pyremote_bootstrap_finalize() # noqa
1776
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]
3286
+
1777
3287
  while True:
1778
- i = _recv_obj(rt.input, Command)
3288
+ i = chan.recv_obj(Command)
1779
3289
  if i is None:
1780
3290
  break
1781
3291
 
1782
- if isinstance(i, SubprocessCommand):
1783
- o = SubprocessCommandExecutor().execute(i) # noqa
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
+
1784
3352
  else:
1785
- raise TypeError(i)
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
3495
+
1786
3496
 
1787
- _send_obj(rt.output, o, Command.Output)
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
1788
3511
 
1789
3512
 
1790
3513
  ##
@@ -1795,65 +3518,70 @@ def _main() -> None:
1795
3518
 
1796
3519
  parser = argparse.ArgumentParser()
1797
3520
 
3521
+ parser.add_argument('--_payload-file')
3522
+
1798
3523
  parser.add_argument('-s', '--shell')
1799
3524
  parser.add_argument('-q', '--shell-quote', action='store_true')
1800
3525
  parser.add_argument('--python', default='python3')
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
+
1801
3531
  parser.add_argument('--debug', action='store_true')
1802
- parser.add_argument('--_payload-file')
3532
+
3533
+ parser.add_argument('command', nargs='+')
1803
3534
 
1804
3535
  args = parser.parse_args()
1805
3536
 
1806
3537
  #
1807
3538
 
1808
- payload_src = get_payload_src(file=args._payload_file) # noqa
3539
+ bs = MainBootstrap(
3540
+ main_config=MainConfig(
3541
+ log_level='DEBUG' if args.debug else 'INFO',
1809
3542
 
1810
- #
3543
+ debug=bool(args.debug),
3544
+ ),
1811
3545
 
1812
- remote_src = '\n\n'.join([
1813
- '__name__ = "__remote__"',
1814
- payload_src,
1815
- '_remote_main()',
1816
- ])
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
+ )
1817
3560
 
1818
3561
  #
1819
3562
 
1820
- bs_src = pyremote_build_bootstrap_cmd(__package__ or 'manage')
3563
+ cmds = [
3564
+ SubprocessCommand([c])
3565
+ for c in args.command
3566
+ ]
1821
3567
 
1822
3568
  #
1823
3569
 
1824
- spawner = PySpawner(
1825
- bs_src,
3570
+ tgt = RemoteSpawning.Target(
1826
3571
  shell=args.shell,
1827
3572
  shell_quote=args.shell_quote,
1828
3573
  python=args.python,
1829
3574
  )
1830
- with spawner.spawn() as proc:
1831
- res = PyremoteBootstrapDriver( # noqa
1832
- remote_src,
1833
- PyremoteBootstrapOptions(
1834
- debug=args.debug,
1835
- ),
1836
- ).run(proc.stdin, proc.stdout)
1837
- # print(res)
1838
3575
 
1839
- #
3576
+ with injector[RemoteExecution].connect(tgt, bs) as rce:
3577
+ for cmd in cmds:
3578
+ r = rce.try_execute(cmd)
1840
3579
 
1841
- for ci in [
1842
- SubprocessCommand(
1843
- args=['python3', '-'],
1844
- input=b'print(1)\n',
1845
- capture_stdout=True,
1846
- ),
1847
- SubprocessCommand(
1848
- args=['uname'],
1849
- capture_stdout=True,
1850
- ),
1851
- ]:
1852
- _send_obj(proc.stdin, ci, Command)
3580
+ print(injector[ObjMarshalerManager].marshal_obj(r, opts=ObjMarshalOptions(raw_bytes=True)))
1853
3581
 
1854
- o = _recv_obj(proc.stdout, Command.Output)
3582
+ #
1855
3583
 
1856
- print(o)
3584
+ print('Success')
1857
3585
 
1858
3586
 
1859
3587
  if __name__ == '__main__':