ominfra 0.0.0.dev142__py3-none-any.whl → 0.0.0.dev144__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) 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/config.py +10 -0
  10. ominfra/manage/deploy/__init__.py +0 -0
  11. ominfra/manage/deploy/command.py +23 -0
  12. ominfra/manage/deploy/inject.py +19 -0
  13. ominfra/manage/inject.py +58 -0
  14. ominfra/manage/main.py +64 -90
  15. ominfra/manage/marshal.py +12 -0
  16. ominfra/manage/remote/__init__.py +0 -0
  17. ominfra/manage/{protocol.py → remote/channel.py} +9 -2
  18. ominfra/manage/remote/config.py +12 -0
  19. ominfra/manage/remote/execution.py +193 -0
  20. ominfra/manage/remote/inject.py +29 -0
  21. ominfra/manage/{payload.py → remote/payload.py} +7 -1
  22. ominfra/manage/{spawning.py → remote/spawning.py} +29 -29
  23. ominfra/pyremote.py +40 -3
  24. ominfra/scripts/journald2aws.py +98 -50
  25. ominfra/scripts/manage.py +2025 -295
  26. ominfra/scripts/supervisor.py +99 -50
  27. ominfra/supervisor/inject.py +2 -1
  28. {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev144.dist-info}/METADATA +3 -3
  29. {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev144.dist-info}/RECORD +33 -17
  30. {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev144.dist-info}/LICENSE +0 -0
  31. {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev144.dist-info}/WHEEL +0 -0
  32. {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev144.dist-info}/entry_points.txt +0 -0
  33. {ominfra-0.0.0.dev142.dist-info → ominfra-0.0.0.dev144.dist-info}/top_level.txt +0 -0
ominfra/scripts/manage.py CHANGED
@@ -31,6 +31,7 @@ import subprocess
31
31
  import sys
32
32
  import threading
33
33
  import time
34
+ import traceback
34
35
  import types
35
36
  import typing as ta
36
37
  import uuid
@@ -48,10 +49,6 @@ if sys.version_info < (3, 8):
48
49
  ########################################
49
50
 
50
51
 
51
- # commands/base.py
52
- CommandT = ta.TypeVar('CommandT', bound='Command')
53
- CommandOutputT = ta.TypeVar('CommandOutputT', bound='Command.Output')
54
-
55
52
  # ../../omlish/lite/cached.py
56
53
  T = ta.TypeVar('T')
57
54
  CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
@@ -59,37 +56,39 @@ CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
59
56
  # ../../omlish/lite/check.py
60
57
  SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
61
58
 
59
+ # commands/base.py
60
+ CommandT = ta.TypeVar('CommandT', bound='Command')
61
+ CommandOutputT = ta.TypeVar('CommandOutputT', bound='Command.Output')
62
+
63
+ # ../../omlish/lite/inject.py
64
+ U = ta.TypeVar('U')
65
+ InjectorKeyCls = ta.Union[type, ta.NewType]
66
+ InjectorProviderFn = ta.Callable[['Injector'], ta.Any]
67
+ InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
68
+ InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
69
+
62
70
  # ../../omlish/lite/subprocesses.py
63
71
  SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull']
64
72
 
65
73
 
66
74
  ########################################
67
- # ../commands/base.py
68
-
69
-
70
- ##
75
+ # ../config.py
71
76
 
72
77
 
73
78
  @dc.dataclass(frozen=True)
74
- class Command(abc.ABC, ta.Generic[CommandOutputT]):
75
- @dc.dataclass(frozen=True)
76
- class Output(abc.ABC): # noqa
77
- pass
78
-
79
-
80
- ##
79
+ class MainConfig:
80
+ log_level: ta.Optional[str] = 'INFO'
81
81
 
82
-
83
- class CommandExecutor(abc.ABC, ta.Generic[CommandT, CommandOutputT]):
84
- @abc.abstractmethod
85
- def execute(self, i: CommandT) -> CommandOutputT:
86
- raise NotImplementedError
82
+ debug: bool = False
87
83
 
88
84
 
89
85
  ########################################
90
86
  # ../../pyremote.py
91
87
  """
92
88
  Basically this: https://mitogen.networkgenomics.com/howitworks.html
89
+
90
+ TODO:
91
+ - log: ta.Optional[logging.Logger] = None + log.debug's
93
92
  """
94
93
 
95
94
 
@@ -100,6 +99,9 @@ Basically this: https://mitogen.networkgenomics.com/howitworks.html
100
99
  class PyremoteBootstrapOptions:
101
100
  debug: bool = False
102
101
 
102
+ DEFAULT_MAIN_NAME_OVERRIDE: ta.ClassVar[str] = '__pyremote__'
103
+ main_name_override: ta.Optional[str] = DEFAULT_MAIN_NAME_OVERRIDE
104
+
103
105
 
104
106
  ##
105
107
 
@@ -416,6 +418,10 @@ def pyremote_bootstrap_finalize() -> PyremotePayloadRuntime:
416
418
  os.dup2(nfd := os.open('/dev/null', os.O_WRONLY), 1)
417
419
  os.close(nfd)
418
420
 
421
+ if (mn := options.main_name_override) is not None:
422
+ # Inspections like typing.get_type_hints need an entry in sys.modules.
423
+ sys.modules[mn] = sys.modules['__main__']
424
+
419
425
  # Write fourth ack
420
426
  output.write(_PYREMOTE_BOOTSTRAP_ACK3)
421
427
 
@@ -434,14 +440,41 @@ def pyremote_bootstrap_finalize() -> PyremotePayloadRuntime:
434
440
 
435
441
 
436
442
  class PyremoteBootstrapDriver:
