omdev 0.0.0.dev426__py3-none-any.whl → 0.0.0.dev428__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.
@@ -5,7 +5,7 @@
5
5
  # @omlish-generated
6
6
  # @omlish-amalg-output ../pyproject/cli.py
7
7
  # @omlish-git-diff-omit
8
- # ruff: noqa: N802 TC003 UP006 UP007 UP036 UP043 UP045
8
+ # ruff: noqa: N802 TC003 UP006 UP007 UP036 UP043 UP045 UP046
9
9
  """
10
10
  TODO:
11
11
  - check / tests, src dir sets
@@ -65,6 +65,7 @@ import tarfile
65
65
  import tempfile
66
66
  import threading
67
67
  import time
68
+ import traceback
68
69
  import types
69
70
  import typing as ta
70
71
  import uuid
@@ -112,6 +113,9 @@ A0 = ta.TypeVar('A0')
112
113
  A1 = ta.TypeVar('A1')
113
114
  A2 = ta.TypeVar('A2')
114
115
 
116
+ # ../../omlish/logs/levels.py
117
+ LogLevel = int # ta.TypeAlias
118
+
115
119
  # ../packaging/specifiers.py
116
120
  UnparsedVersion = ta.Union['Version', str]
117
121
  UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
@@ -135,6 +139,14 @@ InjectorProviderFn = ta.Callable[['Injector'], ta.Any]
135
139
  InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
136
140
  InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
137
141
 
142
+ # ../../omlish/logs/contexts.py
143
+ LoggingExcInfoTuple = ta.Tuple[ta.Type[BaseException], BaseException, ta.Optional[types.TracebackType]] # ta.TypeAlias
144
+ LoggingExcInfo = ta.Union[BaseException, LoggingExcInfoTuple] # ta.TypeAlias
145
+ LoggingExcInfoArg = ta.Union[LoggingExcInfo, bool, None] # ta.TypeAlias
146
+
147
+ # ../../omlish/logs/base.py
148
+ LoggingMsgFn = ta.Callable[[], ta.Union[str, tuple]] # ta.TypeAlias
149
+
138
150
  # ../../omlish/subprocesses/base.py
139
151
  SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
140
152
 
@@ -2948,14 +2960,188 @@ def typing_annotations_attr() -> str:
2948
2960
 
2949
2961
 
2950
2962
  ########################################
2951
- # ../../../omlish/logs/modules.py
2963
+ # ../../../omlish/logs/infos.py
2964
+
2965
+
2966
+ ##
2967
+
2968
+
2969
+ class _LoggingContextInfo:
2970
+ def __mro_entries__(self, bases):
2971
+ return ()
2972
+
2973
+
2974
+ LoggingContextInfo: type = ta.cast(ta.Any, _LoggingContextInfo())
2975
+
2976
+
2977
+ ##
2978
+
2979
+
2980
+ @ta.final
2981
+ class LoggingSourceFileInfo(LoggingContextInfo, ta.NamedTuple): # type: ignore[misc]
2982
+ file_name: str
2983
+ module: str
2984
+
2985
+ @classmethod
2986
+ def build(cls, file_path: ta.Optional[str]) -> ta.Optional['LoggingSourceFileInfo']:
2987
+ if file_path is None:
2988
+ return None
2989
+
2990
+ # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L331-L336 # noqa
2991
+ try:
2992
+ file_name = os.path.basename(file_path)
2993
+ module = os.path.splitext(file_name)[0]
2994
+ except (TypeError, ValueError, AttributeError):
2995
+ return None
2996
+
2997
+ return cls(
2998
+ file_name,
2999
+ module,
3000
+ )
3001
+
3002
+
3003
+ ##
3004
+
3005
+
3006
+ @ta.final
3007
+ class LoggingThreadInfo(LoggingContextInfo, ta.NamedTuple): # type: ignore[misc]
3008
+ ident: int
3009
+ native_id: ta.Optional[int]
3010
+ name: str
3011
+
3012
+ @classmethod
3013
+ def build(cls) -> 'LoggingThreadInfo':
3014
+ return cls(
3015
+ threading.get_ident(),
3016
+ threading.get_native_id() if hasattr(threading, 'get_native_id') else None,
3017
+ threading.current_thread().name,
3018
+ )
3019
+
3020
+
3021
+ ##
3022
+
3023
+
3024
+ @ta.final
3025
+ class LoggingProcessInfo(LoggingContextInfo, ta.NamedTuple): # type: ignore[misc]
3026
+ pid: int
3027
+
3028
+ @classmethod
3029
+ def build(cls) -> 'LoggingProcessInfo':
3030
+ return cls(
3031
+ os.getpid(),
3032
+ )
3033
+
3034
+
3035
+ ##
3036
+
3037
+
3038
+ @ta.final
3039
+ class LoggingMultiprocessingInfo(LoggingContextInfo, ta.NamedTuple): # type: ignore[misc]
3040
+ process_name: str
3041
+
3042
+ @classmethod
3043
+ def build(cls) -> ta.Optional['LoggingMultiprocessingInfo']:
3044
+ # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L355-L364 # noqa
3045
+ if (mp := sys.modules.get('multiprocessing')) is None:
3046
+ return None
3047
+
3048
+ return cls(
3049
+ mp.current_process().name,
3050
+ )
3051
+
3052
+
3053
+ ##
3054
+
3055
+
3056
+ @ta.final
3057
+ class LoggingAsyncioTaskInfo(LoggingContextInfo, ta.NamedTuple): # type: ignore[misc]
3058
+ name: str
3059
+
3060
+ @classmethod
3061
+ def build(cls) -> ta.Optional['LoggingAsyncioTaskInfo']:
3062
+ # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L372-L377 # noqa
3063
+ if (asyncio := sys.modules.get('asyncio')) is None:
3064
+ return None
3065
+
3066
+ try:
3067
+ task = asyncio.current_task()
3068
+ except Exception: # noqa
3069
+ return None
3070
+
3071
+ if task is None:
3072
+ return None
3073
+
3074
+ return cls(
3075
+ task.get_name(), # Always non-None
3076
+ )
3077
+
3078
+
3079
+ ########################################
3080
+ # ../../../omlish/logs/levels.py
2952
3081
 
2953
3082
 
2954
3083
  ##
2955
3084
 
2956
3085
 
2957
- def get_module_logger(mod_globals: ta.Mapping[str, ta.Any]) -> logging.Logger:
2958
- return logging.getLogger(mod_globals.get('__name__'))
3086
+ @ta.final
3087
+ class NamedLogLevel(int):
3088
+ # logging.getLevelNamesMapping (or, as that is unavailable <3.11, logging._nameToLevel) includes the deprecated
3089
+ # aliases.
3090
+ _NAMES_BY_INT: ta.ClassVar[ta.Mapping[LogLevel, str]] = dict(sorted(logging._levelToName.items(), key=lambda t: -t[0])) # noqa
3091
+
3092
+ _INTS_BY_NAME: ta.ClassVar[ta.Mapping[str, LogLevel]] = {v: k for k, v in _NAMES_BY_INT.items()}
3093
+
3094
+ _NAME_INT_PAIRS: ta.ClassVar[ta.Sequence[ta.Tuple[str, LogLevel]]] = list(_INTS_BY_NAME.items())
3095
+
3096
+ #
3097
+
3098
+ @property
3099
+ def exact_name(self) -> ta.Optional[str]:
3100
+ return self._NAMES_BY_INT.get(self)
3101
+
3102
+ _effective_name: ta.Optional[str]
3103
+
3104
+ @property
3105
+ def effective_name(self) -> ta.Optional[str]:
3106
+ try:
3107
+ return self._effective_name
3108
+ except AttributeError:
3109
+ pass
3110
+
3111
+ if (n := self.exact_name) is None:
3112
+ for n, i in self._NAME_INT_PAIRS: # noqa
3113
+ if self >= i:
3114
+ break
3115
+ else:
3116
+ n = None
3117
+
3118
+ self._effective_name = n
3119
+ return n
3120
+
3121
+ #
3122
+
3123
+ def __repr__(self) -> str:
3124
+ return f'{self.__class__.__name__}({int(self)})'
3125
+
3126
+ def __str__(self) -> str:
3127
+ return self.exact_name or f'{self.effective_name or "INVALID"}:{int(self)}'
3128
+
3129
+ #
3130
+
3131
+ CRITICAL: ta.ClassVar['NamedLogLevel']
3132
+ ERROR: ta.ClassVar['NamedLogLevel']
3133
+ WARNING: ta.ClassVar['NamedLogLevel']
3134
+ INFO: ta.ClassVar['NamedLogLevel']
3135
+ DEBUG: ta.ClassVar['NamedLogLevel']
3136
+ NOTSET: ta.ClassVar['NamedLogLevel']
3137
+
3138
+
3139
+ NamedLogLevel.CRITICAL = NamedLogLevel(logging.CRITICAL)
3140
+ NamedLogLevel.ERROR = NamedLogLevel(logging.ERROR)
3141
+ NamedLogLevel.WARNING = NamedLogLevel(logging.WARNING)
3142
+ NamedLogLevel.INFO = NamedLogLevel(logging.INFO)
3143
+ NamedLogLevel.DEBUG = NamedLogLevel(logging.DEBUG)
3144
+ NamedLogLevel.NOTSET = NamedLogLevel(logging.NOTSET)
2959
3145
 
2960
3146
 
2961
3147
  ########################################
@@ -3078,6 +3264,17 @@ class ProxyLoggingHandler(ProxyLoggingFilterer, logging.Handler):
3078
3264
  self._underlying.handleError(record)
3079
3265
 
3080
3266
 
3267
+ ########################################
3268
+ # ../../../omlish/logs/warnings.py
3269
+
3270
+
3271
+ ##
3272
+
3273
+
3274
+ class LoggingSetupWarning(Warning):
3275
+ pass
3276
+
3277
+
3081
3278
  ########################################
3082
3279
  # ../../cexts/magic.py
3083
3280
 
@@ -3832,90 +4029,6 @@ class SpecifierSet(BaseSpecifier):
3832
4029
  return iter(filtered)
3833
4030
 
3834
4031
 
3835
- ########################################
3836
- # ../reqs.py
3837
- """
3838
- TODO:
3839
- - embed pip._internal.req.parse_requirements, add additional env stuff? breaks compat with raw pip
3840
- """
3841
-
3842
-
3843
- log = get_module_logger(globals()) # noqa
3844
-
3845
-
3846
- ##
3847
-
3848
-
3849
- class RequirementsRewriter:
3850
- def __init__(
3851
- self,
3852
- venv: ta.Optional[str] = None,
3853
- ) -> None:
3854
- super().__init__()
3855
-
3856
- self._venv = venv
3857
-
3858
- @cached_nullary
3859
- def _tmp_dir(self) -> str:
3860
- return tempfile.mkdtemp('-omlish-reqs')
3861
-
3862
- VENV_MAGIC = '# @omlish-venv'
3863
-
3864
- def rewrite_file(self, in_file: str) -> str:
3865
- with open(in_file) as f:
3866
- src = f.read()
3867
-
3868
- in_lines = src.splitlines(keepends=True)
3869
- out_lines = []
3870
-
3871
- for l in in_lines:
3872
- if self.VENV_MAGIC in l:
3873
- lp, _, rp = l.partition(self.VENV_MAGIC)
3874
- rp = rp.partition('#')[0]
3875
- omit = False
3876
- for v in rp.split():
3877
- if v[0] == '!':
3878
- if self._venv is not None and self._venv == v[1:]:
3879
- omit = True
3880
- break
3881
- else:
3882
- raise NotImplementedError
3883
-
3884
- if omit:
3885
- out_lines.append('# OMITTED: ' + l)
3886
- continue
3887
-
3888
- out_req = self.rewrite(l.rstrip('\n'), for_file=True)
3889
- out_lines.append(out_req + '\n')
3890
-
3891
- out_file = os.path.join(self._tmp_dir(), os.path.basename(in_file))
3892
- if os.path.exists(out_file):
3893
- raise Exception(f'file exists: {out_file}')
3894
-
3895
- with open(out_file, 'w') as f:
3896
- f.write(''.join(out_lines))
3897
- log.info('Rewrote requirements file %s to %s', in_file, out_file)
3898
- return out_file
3899
-
3900
- def rewrite(self, in_req: str, *, for_file: bool = False) -> str:
3901
- if in_req.strip().startswith('-r'):
3902
- l = in_req.strip()
3903
- lp, _, rp = l.partition(' ')
3904
- if lp == '-r':
3905
- inc_in_file, _, rest = rp.partition(' ')
3906
- else:
3907
- inc_in_file, rest = lp[2:], rp
3908
-
3909
- inc_out_file = self.rewrite_file(inc_in_file)
3910
- if for_file:
3911
- return ' '.join(['-r ', inc_out_file, rest])
3912
- else:
3913
- return '-r' + inc_out_file
3914
-
3915
- else:
3916
- return in_req
3917
-
3918
-
3919
4032
  ########################################
3920
4033
  # ../../../omlish/argparse/cli.py
3921
4034
  """
