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