437
- def __init__(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:
438
448
  super().__init__()
439
449
 
440
450
  self._main_src = main_src
441
- self._main_z = zlib.compress(main_src.encode('utf-8'))
442
-
443
451
  self._options = options
452
+
453
+ self._prepared_main_src = self._prepare_main_src(main_src, options)
454
+ self._main_z = zlib.compress(self._prepared_main_src.encode('utf-8'))
455
+
444
456
  self._options_json = json.dumps(dc.asdict(options), indent=None, separators=(',', ':')).encode('utf-8') # noqa
457
+ #
458
+
459
+ @classmethod
460
+ def _prepare_main_src(
461
+ cls,
462
+ main_src: ta.Union[str, ta.Sequence[str]],
463
+ options: PyremoteBootstrapOptions,
464
+ ) -> str:
465
+ parts: ta.List[str]
466
+ if isinstance(main_src, str):
467
+ parts = [main_src]
468
+ else:
469
+ parts = list(main_src)
470
+
471
+ if (mn := options.main_name_override) is not None:
472
+ parts.insert(0, f'__name__ = {mn!r}')
473
+
474
+ if len(parts) == 1:
475
+ return parts[0]
476
+ else:
477
+ return '\n\n'.join(parts)
445
478
 
446
479
  #
447
480
 
@@ -702,6 +735,99 @@ json_dump_compact: ta.Callable[..., bytes] = functools.partial(json.dump, **JSON
702
735
  json_dumps_compact: ta.Callable[..., str] = functools.partial(json.dumps, **JSON_COMPACT_KWARGS)
703
736
 
704
737
 
738
+ ########################################
739
+ # ../../../omlish/lite/maybes.py
740
+
741
+
742
+ class Maybe(ta.Generic[T]):
743
+ @property
744
+ @abc.abstractmethod
745
+ def present(self) -> bool:
746
+ raise NotImplementedError
747
+
748
+ @abc.abstractmethod
749
+ def must(self) -> T:
750
+ raise NotImplementedError
751
+
752
+ @classmethod
753
+ def just(cls, v: T) -> 'Maybe[T]':
754
+ return tuple.__new__(_Maybe, (v,)) # noqa
755
+
756
+ _empty: ta.ClassVar['Maybe']
757
+
758
+ @classmethod
759
+ def empty(cls) -> 'Maybe[T]':
760
+ return Maybe._empty
761
+
762
+
763
+ class _Maybe(Maybe[T], tuple):
764
+ __slots__ = ()
765
+
766
+ def __init_subclass__(cls, **kwargs):
767
+ raise TypeError
768
+
769
+ @property
770
+ def present(self) -> bool:
771
+ return bool(self)
772
+
773
+ def must(self) -> T:
774
+ if not self:
775
+ raise ValueError
776
+ return self[0]
777
+
778
+
779
+ Maybe._empty = tuple.__new__(_Maybe, ()) # noqa
780
+
781
+
782
+ ########################################
783
+ # ../../../omlish/lite/pycharm.py
784
+
785
+
786
+ DEFAULT_PYCHARM_VERSION = '242.23726.102'
787
+
788
+
789
+ @dc.dataclass(frozen=True)
790
+ class PycharmRemoteDebug:
791
+ port: int
792
+ host: ta.Optional[str] = 'localhost'
793
+ install_version: ta.Optional[str] = DEFAULT_PYCHARM_VERSION
794
+
795
+
796
+ def pycharm_debug_connect(prd: PycharmRemoteDebug) -> None:
797
+ if prd.install_version is not None:
798
+ import subprocess
799
+ import sys
800
+ subprocess.check_call([
801
+ sys.executable,
802
+ '-mpip',
803
+ 'install',
804
+ f'pydevd-pycharm~={prd.install_version}',
805
+ ])
806
+
807
+ pydevd_pycharm = __import__('pydevd_pycharm') # noqa
808
+ pydevd_pycharm.settrace(
809
+ prd.host,
810
+ port=prd.port,
811
+ stdoutToServer=True,
812
+ stderrToServer=True,
813
+ )
814
+
815
+
816
+ def pycharm_debug_preamble(prd: PycharmRemoteDebug) -> str:
817
+ import inspect
818
+ import textwrap
819
+
820
+ return textwrap.dedent(f"""
821
+ {inspect.getsource(pycharm_debug_connect)}
822
+
823
+ pycharm_debug_connect(PycharmRemoteDebug(
824
+ {prd.port!r},
825
+ host={prd.host!r},
826
+ install_version={prd.install_version!r},
827
+ ))
828
+ """)
829
+
830
+
705
831
  ########################################
706
832
  # ../../../omlish/lite/reflect.py
707
833
 
@@ -758,207 +884,1334 @@ def deep_subclasses(cls: ta.Type[T]) -> ta.Iterator[ta.Type[T]]:
758
884
 
759
885
 
760
886
  ########################################
761
- # ../payload.py
887
+ # ../../../omlish/lite/strings.py
762
888
 
763
889
 
764
- @cached_nullary
765
- def _get_self_src() -> str:
766
- return inspect.getsource(sys.modules[__name__])
890
+ ##
767
891
 
768
892
 
769
- def _is_src_amalg(src: str) -> bool:
770
- for l in src.splitlines(): # noqa
771
- if l.startswith('# @omlish-amalg-output '):
772
- return True
773
- 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
774
900
 
775
901
 
776
- @cached_nullary
777
- def _is_self_amalg() -> bool:
778
- 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('_')
779
905
 
780
906
 
781
- def get_payload_src(*, file: ta.Optional[str]) -> str:
782
- if file is not None:
783
- with open(file) as f:
784
- return f.read()
907
+ ##
785
908
 
786
- if _is_self_amalg():
787
- return _get_self_src()
788
909
 
789
- import importlib.resources
790
- 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
+ )
791
917
 
792
918
 
793
- ########################################
794
- # ../../../omlish/lite/logs.py
795
- """
796
- TODO:
797
- - translate json keys
798
- - debug
799
- """
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
+ )
800
926
 
801
927
 
802
- 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)})'
803
933
 
804
934
 
805
935
  ##
806
936
 
807
937
 
808
- class TidLogFilter(logging.Filter):
938
+ FORMAT_NUM_BYTES_SUFFIXES: ta.Sequence[str] = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB']
809
939
 
810
- def filter(self, record):
811
- record.tid = threading.get_native_id()
812
- return True
813
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}'
814
949
 
815
- ##
950
+ return f'{num_bytes / 1024 ** (len(FORMAT_NUM_BYTES_SUFFIXES) - 1):.2f}{FORMAT_NUM_BYTES_SUFFIXES[-1]}'
816
951
 
817
952
 
818
- class JsonLogFormatter(logging.Formatter):
953
+ ########################################
954
+ # ../commands/base.py
819
955
 
820
- KEYS: ta.Mapping[str, bool] = {
821
- 'name': False,
822
- 'msg': False,
823
- 'args': False,
824
- 'levelname': False,
825
- 'levelno': False,
826
- 'pathname': False,
827
- 'filename': False,
828
- 'module': False,
829
- 'exc_info': True,
830
- 'exc_text': True,
831
- 'stack_info': True,
832
- 'lineno': False,
833
- 'funcName': False,
834
- 'created': False,
835
- 'msecs': False,
836
- 'relativeCreated': False,
837
- 'thread': False,
838
- 'threadName': False,
839
- 'processName': False,
840
- 'process': False,
841
- }
842
956
 
843
- def format(self, record: logging.LogRecord) -> str:
844
- dct = {
845
- k: v
846
- for k, o in self.KEYS.items()
847
- for v in [getattr(record, k)]
848
- if not (o and v is None)
849
- }
850
- 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]
851
969
 
852
970
 
853
971
  ##
854
972
 
855
973
 
856
- STANDARD_LOG_FORMAT_PARTS = [
857
- ('asctime', '%(asctime)-15s'),
858
- ('process', 'pid=%(process)-6s'),
859
- ('thread', 'tid=%(thread)x'),
860
- ('levelname', '%(levelname)s'),
861
- ('name', '%(name)s'),
862
- ('separator', '::'),
863
- ('message', '%(message)s'),
864
- ]
974
+ @dc.dataclass(frozen=True)
975
+ class CommandException:
976
+ name: str
977
+ repr: str
865
978
 
979
+ traceback: ta.Optional[str] = None
866
980
 
867
- class StandardLogFormatter(logging.Formatter):
981
+ exc: ta.Optional[ta.Any] = None # Exception
868
982
 
869
- @staticmethod
870
- def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
871
- return ' '.join(v for k, v in parts)
983
+ cmd: ta.Optional[Command] = None
872
984
 
873
- converter = datetime.datetime.fromtimestamp # type: ignore
985
+ @classmethod
986
+ def of(
987
+ cls,
988
+ exc: Exception,
989
+ *,
990
+ omit_exc_object: bool = False,
874
991
 
875
- def formatTime(self, record, datefmt=None):
876
- ct = self.converter(record.created) # type: ignore
877
- if datefmt:
878
- return ct.strftime(datefmt) # noqa
879
- else:
880
- t = ct.strftime('%Y-%m-%d %H:%M:%S')
881
- 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),
882
997
 
