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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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__':