@@ -5412,6 +5525,104 @@ class PredicateTimeout(Timeout):
5412
5525
  return self()
5413
5526
 
5414
5527
 
5528
+ ########################################
5529
+ # ../../../omlish/logs/callers.py
5530
+
5531
+
5532
+ ##
5533
+
5534
+
5535
+ class LoggingCaller(LoggingContextInfo, ta.NamedTuple): # type: ignore[misc]
5536
+ file_path: str
5537
+ line_no: int
5538
+ name: str
5539
+ stack_info: ta.Optional[str]
5540
+
5541
+ @classmethod
5542
+ def is_internal_frame(cls, frame: types.FrameType) -> bool:
5543
+ file_path = os.path.normcase(frame.f_code.co_filename)
5544
+
5545
+ # Yes, really.
5546
+ # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L204
5547
+ # https://github.com/python/cpython/commit/5ca6d7469be53960843df39bb900e9c3359f127f
5548
+ if 'importlib' in file_path and '_bootstrap' in file_path:
5549
+ return True
5550
+
5551
+ return False
5552
+
5553
+ @classmethod
5554
+ def find_frame(cls, ofs: int = 0) -> ta.Optional[types.FrameType]:
5555
+ f: ta.Optional[types.FrameType] = sys._getframe(2 + ofs) # noqa
5556
+
5557
+ while f is not None:
5558
+ # NOTE: We don't check __file__ like stdlib since we may be running amalgamated - we rely on careful, manual
5559
+ # stack_offset management.
5560
+ if hasattr(f, 'f_code'):
5561
+ return f
5562
+
5563
+ f = f.f_back
5564
+
5565
+ return None
5566
+
5567
+ @classmethod
5568
+ def find(
5569
+ cls,
5570
+ ofs: int = 0,
5571
+ *,
5572
+ stack_info: bool = False,
5573
+ ) -> ta.Optional['LoggingCaller']:
5574
+ if (f := cls.find_frame(ofs + 1)) is None:
5575
+ return None
5576
+
5577
+ # https://github.com/python/cpython/blob/08e9794517063c8cd92c48714071b1d3c60b71bd/Lib/logging/__init__.py#L1616-L1623 # noqa
5578
+ sinfo = None
5579
+ if stack_info:
5580
+ sio = io.StringIO()
5581
+ traceback.print_stack(f, file=sio)
5582
+ sinfo = sio.getvalue()
5583
+ sio.close()
5584
+ if sinfo[-1] == '\n':
5585
+ sinfo = sinfo[:-1]
5586
+
5587
+ return cls(
5588
+ f.f_code.co_filename,
5589
+ f.f_lineno or 0,
5590
+ f.f_code.co_name,
5591
+ sinfo,
5592
+ )
5593
+
5594
+
5595
+ ########################################
5596
+ # ../../../omlish/logs/protocols.py
5597
+
5598
+
5599
+ ##
5600
+
5601
+
5602
+ class LoggerLike(ta.Protocol):
5603
+ """Satisfied by both our Logger and stdlib logging.Logger."""
5604
+
5605
+ def isEnabledFor(self, level: LogLevel) -> bool: ... # noqa
5606
+
5607
+ def getEffectiveLevel(self) -> LogLevel: ... # noqa
5608
+
5609
+ #
5610
+
5611
+ def log(self, level: LogLevel, msg: str, /, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
5612
+
5613
+ def debug(self, msg: str, /, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
5614
+
5615
+ def info(self, msg: str, /, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
5616
+
5617
+ def warning(self, msg: str, /, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
5618
+
5619
+ def error(self, msg: str, /, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
5620
+
5621
+ def exception(self, msg: str, /, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
5622
+
5623
+ def critical(self, msg: str, /, *args: ta.Any, **kwargs: ta.Any) -> None: ... # noqa
5624
+
5625
+
5415
5626
  ########################################
5416
5627
  # ../../../omlish/logs/std/json.py
5417
5628
  """
@@ -5470,17 +5681,100 @@ class JsonLoggingFormatter(logging.Formatter):
5470
5681
 
5471
5682
 
5472
5683
  ########################################
5473
- # ../../interp/types.py
5684
+ # ../../../omlish/logs/times.py
5474
5685
 
5475
5686
 
5476
5687
  ##
5477
5688
 
5478
5689
 
5479
- # See https://peps.python.org/pep-3149/
5480
- INTERP_OPT_GLYPHS_BY_ATTR: ta.Mapping[str, str] = collections.OrderedDict([
5481
- ('debug', 'd'),
5482
- ('threaded', 't'),
5483
- ])
5690
+ class LoggingTimeFields(LoggingContextInfo, ta.NamedTuple): # type: ignore[misc]
5691
+ """Maps directly to stdlib `logging.LogRecord` fields, and must be kept in sync with it."""
5692
+
5693
+ created: float
5694
+ msecs: float
5695
+ relative_created: float
5696
+
5697
+ @classmethod
5698
+ def get_std_start_time_ns(cls) -> int:
5699
+ x: ta.Any = logging._startTime # type: ignore[attr-defined] # noqa
5700
+
5701
+ # Before 3.13.0b1 this will be `time.time()`, a float of seconds. After that, it will be `time.time_ns()`, an
5702
+ # int.
5703
+ #
5704
+ # See:
5705
+ # - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
5706
+ #
5707
+ if isinstance(x, float):
5708
+ return int(x * 1e9)
5709
+ else:
5710
+ return x
5711
+
5712
+ @classmethod
5713
+ def build(
5714
+ cls,
5715
+ time_ns: int,
5716
+ *,
5717
+ start_time_ns: ta.Optional[int] = None,
5718
+ ) -> 'LoggingTimeFields':
5719
+ # https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
5720
+ created = time_ns / 1e9 # ns to float seconds
5721
+
5722
+ # Get the number of whole milliseconds (0-999) in the fractional part of seconds.
5723
+ # Eg: 1_677_903_920_999_998_503 ns --> 999_998_503 ns--> 999 ms
5724
+ # Convert to float by adding 0.0 for historical reasons. See gh-89047
5725
+ msecs = (time_ns % 1_000_000_000) // 1_000_000 + 0.0
5726
+
5727
+ # https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
5728
+ if msecs == 999.0 and int(created) != time_ns // 1_000_000_000:
5729
+ # ns -> sec conversion can round up, e.g:
5730
+ # 1_677_903_920_999_999_900 ns --> 1_677_903_921.0 sec
5731
+ msecs = 0.0
5732
+
5733
+ if start_time_ns is None:
5734
+ start_time_ns = cls.get_std_start_time_ns()
5735
+ relative_created = (time_ns - start_time_ns) / 1e6
5736
+
5737
+ return cls(
5738
+ created,
5739
+ msecs,
5740
+ relative_created,
5741
+ )
5742
+
5743
+
5744
+ ##
5745
+
5746
+
5747
+ class UnexpectedLoggingStartTimeWarning(LoggingSetupWarning):
5748
+ pass
5749
+
5750
+
5751
+ def _check_logging_start_time() -> None:
5752
+ if (x := LoggingTimeFields.get_std_start_time_ns()) < (t := time.time()):
5753
+ import warnings # noqa
5754
+
5755
+ warnings.warn(
5756
+ f'Unexpected logging start time detected: '
5757
+ f'get_std_start_time_ns={x}, '
5758
+ f'time.time()={t}',
5759
+ UnexpectedLoggingStartTimeWarning,
5760
+ )
5761
+
5762
+
5763
+ _check_logging_start_time()
5764
+
5765
+
5766
+ ########################################
5767
+ # ../../interp/types.py
5768
+
5769
+
5770
+ ##
5771
+
5772
+
5773
+ # See https://peps.python.org/pep-3149/
5774
+ INTERP_OPT_GLYPHS_BY_ATTR: ta.Mapping[str, str] = collections.OrderedDict([
5775
+ ('debug', 'd'),
5776
+ ('threaded', 't'),
5777
+ ])
5484
5778
 
5485
5779
  INTERP_OPT_ATTRS_BY_GLYPH: ta.Mapping[str, str] = collections.OrderedDict(
5486
5780
  (g, a) for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items()
@@ -6674,6 +6968,263 @@ class InjectionApi:
6674
6968
  inj = InjectionApi()
6675
6969
 
6676
6970
 
6971
+ ########################################
6972
+ # ../../../omlish/logs/contexts.py
6973
+
6974
+
6975
+ ##
6976
+
6977
+
6978
+ class LoggingContext(Abstract):
6979
+ @property
6980
+ @abc.abstractmethod
6981
+ def level(self) -> NamedLogLevel:
6982
+ raise NotImplementedError
6983
+
6984
+ #
6985
+
6986
+ @property
6987
+ @abc.abstractmethod
6988
+ def time_ns(self) -> int:
6989
+ raise NotImplementedError
6990
+
6991
+ @property
6992
+ @abc.abstractmethod
6993
+ def times(self) -> LoggingTimeFields:
6994
+ raise NotImplementedError
6995
+
6996
+ #
6997
+
6998
+ @property
6999
+ @abc.abstractmethod
7000
+ def exc_info(self) -> ta.Optional[LoggingExcInfo]:
7001
+ raise NotImplementedError
7002
+
7003
+ @property
7004
+ @abc.abstractmethod
7005
+ def exc_info_tuple(self) -> ta.Optional[LoggingExcInfoTuple]:
7006
+ raise NotImplementedError
7007
+
7008
+ #
7009
+
7010
+ @abc.abstractmethod
7011
+ def caller(self) -> ta.Optional[LoggingCaller]:
7012
+ raise NotImplementedError
7013
+
7014
+ @abc.abstractmethod
7015
+ def source_file(self) -> ta.Optional[LoggingSourceFileInfo]:
7016
+ raise NotImplementedError
7017
+
7018
+ #
7019
+
7020
+ @abc.abstractmethod
7021
+ def thread(self) -> ta.Optional[LoggingThreadInfo]:
7022
+ raise NotImplementedError
7023
+
7024
+ @abc.abstractmethod
7025
+ def process(self) -> ta.Optional[LoggingProcessInfo]:
7026
+ raise NotImplementedError
7027
+
7028
+ @abc.abstractmethod
7029
+ def multiprocessing(self) -> ta.Optional[LoggingMultiprocessingInfo]:
7030
+ raise NotImplementedError
7031
+
7032
+ @abc.abstractmethod
7033
+ def asyncio_task(self) -> ta.Optional[LoggingAsyncioTaskInfo]:
7034
+ raise NotImplementedError
7035
+
7036
+
7037
+ ##
7038
+
7039
+
7040
+ class CaptureLoggingContext(LoggingContext, Abstract):
7041
+ class AlreadyCapturedError(Exception):
7042
+ pass
7043
+
7044
+ class NotCapturedError(Exception):
7045
+ pass
7046
+
7047
+ @abc.abstractmethod
7048
+ def capture(self) -> None:
7049
+ """Must be cooperatively called only from the expected locations."""
7050
+
7051
+ raise NotImplementedError
7052
+
7053
+
7054
+ @ta.final
7055
+ class CaptureLoggingContextImpl(CaptureLoggingContext):
7056
+ @ta.final
7057
+ class NOT_SET: # noqa
7058
+ def __new__(cls, *args, **kwargs): # noqa
7059
+ raise TypeError
7060
+
7061
+ #
7062
+
7063
+ def __init__(
7064
+ self,
7065
+ level: LogLevel,
7066
+ *,
7067
+ time_ns: ta.Optional[int] = None,
7068
+
7069
+ exc_info: LoggingExcInfoArg = False,
7070
+
7071
+ caller: ta.Union[LoggingCaller, ta.Type[NOT_SET], None] = NOT_SET,
7072
+ stack_offset: int = 0,
7073
+ stack_info: bool = False,
7074
+ ) -> None:
7075
+ self._level: NamedLogLevel = level if level.__class__ is NamedLogLevel else NamedLogLevel(level) # type: ignore[assignment] # noqa
7076
+
7077
+ #
7078
+
7079
+ if time_ns is None:
7080
+ time_ns = time.time_ns()
7081
+ self._time_ns: int = time_ns
7082
+
7083
+ #
7084
+
7085
+ if exc_info is True:
7086
+ sys_exc_info = sys.exc_info()
7087
+ if sys_exc_info[0] is not None:
7088
+ exc_info = sys_exc_info
7089
+ else:
7090
+ exc_info = None
7091
+ elif exc_info is False:
7092
+ exc_info = None
7093
+
7094
+ if exc_info is not None:
7095
+ self._exc_info: ta.Optional[LoggingExcInfo] = exc_info
7096
+ if isinstance(exc_info, BaseException):
7097
+ self._exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = (type(exc_info), exc_info, exc_info.__traceback__) # noqa
7098
+ else:
7099
+ self._exc_info_tuple = exc_info
7100
+
7101
+ #
7102
+
7103
+ if caller is not CaptureLoggingContextImpl.NOT_SET:
7104
+ self._caller = caller # type: ignore[assignment]
7105
+ else:
7106
+ self._stack_offset = stack_offset
7107
+ self._stack_info = stack_info
7108
+
7109
+ ##
7110
+
7111
+ @property
7112
+ def level(self) -> NamedLogLevel:
7113
+ return self._level
7114
+
7115
+ #
7116
+
7117
+ @property
7118
+ def time_ns(self) -> int:
7119
+ return self._time_ns
7120
+
7121
+ _times: LoggingTimeFields
7122
+
7123
+ @property
7124
+ def times(self) -> LoggingTimeFields:
7125
+ try:
7126
+ return self._times
7127
+ except AttributeError:
7128
+ pass
7129
+
7130
+ times = self._times = LoggingTimeFields.build(self.time_ns)
7131
+ return times
7132
+
7133
+ #
7134
+
7135
+ _exc_info: ta.Optional[LoggingExcInfo] = None
7136
+ _exc_info_tuple: ta.Optional[LoggingExcInfoTuple] = None
7137
+
7138
+ @property
7139
+ def exc_info(self) -> ta.Optional[LoggingExcInfo]:
7140
+ return self._exc_info
7141
+
7142
+ @property
7143
+ def exc_info_tuple(self) -> ta.Optional[LoggingExcInfoTuple]:
7144
+ return self._exc_info_tuple
7145
+
7146
+ ##
7147
+
7148
+ _stack_offset: int
7149
+ _stack_info: bool
7150
+
7151
+ def inc_stack_offset(self, ofs: int = 1) -> 'CaptureLoggingContext':
7152
+ if hasattr(self, '_stack_offset'):
7153
+ self._stack_offset += ofs
7154
+ return self
7155
+
7156
+ _has_captured: bool = False
7157
+
7158
+ _caller: ta.Optional[LoggingCaller]
7159
+ _source_file: ta.Optional[LoggingSourceFileInfo]
7160
+
7161
+ _thread: ta.Optional[LoggingThreadInfo]
7162
+ _process: ta.Optional[LoggingProcessInfo]
7163
+ _multiprocessing: ta.Optional[LoggingMultiprocessingInfo]
7164
+ _asyncio_task: ta.Optional[LoggingAsyncioTaskInfo]
7165
+
7166
+ def capture(self) -> None:
7167
+ if self._has_captured:
7168
+ raise CaptureLoggingContextImpl.AlreadyCapturedError
7169
+ self._has_captured = True
7170
+
7171
+ if not hasattr(self, '_caller'):
7172
+ self._caller = LoggingCaller.find(
7173
+ self._stack_offset + 1,
7174
+ stack_info=self._stack_info,
7175
+ )
7176
+
7177
+ if (caller := self._caller) is not None:
7178
+ self._source_file = LoggingSourceFileInfo.build(caller.file_path)
7179
+ else:
7180
+ self._source_file = None
7181
+
7182
+ self._thread = LoggingThreadInfo.build()
7183
+ self._process = LoggingProcessInfo.build()
7184
+ self._multiprocessing = LoggingMultiprocessingInfo.build()
7185
+ self._asyncio_task = LoggingAsyncioTaskInfo.build()
7186
+
7187
+ #
7188
+
7189
+ def caller(self) -> ta.Optional[LoggingCaller]:
7190
+ try:
7191
+ return self._caller
7192
+ except AttributeError:
7193
+ raise CaptureLoggingContext.NotCapturedError from None
7194
+
7195
+ def source_file(self) -> ta.Optional[LoggingSourceFileInfo]:
7196
+ try:
7197
+ return self._source_file
7198
+ except AttributeError:
7199
+ raise CaptureLoggingContext.NotCapturedError from None
7200
+
7201
+ #
7202
+
7203
+ def thread(self) -> ta.Optional[LoggingThreadInfo]:
7204
+ try:
7205
+ return self._thread
7206
+ except AttributeError:
7207
+ raise CaptureLoggingContext.NotCapturedError from None
7208
+
7209
+ def process(self) -> ta.Optional[LoggingProcessInfo]:
7210
+ try:
7211
+ return self._process
7212
+ except AttributeError:
7213
+ raise CaptureLoggingContext.NotCapturedError from None
7214
+
7215
+ def multiprocessing(self) -> ta.Optional[LoggingMultiprocessingInfo]:
7216
+ try:
7217
+ return self._multiprocessing
7218
+ except AttributeError:
7219
+ raise CaptureLoggingContext.NotCapturedError from None
7220
+
7221
+ def asyncio_task(self) -> ta.Optional[LoggingAsyncioTaskInfo]:
7222
+ try:
7223
+ return self._asyncio_task
7224
+ except AttributeError:
7225
+ raise CaptureLoggingContext.NotCapturedError from None
7226
+
7227
+
6677
7228
  ########################################
6678
7229
  # ../../../omlish/logs/standard.py
6679
7230
  """
@@ -7018,70 +7569,585 @@ InterpProviders = ta.NewType('InterpProviders', ta.Sequence[InterpProvider])
7018
7569
 
7019
7570
 
7020
7571
  ########################################
7021
- # ../../../omlish/subprocesses/base.py
7572
+ # ../../../omlish/logs/base.py
7022
7573
 
7023
7574
 
7024
7575
  ##
7025
7576
 
7026
7577
 
7027
- # Valid channel type kwarg values:
7028
- # - A special flag negative int
7029
- # - A positive fd int
7030
- # - A file-like object
7031
- # - None
7578
+ class AnyLogger(Abstract, ta.Generic[T]):
7579
+ def is_enabled_for(self, level: LogLevel) -> bool:
7580
+ return self.get_effective_level() >= level
7032
7581
 
7033
- SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
7034
- 'pipe': subprocess.PIPE,
7035
- 'stdout': subprocess.STDOUT,
7036
- 'devnull': subprocess.DEVNULL,
7037
- }
7582
+ @abc.abstractmethod
7583
+ def get_effective_level(self) -> LogLevel:
7584
+ raise NotImplementedError
7038
7585
 
7586
+ #
7039
7587
 
7040
- ##
7588
+ @ta.final
7589
+ def isEnabledFor(self, level: LogLevel) -> bool: # noqa
7590
+ return self.is_enabled_for(level)
7041
7591
 
7592
+ @ta.final
7593
+ def getEffectiveLevel(self) -> LogLevel: # noqa
7594
+ return self.get_effective_level()
7042
7595
 
7043
- class VerboseCalledProcessError(subprocess.CalledProcessError):
7044
- @classmethod
7045
- def from_std(cls, e: subprocess.CalledProcessError) -> 'VerboseCalledProcessError':
7046
- return cls(
7047
- e.returncode,
7048
- e.cmd,
7049
- output=e.output,
7050
- stderr=e.stderr,
7051
- )
7596
+ ##
7052
7597
 
7053
- def __str__(self) -> str:
7054
- msg = super().__str__()
7055
- if self.output is not None:
7056
- msg += f' Output: {self.output!r}'
7057
- if self.stderr is not None:
7058
- msg += f' Stderr: {self.stderr!r}'
7059
- return msg
7598
+ @ta.overload
7599
+ def log(self, level: LogLevel, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
7600
+ ...
7060
7601
 
7602
+ @ta.overload
7603
+ def log(self, level: LogLevel, msg: ta.Tuple[ta.Any, ...], **kwargs: ta.Any) -> T:
7604
+ ...
7061
7605
 
7062
- class BaseSubprocesses(Abstract):
7063
- DEFAULT_LOGGER: ta.ClassVar[ta.Optional[logging.Logger]] = None
7606
+ @ta.overload
7607
+ def log(self, level: LogLevel, msg_fn: LoggingMsgFn, **kwargs: ta.Any) -> T:
7608
+ ...
7064
7609
 
7065
- def __init__(
7066
- self,
7067
- *,
7068
- log: ta.Optional[logging.Logger] = None,
7069
- try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
7070
- ) -> None:
7071
- super().__init__()
7610
+ @ta.final
7611
+ def log(self, level: LogLevel, *args, **kwargs):
7612
+ return self._log(CaptureLoggingContextImpl(level, stack_offset=1), *args, **kwargs)
7072
7613
 
7073
- self._log = log if log is not None else self.DEFAULT_LOGGER
7074
- self._try_exceptions = try_exceptions if try_exceptions is not None else self.DEFAULT_TRY_EXCEPTIONS
7614
+ #
7075
7615
 
7076
- def set_logger(self, log: ta.Optional[logging.Logger]) -> None:
7077
- self._log = log
7616
+ @ta.overload
7617
+ def debug(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
7618
+ ...
7619
+
7620
+ @ta.overload
7621
+ def debug(self, msg: ta.Tuple[ta.Any, ...], **kwargs: ta.Any) -> T:
7622
+ ...
7623
+
7624
+ @ta.overload
7625
+ def debug(self, msg_fn: LoggingMsgFn, **kwargs: ta.Any) -> T:
7626
+ ...
7627
+
7628
+ @ta.final
7629
+ def debug(self, *args, **kwargs):
7630
+ return self._log(CaptureLoggingContextImpl(NamedLogLevel.DEBUG, stack_offset=1), *args, **kwargs)
7078
7631
 
7079
7632
  #
7080
7633
 
7081
- def prepare_args(
7082
- self,
7083
- *cmd: str,
7084
- env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
7634
+ @ta.overload
7635
+ def info(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
7636
+ ...
7637
+
7638
+ @ta.overload
7639
+ def info(self, msg: ta.Tuple[ta.Any, ...], **kwargs: ta.Any) -> T:
7640
+ ...
7641
+
7642
+ @ta.overload
7643
+ def info(self, msg_fn: LoggingMsgFn, **kwargs: ta.Any) -> T:
7644
+ ...
7645
+
7646
+ @ta.final
7647
+ def info(self, *args, **kwargs):
7648
+ return self._log(CaptureLoggingContextImpl(NamedLogLevel.INFO, stack_offset=1), *args, **kwargs)
7649
+
7650
+ #
7651
+
7652
+ @ta.overload
7653
+ def warning(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
7654
+ ...
7655
+
7656
+ @ta.overload
7657
+ def warning(self, msg: ta.Tuple[ta.Any, ...], **kwargs: ta.Any) -> T:
7658
+ ...
7659
+
7660
+ @ta.overload
7661
+ def warning(self, msg_fn: LoggingMsgFn, **kwargs: ta.Any) -> T:
7662
+ ...
7663
+
7664
+ @ta.final
7665
+ def warning(self, *args, **kwargs):
7666
+ return self._log(CaptureLoggingContextImpl(NamedLogLevel.WARNING, stack_offset=1), *args, **kwargs)
7667
+
7668
+ #
7669
+
7670
+ @ta.overload
7671
+ def error(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
7672
+ ...
7673
+
7674
+ @ta.overload
7675
+ def error(self, msg: ta.Tuple[ta.Any, ...], **kwargs: ta.Any) -> T:
7676
+ ...
7677
+
7678
+ @ta.overload
7679
+ def error(self, msg_fn: LoggingMsgFn, **kwargs: ta.Any) -> T:
7680
+ ...
7681
+
7682
+ @ta.final
7683
+ def error(self, *args, **kwargs):
7684
+ return self._log(CaptureLoggingContextImpl(NamedLogLevel.ERROR, stack_offset=1), *args, **kwargs)
7685
+
7686
+ #
7687
+
7688
+ @ta.overload
7689
+ def exception(self, msg: str, *args: ta.Any, exc_info: LoggingExcInfoArg = True, **kwargs: ta.Any) -> T:
7690
+ ...
7691
+
7692
+ @ta.overload
7693
+ def exception(self, msg: ta.Tuple[ta.Any, ...], *, exc_info: LoggingExcInfoArg = True, **kwargs: ta.Any) -> T:
7694
+ ...
7695
+
7696
+ @ta.overload
7697
+ def exception(self, msg_fn: LoggingMsgFn, *, exc_info: LoggingExcInfoArg = True, **kwargs: ta.Any) -> T:
7698
+ ...
7699
+
7700
+ @ta.final
7701
+ def exception(self, *args, exc_info: LoggingExcInfoArg = True, **kwargs):
7702
+ return self._log(CaptureLoggingContextImpl(NamedLogLevel.ERROR, exc_info=exc_info, stack_offset=1), *args, **kwargs) # noqa
7703
+
7704
+ #
7705
+
7706
+ @ta.overload
7707
+ def critical(self, msg: str, *args: ta.Any, **kwargs: ta.Any) -> T:
7708
+ ...
7709
+
7710
+ @ta.overload
7711
+ def critical(self, msg: ta.Tuple[ta.Any, ...], **kwargs: ta.Any) -> T:
7712
+ ...
7713
+
7714
+ @ta.overload
7715
+ def critical(self, msg_fn: LoggingMsgFn, **kwargs: ta.Any) -> T:
7716
+ ...
7717
+
7718
+ @ta.final
7719
+ def critical(self, *args, **kwargs):
7720
+ return self._log(CaptureLoggingContextImpl(NamedLogLevel.CRITICAL, stack_offset=1), *args, **kwargs)
7721
+
7722
+ ##
7723
+
7724
+ @classmethod
7725
+ def _prepare_msg_args(cls, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> ta.Tuple[str, tuple]:
7726
+ if callable(msg):
7727
+ if args:
7728
+ raise TypeError(f'Must not provide both a message function and args: {msg=} {args=}')
7729
+ x = msg()
7730
+ if isinstance(x, str):
7731
+ return x, ()
7732
+ elif isinstance(x, tuple):
7733
+ if x:
7734
+ return x[0], x[1:]
7735
+ else:
7736
+ return '', ()
7737
+ else:
7738
+ raise TypeError(x)
7739
+
7740
+ elif isinstance(msg, tuple):
7741
+ if args:
7742
+ raise TypeError(f'Must not provide both a tuple message and args: {msg=} {args=}')
7743
+ if msg:
7744
+ return msg[0], msg[1:]
7745
+ else:
7746
+ return '', ()
7747
+
7748
+ elif isinstance(msg, str):
7749
+ return msg, args
7750
+
7751
+ else:
7752
+ raise TypeError(msg)
7753
+
7754
+ @abc.abstractmethod
7755
+ def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> T: # noqa
7756
+ raise NotImplementedError
7757
+
7758
+
7759
+ class Logger(AnyLogger[None], Abstract):
7760
+ @abc.abstractmethod
7761
+ def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> None: # noqa
7762
+ raise NotImplementedError
7763
+
7764
+
7765
+ class AsyncLogger(AnyLogger[ta.Awaitable[None]], Abstract):
7766
+ @abc.abstractmethod
7767
+ def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> ta.Awaitable[None]: # noqa
7768
+ raise NotImplementedError
7769
+
7770
+
7771
+ ##
7772
+
7773
+
7774
+ class AnyNopLogger(AnyLogger[T], Abstract):
7775
+ @ta.final
7776
+ def get_effective_level(self) -> LogLevel:
7777
+ return 999
7778
+
7779
+
7780
+ @ta.final
7781
+ class NopLogger(AnyNopLogger[None], Logger):
7782
+ def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> None: # noqa
7783
+ pass
7784
+
7785
+
7786
+ @ta.final
7787
+ class AsyncNopLogger(AnyNopLogger[ta.Awaitable[None]], AsyncLogger):
7788
+ async def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any, **kwargs: ta.Any) -> None: # noqa
7789
+ pass
7790
+
7791
+
7792
+ ########################################
7793
+ # ../../../omlish/logs/std/records.py
7794
+
7795
+
7796
+ ##
7797
+
7798
+
7799
+ # Ref:
7800
+ # - https://docs.python.org/3/library/logging.html#logrecord-attributes
7801
+ #
7802
+ # LogRecord:
7803
+ # - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L276 (3.8)
7804
+ # - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L286 (~3.14) # noqa
7805
+ #
7806
+ # LogRecord.__init__ args:
7807
+ # - name: str
7808
+ # - level: int
7809
+ # - pathname: str - Confusingly referred to as `fn` before the LogRecord ctor. May be empty or "(unknown file)".
7810
+ # - lineno: int - May be 0.
7811
+ # - msg: str
7812
+ # - args: tuple | dict | 1-tuple[dict]
7813
+ # - exc_info: LoggingExcInfoTuple | None
7814
+ # - func: str | None = None -> funcName
7815
+ # - sinfo: str | None = None -> stack_info
7816
+ #
7817
+ KNOWN_STD_LOGGING_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
7818
+ # Name of the logger used to log the call. Unmodified by ctor.
7819
+ name=str,
7820
+
7821
+ # The format string passed in the original logging call. Merged with args to produce message, or an arbitrary object
7822
+ # (see Using arbitrary objects as messages). Unmodified by ctor.
7823
+ msg=str,
7824
+
7825
+ # The tuple of arguments merged into msg to produce message, or a dict whose values are used for the merge (when
7826
+ # there is only one argument, and it is a dictionary). Ctor will transform a 1-tuple containing a Mapping into just
7827
+ # the mapping, but is otherwise unmodified.
7828
+ args=ta.Union[tuple, dict],
7829
+
7830
+ #
7831
+
7832
+ # Text logging level for the message ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'). Set to
7833
+ # `getLevelName(level)`.
7834
+ levelname=str,
7835
+
7836
+ # Numeric logging level for the message (DEBUG, INFO, WARNING, ERROR, CRITICAL). Unmodified by ctor.
7837
+ levelno=int,
7838
+
7839
+ #
7840
+
7841
+ # Full pathname of the source file where the logging call was issued (if available). Unmodified by ctor. May default
7842
+ # to "(unknown file)" by Logger.findCaller / Logger._log.
7843
+ pathname=str,
7844
+
7845
+ # Filename portion of pathname. Set to `os.path.basename(pathname)` if successful, otherwise defaults to pathname.
7846
+ filename=str,
7847
+
7848
+ # Module (name portion of filename). Set to `os.path.splitext(filename)[0]`, otherwise defaults to
7849
+ # "Unknown module".
7850
+ module=str,
7851
+
7852
+ #
7853
+
7854
+ # Exception tuple (à la sys.exc_info) or, if no exception has occurred, None. Unmodified by ctor.
7855
+ exc_info=ta.Optional[LoggingExcInfoTuple],
7856
+
7857
+ # Used to cache the traceback text. Simply set to None by ctor, later set by Formatter.format.
7858
+ exc_text=ta.Optional[str],
7859
+
7860
+ #
7861
+
7862
+ # Stack frame information (where available) from the bottom of the stack in the current thread, up to and including
7863
+ # the stack frame of the logging call which resulted in the creation of this record. Set by ctor to `sinfo` arg,
7864
+ # unmodified. Mostly set, if requested, by `Logger.findCaller`, to `traceback.print_stack(f)`, but prepended with
7865
+ # the literal "Stack (most recent call last):\n", and stripped of exactly one trailing `\n` if present.
7866
+ stack_info=ta.Optional[str],
7867
+
7868
+ # Source line number where the logging call was issued (if available). Unmodified by ctor. May default to 0 by
7869
+ # Logger.findCaller / Logger._log.
7870
+ lineno=int,
7871
+
7872
+ # Name of function containing the logging call. Set by ctor to `func` arg, unmodified. May default to
7873
+ # "(unknown function)" by Logger.findCaller / Logger._log.
7874
+ funcName=str,
7875
+
7876
+ #
7877
+
7878
+ # Time when the LogRecord was created. Set to `time.time_ns() / 1e9` for >=3.13.0b1, otherwise simply `time.time()`.
7879
+ #
7880
+ # See:
7881
+ # - https://github.com/python/cpython/commit/1316692e8c7c1e1f3b6639e51804f9db5ed892ea
7882
+ # - https://github.com/python/cpython/commit/1500a23f33f5a6d052ff1ef6383d9839928b8ff1
7883
+ #
7884
+ created=float,
7885
+
7886
+ # Millisecond portion of the time when the LogRecord was created.
7887
+ msecs=float,
7888
+
7889
+ # Time in milliseconds when the LogRecord was created, relative to the time the logging module was loaded.
7890
+ relativeCreated=float,
7891
+
7892
+ #
7893
+
7894
+ # Thread ID if available, and `logging.logThreads` is truthy.
7895
+ thread=ta.Optional[int],
7896
+
7897
+ # Thread name if available, and `logging.logThreads` is truthy.
7898
+ threadName=ta.Optional[str],
7899
+
7900
+ #
7901
+
7902
+ # Process name if available. Set to None if `logging.logMultiprocessing` is not truthy. Otherwise, set to
7903
+ # 'MainProcess', then `sys.modules.get('multiprocessing').current_process().name` if that works, otherwise remains
7904
+ # as 'MainProcess'.
7905
+ #
7906
+ # As noted by stdlib:
7907
+ #
7908
+ # Errors may occur if multiprocessing has not finished loading yet - e.g. if a custom import hook causes
7909
+ # third-party code to run when multiprocessing calls import. See issue 8200 for an example
7910
+ #
7911
+ processName=ta.Optional[str],
7912
+
7913
+ # Process ID if available - that is, if `hasattr(os, 'getpid')` - and `logging.logProcesses` is truthy, otherwise
7914
+ # None.
7915
+ process=ta.Optional[int],
7916
+
7917
+ #
7918
+
7919
+ # Absent <3.12, otherwise asyncio.Task name if available, and `logging.logAsyncioTasks` is truthy. Set to
7920
+ # `sys.modules.get('asyncio').current_task().get_name()`, otherwise None.
7921
+ taskName=ta.Optional[str],
7922
+ )
7923
+
7924
+ KNOWN_STD_LOGGING_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(KNOWN_STD_LOGGING_RECORD_ATTRS)
7925
+
7926
+
7927
+ # Formatter:
7928
+ # - https://github.com/python/cpython/blob/39b2f82717a69dde7212bc39b673b0f55c99e6a3/Lib/logging/__init__.py#L514 (3.8)
7929
+ # - https://github.com/python/cpython/blob/f070f54c5f4a42c7c61d1d5d3b8f3b7203b4a0fb/Lib/logging/__init__.py#L554 (~3.14) # noqa
7930
+ #
7931
+ KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS: ta.Dict[str, ta.Any] = dict(
7932
+ # The logged message, computed as msg % args. Set to `record.getMessage()`.
7933
+ message=str,
7934
+
7935
+ # Human-readable time when the LogRecord was created. By default this is of the form '2003-07-08 16:49:45,896' (the
7936
+ # numbers after the comma are millisecond portion of the time). Set to `self.formatTime(record, self.datefmt)` if
7937
+ # `self.usesTime()`, otherwise unset.
7938
+ asctime=str,
7939
+
7940
+ # Used to cache the traceback text. If unset (falsey) on the record and `exc_info` is truthy, set to
7941
+ # `self.formatException(record.exc_info)` - otherwise unmodified.
7942
+ exc_text=ta.Optional[str],
7943
+ )
7944
+
7945
+ KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTR_SET: ta.FrozenSet[str] = frozenset(KNOWN_STD_LOGGING_FORMATTER_RECORD_ATTRS)
7946
+
7947
+
7948
+ ##
7949
+
7950
+
7951
+ class UnknownStdLoggingRecordAttrsWarning(LoggingSetupWarning):
7952
+ pass
7953
+
7954
+
7955
+ def _check_std_logging_record_attrs() -> None:
7956
+ rec_dct = dict(logging.makeLogRecord({}).__dict__)
7957
+
7958
+ if (unk_rec_fields := frozenset(rec_dct) - KNOWN_STD_LOGGING_RECORD_ATTR_SET):
7959
+ import warnings # noqa
7960
+
7961
+ warnings.warn(
7962
+ f'Unknown log record attrs detected: {sorted(unk_rec_fields)!r}',
7963
+ UnknownStdLoggingRecordAttrsWarning,
7964
+ )
7965
+
7966
+
7967
+ _check_std_logging_record_attrs()
7968
+
7969
+
7970
+ ##
7971
+
7972
+
7973
+ class LoggingContextLogRecord(logging.LogRecord):
7974
+ _SHOULD_ADD_TASK_NAME: ta.ClassVar[bool] = sys.version_info >= (3, 12)
7975
+
7976
+ _UNKNOWN_PATH_NAME: ta.ClassVar[str] = '(unknown file)'
7977
+ _UNKNOWN_FUNC_NAME: ta.ClassVar[str] = '(unknown function)'
7978
+ _UNKNOWN_MODULE: ta.ClassVar[str] = 'Unknown module'
7979
+
7980
+ _STACK_INFO_PREFIX: ta.ClassVar[str] = 'Stack (most recent call last):\n'
7981
+
7982
+ def __init__( # noqa
7983
+ self,
7984
+ # name,
7985
+ # level,
7986
+ # pathname,
7987
+ # lineno,
7988
+ # msg,
7989
+ # args,
7990
+ # exc_info,
7991
+ # func=None,
7992
+ # sinfo=None,
7993
+ # **kwargs,
7994
+ *,
7995
+ name: str,
7996
+ msg: str,
7997
+ args: ta.Union[tuple, dict],
7998
+
7999
+ _logging_context: LoggingContext,
8000
+ ) -> None:
8001
+ ctx = _logging_context
8002
+
8003
+ self.name: str = name
8004
+
8005
+ self.msg: str = msg
8006
+
8007
+ # https://github.com/python/cpython/blob/e709361fc87d0d9ab9c58033a0a7f2fef0ad43d2/Lib/logging/__init__.py#L307
8008
+ if args and len(args) == 1 and isinstance(args[0], collections.abc.Mapping) and args[0]:
8009
+ args = args[0] # type: ignore[assignment]
8010
+ self.args: ta.Union[tuple, dict] = args
8011
+
8012
+ self.levelname: str = logging.getLevelName(ctx.level)
8013
+ self.levelno: int = ctx.level
8014
+
8015
+ if (caller := ctx.caller()) is not None:
8016
+ self.pathname: str = caller.file_path
8017
+ else:
8018
+ self.pathname = self._UNKNOWN_PATH_NAME
8019
+
8020
+ if (src_file := ctx.source_file()) is not None:
8021
+ self.filename: str = src_file.file_name
8022
+ self.module: str = src_file.module
8023
+ else:
8024
+ self.filename = self.pathname
8025
+ self.module = self._UNKNOWN_MODULE
8026
+
8027
+ self.exc_info: ta.Optional[LoggingExcInfoTuple] = ctx.exc_info_tuple
8028
+ self.exc_text: ta.Optional[str] = None
8029
+
8030
+ # If ctx.build_caller() was never called, we simply don't have a stack trace.
8031
+ if caller is not None:
8032
+ if (sinfo := caller.stack_info) is not None:
8033
+ self.stack_info: ta.Optional[str] = '\n'.join([
8034
+ self._STACK_INFO_PREFIX,
8035
+ sinfo[1:] if sinfo.endswith('\n') else sinfo,
8036
+ ])
8037
+ else:
8038
+ self.stack_info = None
8039
+
8040
+ self.lineno: int = caller.line_no
8041
+ self.funcName: str = caller.name
8042
+
8043
+ else:
8044
+ self.stack_info = None
8045
+
8046
+ self.lineno = 0
8047
+ self.funcName = self._UNKNOWN_FUNC_NAME
8048
+
8049
+ times = ctx.times
8050
+ self.created: float = times.created
8051
+ self.msecs: float = times.msecs
8052
+ self.relativeCreated: float = times.relative_created
8053
+
8054
+ if logging.logThreads:
8055
+ thread = check.not_none(ctx.thread())
8056
+ self.thread: ta.Optional[int] = thread.ident
8057
+ self.threadName: ta.Optional[str] = thread.name
8058
+ else:
8059
+ self.thread = None
8060
+ self.threadName = None
8061
+
8062
+ if logging.logProcesses:
8063
+ process = check.not_none(ctx.process())
8064
+ self.process: ta.Optional[int] = process.pid
8065
+ else:
8066
+ self.process = None
8067
+
8068
+ if logging.logMultiprocessing:
8069
+ if (mp := ctx.multiprocessing()) is not None:
8070
+ self.processName: ta.Optional[str] = mp.process_name
8071
+ else:
8072
+ self.processName = None
8073
+ else:
8074
+ self.processName = None
8075
+
8076
+ # Absent <3.12
8077
+ if getattr(logging, 'logAsyncioTasks', None):
8078
+ if (at := ctx.asyncio_task()) is not None:
8079
+ self.taskName: ta.Optional[str] = at.name
8080
+ else:
8081
+ self.taskName = None
8082
+ else:
8083
+ self.taskName = None
8084
+
8085
+
8086
+ ########################################
8087
+ # ../../../omlish/subprocesses/base.py
8088
+
8089
+
8090
+ ##
8091
+
8092
+
8093
+ # Valid channel type kwarg values:
8094
+ # - A special flag negative int
8095
+ # - A positive fd int
8096
+ # - A file-like object
8097
+ # - None
8098
+
8099
+ SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
8100
+ 'pipe': subprocess.PIPE,
8101
+ 'stdout': subprocess.STDOUT,
8102
+ 'devnull': subprocess.DEVNULL,
8103
+ }
8104
+
8105
+
8106
+ ##
8107
+
8108
+
8109
+ class VerboseCalledProcessError(subprocess.CalledProcessError):
8110
+ @classmethod
8111
+ def from_std(cls, e: subprocess.CalledProcessError) -> 'VerboseCalledProcessError':
8112
+ return cls(
8113
+ e.returncode,
8114
+ e.cmd,
8115
+ output=e.output,
8116
+ stderr=e.stderr,
8117
+ )
8118
+
8119
+ def __str__(self) -> str:
8120
+ msg = super().__str__()
8121
+ if self.output is not None:
8122
+ msg += f' Output: {self.output!r}'
8123
+ if self.stderr is not None:
8124
+ msg += f' Stderr: {self.stderr!r}'
8125
+ return msg
8126
+
8127
+
8128
+ class BaseSubprocesses(Abstract):
8129
+ DEFAULT_LOGGER: ta.ClassVar[ta.Optional[LoggerLike]] = None
8130
+
8131
+ def __init__(
8132
+ self,
8133
+ *,
8134
+ log: ta.Optional[LoggerLike] = None,
8135
+ try_exceptions: ta.Optional[ta.Tuple[ta.Type[Exception], ...]] = None,
8136
+ ) -> None:
8137
+ super().__init__()
8138
+
8139
+ self._log = log if log is not None else self.DEFAULT_LOGGER
8140
+ self._try_exceptions = try_exceptions if try_exceptions is not None else self.DEFAULT_TRY_EXCEPTIONS
8141
+
8142
+ def set_logger(self, log: ta.Optional[LoggerLike]) -> None:
8143
+ self._log = log
8144
+
8145
+ #
8146
+
8147
+ def prepare_args(
8148
+ self,
8149
+ *cmd: str,
8150
+ env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
7085
8151
  extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
7086
8152
  quiet: bool = False,
7087
8153
  shell: bool = False,
@@ -7317,6 +8383,45 @@ class InterpResolver:
7317
8383
  print(f' {si}')
7318
8384
 
7319
8385
 
8386
+ ########################################
8387
+ # ../../../omlish/logs/std/adapters.py
8388
+
8389
+
8390
+ ##
8391
+
8392
+
8393
+ class StdLogger(Logger):
8394
+ def __init__(self, std: logging.Logger) -> None:
8395
+ super().__init__()
8396
+
8397
+ self._std = std
8398
+
8399
+ @property
8400
+ def std(self) -> logging.Logger:
8401
+ return self._std
8402
+
8403
+ def get_effective_level(self) -> LogLevel:
8404
+ return self._std.getEffectiveLevel()
8405
+
8406
+ def _log(self, ctx: CaptureLoggingContext, msg: ta.Union[str, tuple, LoggingMsgFn], *args: ta.Any) -> None:
8407
+ if not self.is_enabled_for(ctx.level):
8408
+ return
8409
+
8410
+ ctx.capture()
8411
+
8412
+ ms, args = self._prepare_msg_args(msg, *args)
8413
+
8414
+ rec = LoggingContextLogRecord(
8415
+ name=self._std.name,
8416
+ msg=ms,
8417
+ args=args,
8418
+
8419
+ _logging_context=ctx,
8420
+ )
8421
+
8422
+ self._std.handle(rec)
8423
+
8424
+
7320
8425
  ########################################
7321
8426
  # ../../../omlish/subprocesses/asyncs.py
7322
8427
 
@@ -7628,7 +8733,7 @@ class AsyncioProcessCommunicator:
7628
8733
  proc: asyncio.subprocess.Process,
7629
8734
  loop: ta.Optional[ta.Any] = None,
7630
8735
  *,
7631
- log: ta.Optional[logging.Logger] = None,
8736
+ log: ta.Optional[LoggerLike] = None,
7632
8737
  ) -> None:
7633
8738
  super().__init__()
7634
8739
 
@@ -7811,6 +8916,17 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
7811
8916
  asyncio_subprocesses = AsyncioSubprocesses()
7812
8917
 
7813
8918
 
8919
+ ########################################
8920
+ # ../../../omlish/logs/modules.py
8921
+
8922
+
8923
+ ##
8924
+
8925
+
8926
+ def get_module_logger(mod_globals: ta.Mapping[str, ta.Any]) -> Logger:
8927
+ return StdLogger(logging.getLogger(mod_globals.get('__name__'))) # noqa
8928
+
8929
+
7814
8930
  ########################################
7815
8931
  # ../../interp/inspect.py
7816
8932
 
@@ -7851,7 +8967,7 @@ class InterpInspector:
7851
8967
  def __init__(
7852
8968
  self,
7853
8969
  *,
7854
- log: ta.Optional[logging.Logger] = None,
8970
+ log: ta.Optional[LoggerLike] = None,
7855
8971
  ) -> None:
7856
8972
  super().__init__()
7857
8973
 
@@ -8015,7 +9131,7 @@ class Uv:
8015
9131
  self,
8016
9132
  config: UvConfig = UvConfig(),
8017
9133
  *,
8018
- log: ta.Optional[logging.Logger] = None,
9134
+ log: ta.Optional[LoggerLike] = None,
8019
9135
  ) -> None:
8020
9136
  super().__init__()
8021
9137
 
@@ -8182,6 +9298,90 @@ class GitRevisionAdder:
8182
9298
  #
8183
9299
 
8184
9300
 
9301
+ ########################################
9302
+ # ../reqs.py
9303
+ """
9304
+ TODO:
9305
+ - embed pip._internal.req.parse_requirements, add additional env stuff? breaks compat with raw pip
9306
+ """
9307
+
9308
+
9309
+ log = get_module_logger(globals()) # noqa
9310
+
9311
+
9312
+ ##
9313
+
9314
+
9315
+ class RequirementsRewriter:
9316
+ def __init__(
9317
+ self,
9318
+ venv: ta.Optional[str] = None,
9319
+ ) -> None:
9320
+ super().__init__()
9321
+
9322
+ self._venv = venv
9323
+
9324
+ @cached_nullary
9325
+ def _tmp_dir(self) -> str:
9326
+ return tempfile.mkdtemp('-omlish-reqs')
9327
+
9328
+ VENV_MAGIC = '# @omlish-venv'
9329
+
9330
+ def rewrite_file(self, in_file: str) -> str:
9331
+ with open(in_file) as f:
9332
+ src = f.read()
9333
+
9334
+ in_lines = src.splitlines(keepends=True)
9335
+ out_lines = []
9336
+
9337
+ for l in in_lines:
9338
+ if self.VENV_MAGIC in l:
9339
+ lp, _, rp = l.partition(self.VENV_MAGIC)
9340
+ rp = rp.partition('#')[0]
9341
+ omit = False
9342
+ for v in rp.split():
9343
+ if v[0] == '!':
9344
+ if self._venv is not None and self._venv == v[1:]:
9345
+ omit = True
9346
+ break
9347
+ else:
9348
+ raise NotImplementedError
9349
+
9350
+ if omit:
9351
+ out_lines.append('# OMITTED: ' + l)
9352
+ continue
9353
+
9354
+ out_req = self.rewrite(l.rstrip('\n'), for_file=True)
9355
+ out_lines.append(out_req + '\n')
9356
+
9357
+ out_file = os.path.join(self._tmp_dir(), os.path.basename(in_file))
9358
+ if os.path.exists(out_file):
9359
+ raise Exception(f'file exists: {out_file}')
9360
+
9361
+ with open(out_file, 'w') as f:
9362
+ f.write(''.join(out_lines))
9363
+ log.info('Rewrote requirements file %s to %s', in_file, out_file)
9364
+ return out_file
9365
+
9366
+ def rewrite(self, in_req: str, *, for_file: bool = False) -> str:
9367
+ if in_req.strip().startswith('-r'):
9368
+ l = in_req.strip()
9369
+ lp, _, rp = l.partition(' ')
9370
+ if lp == '-r':
9371
+ inc_in_file, _, rest = rp.partition(' ')
9372
+ else:
9373
+ inc_in_file, rest = lp[2:], rp
9374
+
9375
+ inc_out_file = self.rewrite_file(inc_in_file)
9376
+ if for_file:
9377
+ return ' '.join(['-r ', inc_out_file, rest])
9378
+ else:
9379
+ return '-r' + inc_out_file
9380
+
9381
+ else:
9382
+ return in_req
9383
+
9384
+
8185
9385
  ########################################
8186
9386
  # ../../interp/providers/running.py
8187
9387
 
@@ -8231,7 +9431,7 @@ class SystemInterpProvider(InterpProvider):
8231
9431
  options: Options = Options(),
8232
9432
  *,
8233
9433
  inspector: ta.Optional[InterpInspector] = None,
8234
- log: ta.Optional[logging.Logger] = None,
9434
+ log: ta.Optional[LoggerLike] = None,
8235
9435
  ) -> None:
8236
9436
  super().__init__()
8237
9437
 
@@ -8614,7 +9814,7 @@ class UvInterpProvider(InterpProvider):
8614
9814
  *,
8615
9815
  pyenv: Uv,
8616
9816
  inspector: InterpInspector,
8617
- log: ta.Optional[logging.Logger] = None,
9817
+ log: ta.Optional[LoggerLike] = None,
8618
9818
  ) -> None:
8619
9819
  super().__init__()
8620
9820
 
@@ -9233,7 +10433,7 @@ class PyenvInterpProvider(InterpProvider):
9233
10433
  *,
9234
10434
  pyenv: Pyenv,
9235
10435
  inspector: InterpInspector,
9236
- log: ta.Optional[logging.Logger] = None,
10436
+ log: ta.Optional[LoggerLike] = None,
9237
10437
  ) -> None:
9238
10438
  super().__init__()
9239
10439
 
@@ -9465,7 +10665,7 @@ class InterpVenv:
9465
10665
  cfg: InterpVenvConfig,
9466
10666
  *,
9467
10667
  requirements_processor: ta.Optional[InterpVenvRequirementsProcessor] = None,
9468
- log: ta.Optional[logging.Logger] = None,
10668
+ log: ta.Optional[LoggerLike] = None,
9469
10669
  ) -> None:
9470
10670
  super().__init__()
9471
10671