998
+ traceback=(
999
+ ''.join(traceback.format_tb(exc.__traceback__))
1000
+ if getattr(exc, '__traceback__', None) is not None else None
1001
+ ),
883
1002
 
884
- ##
1003
+ exc=None if omit_exc_object else exc,
885
1004
 
1005
+ cmd=cmd,
1006
+ )
886
1007
 
887
- class ProxyLogFilterer(logging.Filterer):
888
- def __init__(self, underlying: logging.Filterer) -> None: # noqa
889
- self._underlying = underlying
890
1008
 
1009
+ class CommandOutputOrException(abc.ABC, ta.Generic[CommandOutputT]):
891
1010
  @property
892
- def underlying(self) -> logging.Filterer:
893
- return self._underlying
1011
+ @abc.abstractmethod
1012
+ def output(self) -> ta.Optional[CommandOutputT]:
1013
+ raise NotImplementedError
894
1014
 
895
1015
  @property
896
- def filters(self):
897
- return self._underlying.filters
1016
+ @abc.abstractmethod
1017
+ def exception(self) -> ta.Optional[CommandException]:
1018
+ raise NotImplementedError
898
1019
 
899
- @filters.setter
900
- def filters(self, filters):
901
- self._underlying.filters = filters
902
1020
 
903
- def addFilter(self, filter): # noqa
904
- 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
905
1025
 
906
- def removeFilter(self, filter): # noqa
907
- self._underlying.removeFilter(filter)
908
1026
 
909
- def filter(self, record):
910
- 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
911
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)
912
1041
 
913
- class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
914
- def __init__(self, underlying: logging.Handler) -> None: # noqa
915
- 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))
916
1045
 
917
- _underlying: logging.Handler
1046
+ return CommandOutputOrExceptionData(exception=CommandException.of(
1047
+ e,
1048
+ omit_exc_object=omit_exc_object,
1049
+ cmd=cmd,
1050
+ ))
918
1051
 
919
- @property
920
- def underlying(self) -> logging.Handler:
921
- return self._underlying
1052
+ else:
1053
+ return CommandOutputOrExceptionData(output=o)
922
1054
 
923
- def get_name(self):
924
- return self._underlying.get_name()
925
1055
 
926
- def set_name(self, name):
927
- self._underlying.set_name(name)
1056
+ ##
928
1057
 
929
- @property
930
- def name(self):
931
- return self._underlying.name
932
1058
 
933
- @property
934
- def level(self):
935
- return self._underlying.level
1059
+ @dc.dataclass(frozen=True)
1060
+ class CommandRegistration:
1061
+ command_cls: ta.Type[Command]
936
1062
 
937
- @level.setter
938
- def level(self, level):
939
- self._underlying.level = level
1063
+ name: ta.Optional[str] = None
940
1064
 
941
1065
  @property
942
- def formatter(self):
943
- 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')])
944
1070
 
945
- @formatter.setter
946
- def formatter(self, formatter):
947
- self._underlying.formatter = formatter
948
1071
 
949
- def createLock(self):
950
- self._underlying.createLock()
1072
+ CommandRegistrations = ta.NewType('CommandRegistrations', ta.Sequence[CommandRegistration])
951
1073
 
952
- def acquire(self):
953
- self._underlying.acquire()
954
1074
 
955
- def release(self):
956
- self._underlying.release()
1075
+ ##
957
1076
 
958
- def setLevel(self, level):
959
- self._underlying.setLevel(level)
960
1077
 
961
- def format(self, 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):
962
2215
  return self._underlying.format(record)
963
2216
 
964
2217
  def emit(self, record):
@@ -1072,21 +2325,26 @@ TODO:
1072
2325
  ##
1073
2326
 
1074
2327
 
2328
+ @dc.dataclass(frozen=True)
2329
+ class ObjMarshalOptions:
2330
+ raw_bytes: bool = False
2331
+
2332
+
1075
2333
  class ObjMarshaler(abc.ABC):
1076
2334
  @abc.abstractmethod
1077
- def marshal(self, o: ta.Any) -> ta.Any:
2335
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1078
2336
  raise NotImplementedError
1079
2337
 
1080
2338
  @abc.abstractmethod
1081
- def unmarshal(self, o: ta.Any) -> ta.Any:
2339
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1082
2340
  raise NotImplementedError
1083
2341
 
1084
2342
 
1085
2343
  class NopObjMarshaler(ObjMarshaler):
1086
- def marshal(self, o: ta.Any) -> ta.Any:
2344
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1087
2345
  return o
1088
2346
 
1089
- def unmarshal(self, o: ta.Any) -> ta.Any:
2347
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1090
2348
  return o
1091
2349
 
1092
2350
 
@@ -1094,29 +2352,29 @@ class NopObjMarshaler(ObjMarshaler):
1094
2352
  class ProxyObjMarshaler(ObjMarshaler):
1095
2353
  m: ta.Optional[ObjMarshaler] = None
1096
2354
 
1097
- def marshal(self, o: ta.Any) -> ta.Any:
1098
- return check_not_none(self.m).marshal(o)
2355
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
2356
+ return check_not_none(self.m).marshal(o, opts)
1099
2357
 
1100
- def unmarshal(self, o: ta.Any) -> ta.Any:
1101
- return check_not_none(self.m).unmarshal(o)
2358
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
2359
+ return check_not_none(self.m).unmarshal(o, opts)
1102
2360
 
1103
2361
 
1104
2362
  @dc.dataclass(frozen=True)
1105
2363
  class CastObjMarshaler(ObjMarshaler):
1106
2364
  ty: type
1107
2365
 
1108
- def marshal(self, o: ta.Any) -> ta.Any:
2366
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1109
2367
  return o
1110
2368
 
1111
- def unmarshal(self, o: ta.Any) -> ta.Any:
2369
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1112
2370
  return self.ty(o)
1113
2371
 
1114
2372
 
1115
2373
  class DynamicObjMarshaler(ObjMarshaler):
1116
- def marshal(self, o: ta.Any) -> ta.Any:
2374
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1117
2375
  return marshal_obj(o)
1118
2376
 
1119
- def unmarshal(self, o: ta.Any) -> ta.Any:
2377
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1120
2378
  return o
1121
2379
 
1122
2380
 
@@ -1124,21 +2382,36 @@ class DynamicObjMarshaler(ObjMarshaler):
1124
2382
  class Base64ObjMarshaler(ObjMarshaler):
1125
2383
  ty: type
1126
2384
 
1127
- def marshal(self, o: ta.Any) -> ta.Any:
2385
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1128
2386
  return base64.b64encode(o).decode('ascii')
1129
2387
 
1130
- def unmarshal(self, o: ta.Any) -> ta.Any:
2388
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1131
2389
  return self.ty(base64.b64decode(o))
1132
2390
 
1133
2391
 
2392
+ @dc.dataclass(frozen=True)
2393
+ class BytesSwitchedObjMarshaler(ObjMarshaler):
2394
+ m: ObjMarshaler
2395
+
2396
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
2397
+ if opts.raw_bytes:
2398
+ return o
2399
+ return self.m.marshal(o, opts)
2400
+
2401
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
2402
+ if opts.raw_bytes:
2403
+ return o
2404
+ return self.m.unmarshal(o, opts)
2405
+
2406
+
1134
2407
  @dc.dataclass(frozen=True)
1135
2408
  class EnumObjMarshaler(ObjMarshaler):
1136
2409
  ty: type
1137
2410
 
1138
- def marshal(self, o: ta.Any) -> ta.Any:
2411
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1139
2412
  return o.name
1140
2413
 
1141
- def unmarshal(self, o: ta.Any) -> ta.Any:
2414
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1142
2415
  return self.ty.__members__[o] # type: ignore
1143
2416
 
1144
2417
 
@@ -1146,15 +2419,15 @@ class EnumObjMarshaler(ObjMarshaler):
1146
2419
  class OptionalObjMarshaler(ObjMarshaler):
1147
2420
  item: ObjMarshaler
1148
2421
 
1149
- def marshal(self, o: ta.Any) -> ta.Any:
2422
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1150
2423
  if o is None:
1151
2424
  return None
1152
- return self.item.marshal(o)
2425
+ return self.item.marshal(o, opts)
1153
2426
 
1154
- def unmarshal(self, o: ta.Any) -> ta.Any:
2427
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1155
2428
  if o is None:
1156
2429
  return None
1157
- return self.item.unmarshal(o)
2430
+ return self.item.unmarshal(o, opts)
1158
2431
 
1159
2432
 
1160
2433
  @dc.dataclass(frozen=True)
@@ -1163,11 +2436,11 @@ class MappingObjMarshaler(ObjMarshaler):
1163
2436
  km: ObjMarshaler
1164
2437
  vm: ObjMarshaler
1165
2438
 
1166
- def marshal(self, o: ta.Any) -> ta.Any:
1167
- return {self.km.marshal(k): self.vm.marshal(v) for k, v in o.items()}
2439
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
2440
+ return {self.km.marshal(k, opts): self.vm.marshal(v, opts) for k, v in o.items()}
1168
2441
 
1169
- def unmarshal(self, o: ta.Any) -> ta.Any:
1170
- return self.ty((self.km.unmarshal(k), self.vm.unmarshal(v)) for k, v in o.items())
2442
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
2443
+ return self.ty((self.km.unmarshal(k, opts), self.vm.unmarshal(v, opts)) for k, v in o.items())
1171
2444
 
1172
2445
 
1173
2446
  @dc.dataclass(frozen=True)
@@ -1175,11 +2448,11 @@ class IterableObjMarshaler(ObjMarshaler):
1175
2448
  ty: type
1176
2449
  item: ObjMarshaler
1177
2450
 
1178
- def marshal(self, o: ta.Any) -> ta.Any:
1179
- return [self.item.marshal(e) for e in o]
2451
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
2452
+ return [self.item.marshal(e, opts) for e in o]
1180
2453
 
1181
- def unmarshal(self, o: ta.Any) -> ta.Any:
1182
- return self.ty(self.item.unmarshal(e) for e in o)
2454
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
2455
+ return self.ty(self.item.unmarshal(e, opts) for e in o)
1183
2456
 
1184
2457
 
1185
2458
  @dc.dataclass(frozen=True)
@@ -1188,11 +2461,11 @@ class DataclassObjMarshaler(ObjMarshaler):
1188
2461
  fs: ta.Mapping[str, ObjMarshaler]
1189
2462
  nonstrict: bool = False
1190
2463
 
1191
- def marshal(self, o: ta.Any) -> ta.Any:
1192
- return {k: m.marshal(getattr(o, k)) for k, m in self.fs.items()}
2464
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
2465
+ return {k: m.marshal(getattr(o, k), opts) for k, m in self.fs.items()}
1193
2466
 
1194
- def unmarshal(self, o: ta.Any) -> ta.Any:
1195
- return self.ty(**{k: self.fs[k].unmarshal(v) for k, v in o.items() if not self.nonstrict or k in self.fs})
2467
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
2468
+ return self.ty(**{k: self.fs[k].unmarshal(v, opts) for k, v in o.items() if not self.nonstrict or k in self.fs})
1196
2469
 
1197
2470
 
1198
2471
  @dc.dataclass(frozen=True)
@@ -1212,50 +2485,50 @@ class PolymorphicObjMarshaler(ObjMarshaler):
1212
2485
  {i.tag: i for i in impls},
1213
2486
  )
1214
2487
 
1215
- def marshal(self, o: ta.Any) -> ta.Any:
2488
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1216
2489
  impl = self.impls_by_ty[type(o)]
1217
- return {impl.tag: impl.m.marshal(o)}
2490
+ return {impl.tag: impl.m.marshal(o, opts)}
1218
2491
 
1219
- def unmarshal(self, o: ta.Any) -> ta.Any:
2492
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1220
2493
  [(t, v)] = o.items()
1221
2494
  impl = self.impls_by_tag[t]
1222
- return impl.m.unmarshal(v)
2495
+ return impl.m.unmarshal(v, opts)
1223
2496
 
1224
2497
 
1225
2498
  @dc.dataclass(frozen=True)
1226
2499
  class DatetimeObjMarshaler(ObjMarshaler):
1227
2500
  ty: type
1228
2501
 
1229
- def marshal(self, o: ta.Any) -> ta.Any:
2502
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1230
2503
  return o.isoformat()
1231
2504
 
1232
- def unmarshal(self, o: ta.Any) -> ta.Any:
2505
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1233
2506
  return self.ty.fromisoformat(o) # type: ignore
1234
2507
 
1235
2508
 
1236
2509
  class DecimalObjMarshaler(ObjMarshaler):
1237
- def marshal(self, o: ta.Any) -> ta.Any:
2510
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1238
2511
  return str(check_isinstance(o, decimal.Decimal))
1239
2512
 
1240
- def unmarshal(self, v: ta.Any) -> ta.Any:
2513
+ def unmarshal(self, v: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1241
2514
  return decimal.Decimal(check_isinstance(v, str))
1242
2515
 
1243
2516
 
1244
2517
  class FractionObjMarshaler(ObjMarshaler):
1245
- def marshal(self, o: ta.Any) -> ta.Any:
2518
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1246
2519
  fr = check_isinstance(o, fractions.Fraction)
1247
2520
  return [fr.numerator, fr.denominator]
1248
2521
 
1249
- def unmarshal(self, v: ta.Any) -> ta.Any:
2522
+ def unmarshal(self, v: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1250
2523
  num, denom = check_isinstance(v, list)
1251
2524
  return fractions.Fraction(num, denom)
1252
2525
 
1253
2526
 
1254
2527
  class UuidObjMarshaler(ObjMarshaler):
1255
- def marshal(self, o: ta.Any) -> ta.Any:
2528
+ def marshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1256
2529
  return str(o)
1257
2530
 
1258
- def unmarshal(self, o: ta.Any) -> ta.Any:
2531
+ def unmarshal(self, o: ta.Any, opts: ObjMarshalOptions) -> ta.Any:
1259
2532
  return uuid.UUID(o)
1260
2533
 
1261
2534
 
@@ -1265,7 +2538,7 @@ class UuidObjMarshaler(ObjMarshaler):
1265
2538
  _DEFAULT_OBJ_MARSHALERS: ta.Dict[ta.Any, ObjMarshaler] = {
1266
2539
  **{t: NopObjMarshaler() for t in (type(None),)},
1267
2540
  **{t: CastObjMarshaler(t) for t in (int, float, str, bool)},
1268
- **{t: Base64ObjMarshaler(t) for t in (bytes, bytearray)},
2541
+ **{t: BytesSwitchedObjMarshaler(Base64ObjMarshaler(t)) for t in (bytes, bytearray)},
1269
2542
  **{t: IterableObjMarshaler(t, DynamicObjMarshaler()) for t in (list, tuple, set, frozenset)},
1270
2543
  **{t: MappingObjMarshaler(t, DynamicObjMarshaler(), DynamicObjMarshaler()) for t in (dict,)},
1271
2544
 
@@ -1298,12 +2571,16 @@ class ObjMarshalerManager:
1298
2571
  def __init__(
1299
2572
  self,
1300
2573
  *,
2574
+ default_options: ObjMarshalOptions = ObjMarshalOptions(),
2575
+
1301
2576
  default_obj_marshalers: ta.Dict[ta.Any, ObjMarshaler] = _DEFAULT_OBJ_MARSHALERS, # noqa
1302
2577
  generic_mapping_types: ta.Dict[ta.Any, type] = _OBJ_MARSHALER_GENERIC_MAPPING_TYPES, # noqa
1303
2578
  generic_iterable_types: ta.Dict[ta.Any, type] = _OBJ_MARSHALER_GENERIC_ITERABLE_TYPES, # noqa
1304
2579
  ) -> None:
1305
2580
  super().__init__()
1306
2581
 
2582
+ self._default_options = default_options
2583
+
1307
2584
  self._obj_marshalers = dict(default_obj_marshalers)
1308
2585
  self._generic_mapping_types = generic_mapping_types
1309
2586
  self._generic_iterable_types = generic_iterable_types
@@ -1412,11 +2689,35 @@ class ObjMarshalerManager:
1412
2689
 
1413
2690
  #
1414
2691
 
1415
- def marshal_obj(self, o: ta.Any, ty: ta.Any = None) -> ta.Any:
1416
- return self.get_obj_marshaler(ty if ty is not None else type(o)).marshal(o)
1417
-
1418
- def unmarshal_obj(self, o: ta.Any, ty: ta.Union[ta.Type[T], ta.Any]) -> T:
1419
- return self.get_obj_marshaler(ty).unmarshal(o)
2692
+ def marshal_obj(
2693
+ self,
2694
+ o: ta.Any,
2695
+ ty: ta.Any = None,
2696
+ opts: ta.Optional[ObjMarshalOptions] = None,
2697
+ ) -> ta.Any:
2698
+ m = self.get_obj_marshaler(ty if ty is not None else type(o))
2699
+ return m.marshal(o, opts or self._default_options)
2700
+
2701
+ def unmarshal_obj(
2702
+ self,
2703
+ o: ta.Any,
2704
+ ty: ta.Union[ta.Type[T], ta.Any],
2705
+ opts: ta.Optional[ObjMarshalOptions] = None,
2706
+ ) -> T:
2707
+ m = self.get_obj_marshaler(ty)
2708
+ return m.unmarshal(o, opts or self._default_options)
2709
+
2710
+ def roundtrip_obj(
2711
+ self,
2712
+ o: ta.Any,
2713
+ ty: ta.Any = None,
2714
+ opts: ta.Optional[ObjMarshalOptions] = None,
2715
+ ) -> ta.Any:
2716
+ if ty is None:
2717
+ ty = type(o)
2718
+ m: ta.Any = self.marshal_obj(o, ty, opts)
2719
+ u: ta.Any = self.unmarshal_obj(m, ty, opts)
2720
+ return u
1420
2721
 
1421
2722
 
1422
2723
  ##
@@ -1449,10 +2750,102 @@ def check_runtime_version() -> None:
1449
2750
 
1450
2751
 
1451
2752
  ########################################
1452
- # ../protocol.py
2753
+ # ../bootstrap.py
2754
+
2755
+
2756
+ @dc.dataclass(frozen=True)
2757
+ class MainBootstrap:
2758
+ main_config: MainConfig = MainConfig()
2759
+
2760
+ remote_config: RemoteConfig = RemoteConfig()
2761
+
2762
+
2763
+ ########################################
2764
+ # ../commands/execution.py
2765
+
2766
+
2767
+ CommandExecutorMap = ta.NewType('CommandExecutorMap', ta.Mapping[ta.Type[Command], CommandExecutor])
2768
+
2769
+
2770
+ class CommandExecutionService(CommandExecutor):
2771
+ def __init__(
2772
+ self,
2773
+ *,
2774
+ command_executors: CommandExecutorMap,
2775
+ ) -> None:
2776
+ super().__init__()
2777
+
2778
+ self._command_executors = command_executors
2779
+
2780
+ def execute(self, cmd: Command) -> Command.Output:
2781
+ ce: CommandExecutor = self._command_executors[type(cmd)]
2782
+ return ce.execute(cmd)
2783
+
2784
+
2785
+ ########################################
2786
+ # ../commands/marshal.py
2787
+
2788
+
2789
+ def install_command_marshaling(
2790
+ cmds: CommandNameMap,
2791
+ msh: ObjMarshalerManager,
2792
+ ) -> None:
2793
+ for fn in [
2794
+ lambda c: c,
2795
+ lambda c: c.Output,
2796
+ ]:
2797
+ msh.register_opj_marshaler(
2798
+ fn(Command),
2799
+ PolymorphicObjMarshaler.of([
2800
+ PolymorphicObjMarshaler.Impl(
2801
+ fn(cmd),
2802
+ name,
2803
+ msh.get_obj_marshaler(fn(cmd)),
2804
+ )
2805
+ for name, cmd in cmds.items()
2806
+ ]),
2807
+ )
2808
+
2809
+
2810
+ ########################################
2811
+ # ../deploy/command.py
2812
+
2813
+
2814
+ ##
2815
+
2816
+
2817
+ @dc.dataclass(frozen=True)
2818
+ class DeployCommand(Command['DeployCommand.Output']):
2819
+ @dc.dataclass(frozen=True)
2820
+ class Output(Command.Output):
2821
+ pass
2822
+
2823
+
2824
+ ##
2825
+
2826
+
2827
+ class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]):
2828
+ def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
2829
+ return DeployCommand.Output()
2830
+
2831
+
2832
+ ########################################
2833
+ # ../marshal.py
2834
+
2835
+
2836
+ @dc.dataclass(frozen=True)
2837
+ class ObjMarshalerInstaller:
2838
+ fn: ta.Callable[[ObjMarshalerManager], None]
2839
+
2840
+
2841
+ ObjMarshalerInstallers = ta.NewType('ObjMarshalerInstallers', ta.Sequence[ObjMarshalerInstaller])
2842
+
2843
+
2844
+ ########################################
2845
+ # ../remote/channel.py
1453
2846
 
1454
2847
 
1455
- class Channel:
2848
+ class RemoteChannel:
1456
2849
  def __init__(
1457
2850
  self,
1458
2851
  input: ta.IO, # noqa
@@ -1466,6 +2859,9 @@ class Channel:
1466
2859
  self._output = output
1467
2860
  self._msh = msh
1468
2861
 
2862
+ def set_marshaler(self, msh: ObjMarshalerManager) -> None:
2863
+ self._msh = msh
2864
+
1469
2865
  def send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
1470
2866
  j = json_dumps_compact(self._msh.marshal_obj(o, ty))
1471
2867
  d = j.encode('utf-8')
@@ -1474,7 +2870,7 @@ class Channel:
1474
2870
  self._output.write(d)
1475
2871
  self._output.flush()
1476
2872
 
1477
- def recv_obj(self, ty: ta.Any) -> ta.Any:
2873
+ def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
1478
2874
  d = self._input.read(4)
1479
2875
  if not d:
1480
2876
  return None
@@ -1695,28 +3091,19 @@ class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCom
1695
3091
 
1696
3092
 
1697
3093
  ########################################
1698
- # ../spawning.py
3094
+ # ../remote/spawning.py
1699
3095
 
1700
3096
 
1701
- class PySpawner:
1702
- DEFAULT_PYTHON = 'python3'
3097
+ class RemoteSpawning:
3098
+ @dc.dataclass(frozen=True)
3099
+ class Target:
3100
+ shell: ta.Optional[str] = None
3101
+ shell_quote: bool = False
1703
3102
 
1704
- def __init__(
1705
- self,
1706
- src: str,
1707
- *,
1708
- shell: ta.Optional[str] = None,
1709
- shell_quote: bool = False,
1710
- python: str = DEFAULT_PYTHON,
1711
- stderr: ta.Optional[SubprocessChannelOption] = None,
1712
- ) -> None:
1713
- super().__init__()
3103
+ DEFAULT_PYTHON: ta.ClassVar[str] = 'python3'
3104
+ python: str = DEFAULT_PYTHON
1714
3105
 
1715
- self._src = src
1716
- self._shell = shell
1717
- self._shell_quote = shell_quote
1718
- self._python = python
1719
- self._stderr = stderr
3106
+ stderr: ta.Optional[str] = None # SubprocessChannelOption
1720
3107
 
1721
3108
  #
1722
3109
 
@@ -1724,20 +3111,24 @@ class PySpawner:
1724
3111
  cmd: ta.Sequence[str]
1725
3112
  shell: bool
1726
3113
 
1727
- def _prepare_cmd(self) -> _PreparedCmd:
1728
- if self._shell is not None:
1729
- sh_src = f'{self._python} -c {shlex.quote(self._src)}'
1730
- if self._shell_quote:
3114
+ def _prepare_cmd(
3115
+ self,
3116
+ tgt: Target,
3117
+ src: str,
3118
+ ) -> _PreparedCmd:
3119
+ if tgt.shell is not None:
3120
+ sh_src = f'{tgt.python} -c {shlex.quote(src)}'
3121
+ if tgt.shell_quote:
1731
3122
  sh_src = shlex.quote(sh_src)
1732
- sh_cmd = f'{self._shell} {sh_src}'
1733
- return PySpawner._PreparedCmd(
3123
+ sh_cmd = f'{tgt.shell} {sh_src}'
3124
+ return RemoteSpawning._PreparedCmd(
1734
3125
  cmd=[sh_cmd],
1735
3126
  shell=True,
1736
3127
  )
1737
3128
 
1738
3129
  else:
1739
- return PySpawner._PreparedCmd(
1740
- cmd=[self._python, '-c', self._src],
3130
+ return RemoteSpawning._PreparedCmd(
3131
+ cmd=[tgt.python, '-c', src],
1741
3132
  shell=False,
1742
3133
  )
1743
3134
 
@@ -1752,23 +3143,28 @@ class PySpawner:
1752
3143
  @contextlib.contextmanager
1753
3144
  def spawn(
1754
3145
  self,
3146
+ tgt: Target,
3147
+ src: str,
1755
3148
  *,
1756
3149
  timeout: ta.Optional[float] = None,
1757
3150
  ) -> ta.Generator[Spawned, None, None]:
1758
- pc = self._prepare_cmd()
3151
+ pc = self._prepare_cmd(tgt, src)
1759
3152
 
1760
3153
  with subprocess.Popen(
1761
3154
  subprocess_maybe_shell_wrap_exec(*pc.cmd),
1762
3155
  shell=pc.shell,
1763
3156
  stdin=subprocess.PIPE,
1764
3157
  stdout=subprocess.PIPE,
1765
- stderr=SUBPROCESS_CHANNEL_OPTION_VALUES[self._stderr] if self._stderr is not None else None,
3158
+ stderr=(
3159
+ SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, tgt.stderr)]
3160
+ if tgt.stderr is not None else None
3161
+ ),
1766
3162
  ) as proc:
1767
3163
  stdin = check_not_none(proc.stdin)
1768
3164
  stdout = check_not_none(proc.stdout)
1769
3165
 
1770
3166
  try:
1771
- yield PySpawner.Spawned(
3167
+ yield RemoteSpawning.Spawned(
1772
3168
  stdin=stdin,
1773
3169
  stdout=stdout,
1774
3170
  stderr=proc.stderr,
@@ -1784,59 +3180,371 @@ class PySpawner:
1784
3180
 
1785
3181
 
1786
3182
  ########################################
1787
- # main.py
3183
+ # ../commands/inject.py
1788
3184
 
1789
3185
 
1790
3186
  ##
1791
3187
 
1792
3188
 
1793
- _COMMAND_TYPES = {
1794
- 'subprocess': SubprocessCommand,
1795
- }
3189
+ def bind_command(
3190
+ command_cls: ta.Type[Command],
3191
+ executor_cls: ta.Optional[ta.Type[CommandExecutor]],
3192
+ ) -> InjectorBindings:
3193
+ lst: ta.List[InjectorBindingOrBindings] = [
3194
+ inj.bind(CommandRegistration(command_cls), array=True),
3195
+ ]
3196
+
3197
+ if executor_cls is not None:
3198
+ lst.extend([
3199
+ inj.bind(executor_cls, singleton=True),
3200
+ inj.bind(CommandExecutorRegistration(command_cls, executor_cls), array=True),
3201
+ ])
3202
+
3203
+ return inj.as_bindings(*lst)
1796
3204
 
1797
3205
 
1798
3206
  ##
1799
3207
 
1800
3208
 
1801
- def register_command_marshaling(msh: ObjMarshalerManager) -> None:
1802
- for fn in [
1803
- lambda c: c,
1804
- lambda c: c.Output,
3209
+ @dc.dataclass(frozen=True)
3210
+ class _FactoryCommandExecutor(CommandExecutor):
3211
+ factory: ta.Callable[[], CommandExecutor]
3212
+
3213
+ def execute(self, i: Command) -> Command.Output:
3214
+ return self.factory().execute(i)
3215
+
3216
+
3217
+ ##
3218
+
3219
+
3220
+ def bind_commands(
3221
+ *,
3222
+ main_config: MainConfig,
3223
+ ) -> InjectorBindings:
3224
+ lst: ta.List[InjectorBindingOrBindings] = [
3225
+ inj.bind_array(CommandRegistration),
3226
+ inj.bind_array_type(CommandRegistration, CommandRegistrations),
3227
+
3228
+ inj.bind_array(CommandExecutorRegistration),
3229
+ inj.bind_array_type(CommandExecutorRegistration, CommandExecutorRegistrations),
3230
+
3231
+ inj.bind(build_command_name_map, singleton=True),
3232
+ ]
3233
+
3234
+ #
3235
+
3236
+ def provide_obj_marshaler_installer(cmds: CommandNameMap) -> ObjMarshalerInstaller:
3237
+ return ObjMarshalerInstaller(functools.partial(install_command_marshaling, cmds))
3238
+
3239
+ lst.append(inj.bind(provide_obj_marshaler_installer, array=True))
3240
+
3241
+ #
3242
+
3243
+ def provide_command_executor_map(
3244
+ injector: Injector,
3245
+ crs: CommandExecutorRegistrations,
3246
+ ) -> CommandExecutorMap:
3247
+ dct: ta.Dict[ta.Type[Command], CommandExecutor] = {}
3248
+
3249
+ cr: CommandExecutorRegistration
3250
+ for cr in crs:
3251
+ if cr.command_cls in dct:
3252
+ raise KeyError(cr.command_cls)
3253
+
3254
+ factory = functools.partial(injector.provide, cr.executor_cls)
3255
+ if main_config.debug:
3256
+ ce = factory()
3257
+ else:
3258
+ ce = _FactoryCommandExecutor(factory)
3259
+
3260
+ dct[cr.command_cls] = ce
3261
+
3262
+ return CommandExecutorMap(dct)
3263
+
3264
+ lst.extend([
3265
+ inj.bind(provide_command_executor_map, singleton=True),
3266
+
3267
+ inj.bind(CommandExecutionService, singleton=True, eager=main_config.debug),
3268
+ inj.bind(CommandExecutor, to_key=CommandExecutionService),
3269
+ ])
3270
+
3271
+ #
3272
+
3273
+ for command_cls, executor_cls in [
3274
+ (SubprocessCommand, SubprocessCommandExecutor),
1805
3275
  ]:
1806
- msh.register_opj_marshaler(
1807
- fn(Command),
1808
- PolymorphicObjMarshaler.of([
1809
- PolymorphicObjMarshaler.Impl(
1810
- fn(cty),
1811
- k,
1812
- msh.get_obj_marshaler(fn(cty)),
1813
- )
1814
- for k, cty in _COMMAND_TYPES.items()
1815
- ]),
1816
- )
3276
+ lst.append(bind_command(command_cls, executor_cls))
3277
+
3278
+ #
3279
+
3280
+ return inj.as_bindings(*lst)
1817
3281
 
1818
3282
 
1819
- register_command_marshaling(OBJ_MARSHALER_MANAGER)
3283
+ ########################################
3284
+ # ../remote/execution.py
1820
3285
 
1821
3286
 
1822
3287
  ##
1823
3288
 
1824
3289
 
1825
- def _remote_main() -> None:
3290
+ def _remote_execution_main() -> None:
1826
3291
  rt = pyremote_bootstrap_finalize() # noqa
1827
- chan = Channel(rt.input, rt.output)
3292
+
3293
+ chan = RemoteChannel(
3294
+ rt.input,
3295
+ rt.output,
3296
+ )
3297
+
3298
+ bs = check_not_none(chan.recv_obj(MainBootstrap))
3299
+
3300
+ if (prd := bs.remote_config.pycharm_remote_debug) is not None:
3301
+ pycharm_debug_connect(prd)
3302
+
3303
+ injector = main_bootstrap(bs)
3304
+
3305
+ chan.set_marshaler(injector[ObjMarshalerManager])
3306
+
3307
+ ce = injector[CommandExecutor]
1828
3308
 
1829
3309
  while True:
1830
3310
  i = chan.recv_obj(Command)
1831
3311
  if i is None:
1832
3312
  break
1833
3313
 
1834
- if isinstance(i, SubprocessCommand):
1835
- o = SubprocessCommandExecutor().execute(i) # noqa
3314
+ r = ce.try_execute(
3315
+ i,
3316
+ log=log,
3317
+ omit_exc_object=True,
3318
+ )
3319
+
3320
+ chan.send_obj(r)
3321
+
3322
+
3323
+ ##
3324
+
3325
+
3326
+ @dc.dataclass()
3327
+ class RemoteCommandError(Exception):
3328
+ e: CommandException
3329
+
3330
+
3331
+ class RemoteCommandExecutor(CommandExecutor):
3332
+ def __init__(self, chan: RemoteChannel) -> None:
3333
+ super().__init__()
3334
+
3335
+ self._chan = chan
3336
+
3337
+ def _remote_execute(self, cmd: Command) -> CommandOutputOrException:
3338
+ self._chan.send_obj(cmd, Command)
3339
+
3340
+ if (r := self._chan.recv_obj(CommandOutputOrExceptionData)) is None:
3341
+ raise EOFError
3342
+
3343
+ return r
3344
+
3345
+ # @ta.override
3346
+ def execute(self, cmd: Command) -> Command.Output:
3347
+ r = self._remote_execute(cmd)
3348
+ if (e := r.exception) is not None:
3349
+ raise RemoteCommandError(e)
3350
+ else:
3351
+ return check_not_none(r.output)
3352
+
3353
+ # @ta.override
3354
+ def try_execute(
3355
+ self,
3356
+ cmd: Command,
3357
+ *,
3358
+ log: ta.Optional[logging.Logger] = None,
3359
+ omit_exc_object: bool = False,
3360
+ ) -> CommandOutputOrException:
3361
+ try:
3362
+ r = self._remote_execute(cmd)
3363
+
3364
+ except Exception as e: # noqa
3365
+ if log is not None:
3366
+ log.exception('Exception executing remote command: %r', type(cmd))
3367
+
3368
+ return CommandOutputOrExceptionData(exception=CommandException.of(
3369
+ e,
3370
+ omit_exc_object=omit_exc_object,
3371
+ cmd=cmd,
3372
+ ))
3373
+
1836
3374
  else:
1837
- raise TypeError(i)
3375
+ return r
3376
+
3377
+
3378
+ ##
3379
+
3380
+
3381
+ class RemoteExecution:
3382
+ def __init__(
3383
+ self,
3384
+ *,
3385
+ spawning: RemoteSpawning,
3386
+ msh: ObjMarshalerManager,
3387
+ payload_file: ta.Optional[RemoteExecutionPayloadFile] = None,
3388
+ ) -> None:
3389
+ super().__init__()
3390
+
3391
+ self._spawning = spawning
3392
+ self._msh = msh
3393
+ self._payload_file = payload_file
3394
+
3395
+ #
3396
+
3397
+ @cached_nullary
3398
+ def _payload_src(self) -> str:
3399
+ return get_remote_payload_src(file=self._payload_file)
3400
+
3401
+ @cached_nullary
3402
+ def _remote_src(self) -> ta.Sequence[str]:
3403
+ return [
3404
+ self._payload_src(),
3405
+ '_remote_execution_main()',
3406
+ ]
3407
+
3408
+ @cached_nullary
3409
+ def _spawn_src(self) -> str:
3410
+ return pyremote_build_bootstrap_cmd(__package__ or 'manage')
3411
+
3412
+ #
3413
+
3414
+ @contextlib.contextmanager
3415
+ def connect(
3416
+ self,
3417
+ tgt: RemoteSpawning.Target,
3418
+ bs: MainBootstrap,
3419
+ ) -> ta.Generator[RemoteCommandExecutor, None, None]:
3420
+ spawn_src = self._spawn_src()
3421
+ remote_src = self._remote_src()
3422
+
3423
+ with self._spawning.spawn(
3424
+ tgt,
3425
+ spawn_src,
3426
+ ) as proc:
3427
+ res = PyremoteBootstrapDriver( # noqa
3428
+ remote_src,
3429
+ PyremoteBootstrapOptions(
3430
+ debug=bs.main_config.debug,
3431
+ ),
3432
+ ).run(
3433
+ proc.stdout,
3434
+ proc.stdin,
3435
+ )
3436
+
3437
+ chan = RemoteChannel(
3438
+ proc.stdout,
3439
+ proc.stdin,
3440
+ msh=self._msh,
3441
+ )
3442
+
3443
+ chan.send_obj(bs)
3444
+
3445
+ yield RemoteCommandExecutor(chan)
3446
+
3447
+
3448
+ ########################################
3449
+ # ../deploy/inject.py
1838
3450
 
1839
- chan.send_obj(o, Command.Output)
3451
+
3452
+ def bind_deploy(
3453
+ ) -> InjectorBindings:
3454
+ lst: ta.List[InjectorBindingOrBindings] = [
3455
+ bind_command(DeployCommand, DeployCommandExecutor),
3456
+ ]
3457
+
3458
+ return inj.as_bindings(*lst)
3459
+
3460
+
3461
+ ########################################
3462
+ # ../remote/inject.py
3463
+
3464
+
3465
+ def bind_remote(
3466
+ *,
3467
+ remote_config: RemoteConfig,
3468
+ ) -> InjectorBindings:
3469
+ lst: ta.List[InjectorBindingOrBindings] = [
3470
+ inj.bind(remote_config),
3471
+
3472
+ inj.bind(RemoteSpawning, singleton=True),
3473
+
3474
+ inj.bind(RemoteExecution, singleton=True),
3475
+ ]
3476
+
3477
+ if (pf := remote_config.payload_file) is not None:
3478
+ lst.append(inj.bind(pf, to_key=RemoteExecutionPayloadFile))
3479
+
3480
+ return inj.as_bindings(*lst)
3481
+
3482
+
3483
+ ########################################
3484
+ # ../inject.py
3485
+
3486
+
3487
+ ##
3488
+
3489
+
3490
+ def bind_main(
3491
+ *,
3492
+ main_config: MainConfig,
3493
+ remote_config: RemoteConfig,
3494
+ ) -> InjectorBindings:
3495
+ lst: ta.List[InjectorBindingOrBindings] = [
3496
+ inj.bind(main_config),
3497
+
3498
+ bind_commands(
3499
+ main_config=main_config,
3500
+ ),
3501
+
3502
+ bind_remote(
3503
+ remote_config=remote_config,
3504
+ ),
3505
+
3506
+ bind_deploy(),
3507
+ ]
3508
+
3509
+ #
3510
+
3511
+ def build_obj_marshaler_manager(insts: ObjMarshalerInstallers) -> ObjMarshalerManager:
3512
+ msh = ObjMarshalerManager()
3513
+ inst: ObjMarshalerInstaller
3514
+ for inst in insts:
3515
+ inst.fn(msh)
3516
+ return msh
3517
+
3518
+ lst.extend([
3519
+ inj.bind(build_obj_marshaler_manager, singleton=True),
3520
+
3521
+ inj.bind_array(ObjMarshalerInstaller),
3522
+ inj.bind_array_type(ObjMarshalerInstaller, ObjMarshalerInstallers),
3523
+ ])
3524
+
3525
+ #
3526
+
3527
+ return inj.as_bindings(*lst)
3528
+
3529
+
3530
+ ########################################
3531
+ # ../bootstrap_.py
3532
+
3533
+
3534
+ def main_bootstrap(bs: MainBootstrap) -> Injector:
3535
+ if (log_level := bs.main_config.log_level) is not None:
3536
+ configure_standard_logging(log_level)
3537
+
3538
+ injector = inj.create_injector(bind_main( # noqa
3539
+ main_config=bs.main_config,
3540
+ remote_config=bs.remote_config,
3541
+ ))
3542
+
3543
+ return injector
3544
+
3545
+
3546
+ ########################################
3547
+ # main.py
1840
3548
 
1841
3549
 
1842
3550
  ##
@@ -1853,50 +3561,72 @@ def _main() -> None:
1853
3561
  parser.add_argument('-q', '--shell-quote', action='store_true')
1854
3562
  parser.add_argument('--python', default='python3')
1855
3563
 
3564
+ parser.add_argument('--pycharm-debug-port', type=int)
3565
+ parser.add_argument('--pycharm-debug-host')
3566
+ parser.add_argument('--pycharm-debug-version')
3567
+
1856
3568
  parser.add_argument('--debug', action='store_true')
1857
3569
 
3570
+ parser.add_argument('--local', action='store_true')
3571
+
3572
+ parser.add_argument('command', nargs='+')
3573
+
1858
3574
  args = parser.parse_args()
1859
3575
 
1860
3576
  #
1861
3577
 
1862
- payload_src = get_payload_src(file=args._payload_file) # noqa
3578
+ bs = MainBootstrap(
3579
+ main_config=MainConfig(
3580
+ log_level='DEBUG' if args.debug else 'INFO',
1863
3581
 
1864
- remote_src = '\n\n'.join([
1865
- '__name__ = "__remote__"',
1866
- payload_src,
1867
- '_remote_main()',
1868
- ])
3582
+ debug=bool(args.debug),
3583
+ ),
1869
3584
 
1870
- #
3585
+ remote_config=RemoteConfig(
3586
+ payload_file=args._payload_file, # noqa
1871
3587
 
1872
- spawner = PySpawner(
1873
- pyremote_build_bootstrap_cmd(__package__ or 'manage'),
1874
- shell=args.shell,
1875
- shell_quote=args.shell_quote,
1876
- python=args.python,
3588
+ pycharm_remote_debug=PycharmRemoteDebug(
3589
+ port=args.pycharm_debug_port,
3590
+ host=args.pycharm_debug_host,
3591
+ install_version=args.pycharm_debug_version,
3592
+ ) if args.pycharm_debug_port is not None else None,
3593
+ ),
1877
3594
  )
1878
3595
 
1879
- with spawner.spawn() as proc:
1880
- res = PyremoteBootstrapDriver( # noqa
1881
- remote_src,
1882
- PyremoteBootstrapOptions(
1883
- debug=args.debug,
1884
- ),
1885
- ).run(proc.stdout, proc.stdin)
3596
+ injector = main_bootstrap(
3597
+ bs,
3598
+ )
1886
3599
 
1887
- chan = Channel(proc.stdout, proc.stdin)
3600
+ #
1888
3601
 
1889
- #
3602
+ cmds: ta.List[Command] = []
3603
+ for c in args.command:
3604
+ if c == 'deploy':
3605
+ cmds.append(DeployCommand())
3606
+ else:
3607
+ cmds.append(SubprocessCommand([c]))
3608
+
3609
+ #
3610
+
3611
+ with contextlib.ExitStack() as es:
3612
+ ce: CommandExecutor
3613
+
3614
+ if args.local:
3615
+ ce = injector[CommandExecutor]
3616
+
3617
+ else:
3618
+ tgt = RemoteSpawning.Target(
3619
+ shell=args.shell,
3620
+ shell_quote=args.shell_quote,
3621
+ python=args.python,
3622
+ )
1890
3623
 
1891
- for ci in [
1892
- SubprocessCommand(['python3', '-'], input=b'print(1)\n'),
1893
- SubprocessCommand(['uname']),
1894
- ]:
1895
- chan.send_obj(ci, Command)
3624
+ ce = es.enter_context(injector[RemoteExecution].connect(tgt, bs)) # noqa
1896
3625
 
1897
- o = chan.recv_obj(Command.Output)
3626
+ for cmd in cmds:
3627
+ r = ce.try_execute(cmd)
1898
3628
 
1899
- print(o)
3629
+ print(injector[ObjMarshalerManager].marshal_obj(r, opts=ObjMarshalOptions(raw_bytes=True)))
1900
3630
 
1901
3631
 
1902
3632
  if __name__ == '__main__':