omdev 0.0.0.dev7__tar.gz → 0.0.0.dev9__tar.gz

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 (73) hide show
  1. {omdev-0.0.0.dev7/omdev.egg-info → omdev-0.0.0.dev9}/PKG-INFO +2 -2
  2. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/__about__.py +1 -1
  3. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/pyproject/cli.py +49 -0
  4. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/pyproject/configs.py +3 -2
  5. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/pyproject/pkg.py +3 -0
  6. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/scripts/interp.py +73 -4
  7. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/scripts/pyproject.py +422 -7
  8. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9/omdev.egg-info}/PKG-INFO +2 -2
  9. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev.egg-info/requires.txt +1 -1
  10. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/pyproject.toml +2 -2
  11. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/LICENSE +0 -0
  12. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/MANIFEST.in +0 -0
  13. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/README.rst +0 -0
  14. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/__init__.py +0 -0
  15. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/amalg/__init__.py +0 -0
  16. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/amalg/__main__.py +0 -0
  17. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/amalg/amalg.py +0 -0
  18. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/classdot.py +0 -0
  19. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/cmake.py +0 -0
  20. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/__init__.py +0 -0
  21. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/_distutils/__init__.py +0 -0
  22. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/_distutils/build_ext.py +0 -0
  23. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/_distutils/compilers/__init__.py +0 -0
  24. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/_distutils/compilers/ccompiler.py +0 -0
  25. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/_distutils/compilers/options.py +0 -0
  26. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/_distutils/compilers/unixccompiler.py +0 -0
  27. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/_distutils/dir_util.py +0 -0
  28. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/_distutils/errors.py +0 -0
  29. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/_distutils/extension.py +0 -0
  30. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/_distutils/file_util.py +0 -0
  31. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/_distutils/modified.py +0 -0
  32. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/_distutils/spawn.py +0 -0
  33. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/_distutils/sysconfig.py +0 -0
  34. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/_distutils/util.py +0 -0
  35. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/_distutils/version.py +0 -0
  36. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/build.py +0 -0
  37. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/cmake.py +0 -0
  38. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/importhook.py +0 -0
  39. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/exts/scan.py +0 -0
  40. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/interp/__init__.py +0 -0
  41. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/interp/__main__.py +0 -0
  42. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/interp/cli.py +0 -0
  43. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/interp/inspect.py +0 -0
  44. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/interp/providers.py +0 -0
  45. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/interp/pyenv.py +0 -0
  46. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/interp/resolvers.py +0 -0
  47. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/interp/standalone.py +0 -0
  48. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/interp/system.py +0 -0
  49. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/interp/types.py +0 -0
  50. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/mypy/__init__.py +0 -0
  51. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/mypy/debug.py +0 -0
  52. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/pyproject/__init__.py +0 -0
  53. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/pyproject/__main__.py +0 -0
  54. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/pyproject/ext.py +0 -0
  55. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/scripts/__init__.py +0 -0
  56. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/scripts/execrss.py +0 -0
  57. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/scripts/findimports.py +0 -0
  58. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/scripts/findmagic.py +0 -0
  59. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/scripts/traceimport.py +0 -0
  60. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/tokens.py +0 -0
  61. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/toml/__init__.py +0 -0
  62. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/toml/parser.py +0 -0
  63. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/toml/writer.py +0 -0
  64. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/tools/__init__.py +0 -0
  65. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/tools/dockertools.py +0 -0
  66. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/tools/sqlrepl.py +0 -0
  67. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/versioning/__init__.py +0 -0
  68. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/versioning/specifiers.py +0 -0
  69. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev/versioning/versions.py +0 -0
  70. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev.egg-info/SOURCES.txt +0 -0
  71. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev.egg-info/dependency_links.txt +0 -0
  72. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/omdev.egg-info/top_level.txt +0 -0
  73. {omdev-0.0.0.dev7 → omdev-0.0.0.dev9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omdev
3
- Version: 0.0.0.dev7
3
+ Version: 0.0.0.dev9
4
4
  Summary: omdev
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Operating System :: POSIX
13
13
  Requires-Python: >=3.12
14
14
  License-File: LICENSE
15
- Requires-Dist: omlish==0.0.0.dev7
15
+ Requires-Dist: omlish==0.0.0.dev9
16
16
  Provides-Extra: c
17
17
  Requires-Dist: pycparser>=2.22; extra == "c"
18
18
  Requires-Dist: cffi>=1.17; extra == "c"
@@ -30,6 +30,6 @@ class Project(ProjectBase):
30
30
 
31
31
  class Setuptools(SetuptoolsBase):
32
32
  find_packages = {
33
- 'include': ['omdev', 'omdev.*'],
33
+ 'include': [Project.name, f'{Project.name}.*'],
34
34
  'exclude': [*SetuptoolsBase.find_packages['exclude']],
35
35
  }
@@ -21,7 +21,9 @@ lookit:
21
21
  - https://github.com/tox-dev/tox/
22
22
  """
23
23
  import argparse
24
+ import concurrent.futures as cf
24
25
  import dataclasses as dc
26
+ import functools
25
27
  import glob
26
28
  import itertools
27
29
  import os.path
@@ -44,6 +46,7 @@ from ..toml.parser import toml_loads
44
46
  from .configs import PyprojectConfig
45
47
  from .configs import PyprojectConfigPreparer
46
48
  from .configs import VenvConfig
49
+ from .pkg import PyprojectPackageGenerator
47
50
 
48
51
 
49
52
  ##
@@ -287,6 +290,46 @@ def _venv_cmd(args) -> None:
287
290
  ##
288
291
 
289
292
 
293
+ def _pkg_cmd(args) -> None:
294
+ run = Run()
295
+
296
+ cmd = args.cmd
297
+ if cmd == 'gen':
298
+ build_root = os.path.join('.pkg')
299
+
300
+ if os.path.exists(build_root):
301
+ shutil.rmtree(build_root)
302
+
303
+ build_output_dir = 'dist'
304
+ run_build = bool(args.build)
305
+
306
+ num_threads = 8
307
+
308
+ if run_build:
309
+ os.makedirs(build_output_dir, exist_ok=True)
310
+
311
+ with cf.ThreadPoolExecutor(num_threads) as ex:
312
+ futs = [
313
+ ex.submit(functools.partial(
314
+ PyprojectPackageGenerator(
315
+ dir_name,
316
+ build_root,
317
+ ).gen,
318
+ run_build=run_build,
319
+ build_output_dir=build_output_dir,
320
+ ))
321
+ for dir_name in run.cfg().pkgs
322
+ ]
323
+ for fut in futs:
324
+ fut.result()
325
+
326
+ else:
327
+ raise Exception(f'unknown subcommand: {cmd}')
328
+
329
+
330
+ ##
331
+
332
+
290
333
  def _build_parser() -> argparse.ArgumentParser:
291
334
  parser = argparse.ArgumentParser()
292
335
  parser.add_argument('--_docker_container', help=argparse.SUPPRESS)
@@ -300,6 +343,12 @@ def _build_parser() -> argparse.ArgumentParser:
300
343
  parser_resolve.add_argument('args', nargs=argparse.REMAINDER)
301
344
  parser_resolve.set_defaults(func=_venv_cmd)
302
345
 
346
+ parser_resolve = subparsers.add_parser('pkg')
347
+ parser_resolve.add_argument('-b', '--build', action='store_true')
348
+ parser_resolve.add_argument('cmd', nargs='?')
349
+ parser_resolve.add_argument('args', nargs=argparse.REMAINDER)
350
+ parser_resolve.set_defaults(func=_pkg_cmd)
351
+
303
352
  return parser
304
353
 
305
354
 
@@ -16,8 +16,9 @@ class VenvConfig:
16
16
 
17
17
  @dc.dataclass(frozen=True)
18
18
  class PyprojectConfig:
19
- srcs: ta.Mapping[str, ta.Sequence[str]]
20
- venvs: ta.Mapping[str, VenvConfig]
19
+ pkgs: ta.Sequence[str] = dc.field(default_factory=list)
20
+ srcs: ta.Mapping[str, ta.Sequence[str]] = dc.field(default_factory=dict)
21
+ venvs: ta.Mapping[str, VenvConfig] = dc.field(default_factory=dict)
21
22
 
22
23
  venvs_dir: str = '.venvs'
23
24
  versions_file: ta.Optional[str] = '.versions'
@@ -27,6 +27,7 @@ import types
27
27
  import typing as ta
28
28
 
29
29
  from omlish.lite.cached import cached_nullary
30
+ from omlish.lite.logs import log
30
31
 
31
32
  from ..toml.writer import TomlWriter
32
33
 
@@ -184,6 +185,8 @@ class PyprojectPackageGenerator:
184
185
  run_build: bool = False,
185
186
  build_output_dir: ta.Optional[str] = None,
186
187
  ) -> str:
188
+ log.info('Generating pyproject package: %s -> %s', self._dir_name, self._build_root)
189
+
187
190
  self._build_dir()
188
191
  self._write_git_ignore()
189
192
  self._symlink_source_dir()
@@ -12,6 +12,7 @@ import abc
12
12
  import argparse
13
13
  import collections
14
14
  import dataclasses as dc
15
+ import datetime
15
16
  import functools
16
17
  import inspect
17
18
  import itertools
@@ -24,6 +25,7 @@ import shlex
24
25
  import shutil
25
26
  import subprocess
26
27
  import sys
28
+ import threading
27
29
  import typing as ta
28
30
 
29
31
 
@@ -1134,14 +1136,28 @@ class SpecifierSet(BaseSpecifier):
1134
1136
  # ../../../omlish/lite/logs.py
1135
1137
  """
1136
1138
  TODO:
1139
+ - translate json keys
1137
1140
  - debug
1138
1141
  """
1139
- # ruff: noqa: UP007
1142
+ # ruff: noqa: UP006 UP007 N802
1140
1143
 
1141
1144
 
1142
1145
  log = logging.getLogger(__name__)
1143
1146
 
1144
1147
 
1148
+ ##
1149
+
1150
+
1151
+ class TidLogFilter(logging.Filter):
1152
+
1153
+ def filter(self, record):
1154
+ record.tid = threading.get_native_id()
1155
+ return True
1156
+
1157
+
1158
+ ##
1159
+
1160
+
1145
1161
  class JsonLogFormatter(logging.Formatter):
1146
1162
 
1147
1163
  KEYS: ta.Mapping[str, bool] = {
@@ -1177,9 +1193,62 @@ class JsonLogFormatter(logging.Formatter):
1177
1193
  return json_dumps_compact(dct)
1178
1194
 
1179
1195
 
1180
- def configure_standard_logging(level: ta.Union[int, str] = logging.INFO) -> None:
1181
- logging.root.addHandler(logging.StreamHandler())
1182
- logging.root.setLevel(level)
1196
+ ##
1197
+
1198
+
1199
+ STANDARD_LOG_FORMAT_PARTS = [
1200
+ ('asctime', '%(asctime)-15s'),
1201
+ ('process', 'pid=%(process)-6s'),
1202
+ ('thread', 'tid=%(thread)-16s'),
1203
+ ('levelname', '%(levelname)-8s'),
1204
+ ('name', '%(name)s'),
1205
+ ('separator', '::'),
1206
+ ('message', '%(message)s'),
1207
+ ]
1208
+
1209
+
1210
+ class StandardLogFormatter(logging.Formatter):
1211
+
1212
+ @staticmethod
1213
+ def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
1214
+ return ' '.join(v for k, v in parts)
1215
+
1216
+ converter = datetime.datetime.fromtimestamp # type: ignore
1217
+
1218
+ def formatTime(self, record, datefmt=None):
1219
+ ct = self.converter(record.created) # type: ignore
1220
+ if datefmt:
1221
+ return ct.strftime(datefmt) # noqa
1222
+ else:
1223
+ t = ct.strftime("%Y-%m-%d %H:%M:%S") # noqa
1224
+ return '%s.%03d' % (t, record.msecs)
1225
+
1226
+
1227
+ ##
1228
+
1229
+
1230
+ def configure_standard_logging(
1231
+ level: ta.Union[int, str] = logging.INFO,
1232
+ *,
1233
+ json: bool = False,
1234
+ ) -> logging.Handler:
1235
+ handler = logging.StreamHandler()
1236
+
1237
+ formatter: logging.Formatter
1238
+ if json:
1239
+ formatter = JsonLogFormatter()
1240
+ else:
1241
+ formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
1242
+ handler.setFormatter(formatter)
1243
+
1244
+ handler.addFilter(TidLogFilter())
1245
+
1246
+ logging.root.addHandler(handler)
1247
+
1248
+ if level is not None:
1249
+ logging.root.setLevel(level)
1250
+
1251
+ return handler
1183
1252
 
1184
1253
 
1185
1254
  ########################################
@@ -26,6 +26,7 @@ import argparse
26
26
  import base64
27
27
  import collections
28
28
  import collections.abc
29
+ import concurrent.futures as cf
29
30
  import dataclasses as dc
30
31
  import datetime
31
32
  import decimal
@@ -33,6 +34,7 @@ import enum
33
34
  import fractions
34
35
  import functools
35
36
  import glob
37
+ import importlib
36
38
  import inspect
37
39
  import itertools
38
40
  import json
@@ -45,6 +47,7 @@ import shutil
45
47
  import string
46
48
  import subprocess
47
49
  import sys
50
+ import threading
48
51
  import types
49
52
  import typing as ta
50
53
  import uuid
@@ -882,6 +885,112 @@ def toml_make_safe_parse_float(parse_float: TomlParseFloat) -> TomlParseFloat:
882
885
  return safe_parse_float
883
886
 
884
887
 
888
+ ########################################
889
+ # ../../toml/writer.py
890
+
891
+
892
+ class TomlWriter:
893
+ def __init__(self, out: ta.TextIO) -> None:
894
+ super().__init__()
895
+ self._out = out
896
+
897
+ self._indent = 0
898
+ self._wrote_indent = False
899
+
900
+ #
901
+
902
+ def _w(self, s: str) -> None:
903
+ if not self._wrote_indent:
904
+ self._out.write(' ' * self._indent)
905
+ self._wrote_indent = True
906
+ self._out.write(s)
907
+
908
+ def _nl(self) -> None:
909
+ self._out.write('\n')
910
+ self._wrote_indent = False
911
+
912
+ def _needs_quote(self, s: str) -> bool:
913
+ return (
914
+ not s or
915
+ any(c in s for c in '\'"\n') or
916
+ s[0] not in string.ascii_letters
917
+ )
918
+
919
+ def _maybe_quote(self, s: str) -> str:
920
+ if self._needs_quote(s):
921
+ return repr(s)
922
+ else:
923
+ return s
924
+
925
+ #
926
+
927
+ def write_root(self, obj: ta.Mapping) -> None:
928
+ for i, (k, v) in enumerate(obj.items()):
929
+ if i:
930
+ self._nl()
931
+ self._w('[')
932
+ self._w(self._maybe_quote(k))
933
+ self._w(']')
934
+ self._nl()
935
+ self.write_table_contents(v)
936
+
937
+ def write_table_contents(self, obj: ta.Mapping) -> None:
938
+ for k, v in obj.items():
939
+ self.write_key(k)
940
+ self._w(' = ')
941
+ self.write_value(v)
942
+ self._nl()
943
+
944
+ def write_array(self, obj: ta.Sequence) -> None:
945
+ self._w('[')
946
+ self._nl()
947
+ self._indent += 1
948
+ for e in obj:
949
+ self.write_value(e)
950
+ self._w(',')
951
+ self._nl()
952
+ self._indent -= 1
953
+ self._w(']')
954
+
955
+ def write_inline_table(self, obj: ta.Mapping) -> None:
956
+ self._w('{')
957
+ for i, (k, v) in enumerate(obj.items()):
958
+ if i:
959
+ self._w(', ')
960
+ self.write_key(k)
961
+ self._w(' = ')
962
+ self.write_value(v)
963
+ self._w('}')
964
+
965
+ def write_inline_array(self, obj: ta.Sequence) -> None:
966
+ self._w('[')
967
+ for i, e in enumerate(obj):
968
+ if i:
969
+ self._w(', ')
970
+ self.write_value(e)
971
+ self._w(']')
972
+
973
+ def write_key(self, obj: ta.Any) -> None:
974
+ if isinstance(obj, str):
975
+ self._w(self._maybe_quote(obj.replace('_', '-')))
976
+ elif isinstance(obj, int):
977
+ self._w(repr(str(obj)))
978
+ else:
979
+ raise TypeError(obj)
980
+
981
+ def write_value(self, obj: ta.Any) -> None:
982
+ if isinstance(obj, bool):
983
+ self._w(str(obj).lower())
984
+ elif isinstance(obj, (str, int, float)):
985
+ self._w(repr(obj))
986
+ elif isinstance(obj, ta.Mapping):
987
+ self.write_inline_table(obj)
988
+ elif isinstance(obj, ta.Sequence):
989
+ self.write_array(obj)
990
+ else:
991
+ raise TypeError(obj)
992
+
993
+
885
994
  ########################################
886
995
  # ../../versioning/versions.py
887
996
  # Copyright (c) Donald Stufft and individual contributors.
@@ -1977,14 +2086,28 @@ class SpecifierSet(BaseSpecifier):
1977
2086
  # ../../../omlish/lite/logs.py
1978
2087
  """
1979
2088
  TODO:
2089
+ - translate json keys
1980
2090
  - debug
1981
2091
  """
1982
- # ruff: noqa: UP007
2092
+ # ruff: noqa: UP006 UP007 N802
1983
2093
 
1984
2094
 
1985
2095
  log = logging.getLogger(__name__)
1986
2096
 
1987
2097
 
2098
+ ##
2099
+
2100
+
2101
+ class TidLogFilter(logging.Filter):
2102
+
2103
+ def filter(self, record):
2104
+ record.tid = threading.get_native_id()
2105
+ return True
2106
+
2107
+
2108
+ ##
2109
+
2110
+
1988
2111
  class JsonLogFormatter(logging.Formatter):
1989
2112
 
1990
2113
  KEYS: ta.Mapping[str, bool] = {
@@ -2020,9 +2143,62 @@ class JsonLogFormatter(logging.Formatter):
2020
2143
  return json_dumps_compact(dct)
2021
2144
 
2022
2145
 
2023
- def configure_standard_logging(level: ta.Union[int, str] = logging.INFO) -> None:
2024
- logging.root.addHandler(logging.StreamHandler())
2025
- logging.root.setLevel(level)
2146
+ ##
2147
+
2148
+
2149
+ STANDARD_LOG_FORMAT_PARTS = [
2150
+ ('asctime', '%(asctime)-15s'),
2151
+ ('process', 'pid=%(process)-6s'),
2152
+ ('thread', 'tid=%(thread)-16s'),
2153
+ ('levelname', '%(levelname)-8s'),
2154
+ ('name', '%(name)s'),
2155
+ ('separator', '::'),
2156
+ ('message', '%(message)s'),
2157
+ ]
2158
+
2159
+
2160
+ class StandardLogFormatter(logging.Formatter):
2161
+
2162
+ @staticmethod
2163
+ def build_log_format(parts: ta.Iterable[ta.Tuple[str, str]]) -> str:
2164
+ return ' '.join(v for k, v in parts)
2165
+
2166
+ converter = datetime.datetime.fromtimestamp # type: ignore
2167
+
2168
+ def formatTime(self, record, datefmt=None):
2169
+ ct = self.converter(record.created) # type: ignore
2170
+ if datefmt:
2171
+ return ct.strftime(datefmt) # noqa
2172
+ else:
2173
+ t = ct.strftime("%Y-%m-%d %H:%M:%S") # noqa
2174
+ return '%s.%03d' % (t, record.msecs)
2175
+
2176
+
2177
+ ##
2178
+
2179
+
2180
+ def configure_standard_logging(
2181
+ level: ta.Union[int, str] = logging.INFO,
2182
+ *,
2183
+ json: bool = False,
2184
+ ) -> logging.Handler:
2185
+ handler = logging.StreamHandler()
2186
+
2187
+ formatter: logging.Formatter
2188
+ if json:
2189
+ formatter = JsonLogFormatter()
2190
+ else:
2191
+ formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
2192
+ handler.setFormatter(formatter)
2193
+
2194
+ handler.addFilter(TidLogFilter())
2195
+
2196
+ logging.root.addHandler(handler)
2197
+
2198
+ if level is not None:
2199
+ logging.root.setLevel(level)
2200
+
2201
+ return handler
2026
2202
 
2027
2203
 
2028
2204
  ########################################
@@ -2030,6 +2206,7 @@ def configure_standard_logging(level: ta.Union[int, str] = logging.INFO) -> None
2030
2206
  """
2031
2207
  TODO:
2032
2208
  - pickle stdlib objs? have to pin to 3.8 pickle protocol, will be cross-version
2209
+ - nonstrict toggle
2033
2210
  """
2034
2211
  # ruff: noqa: UP006 UP007
2035
2212
 
@@ -2151,12 +2328,13 @@ class IterableObjMarshaler(ObjMarshaler):
2151
2328
  class DataclassObjMarshaler(ObjMarshaler):
2152
2329
  ty: type
2153
2330
  fs: ta.Mapping[str, ObjMarshaler]
2331
+ nonstrict: bool = False
2154
2332
 
2155
2333
  def marshal(self, o: ta.Any) -> ta.Any:
2156
2334
  return {k: m.marshal(getattr(o, k)) for k, m in self.fs.items()}
2157
2335
 
2158
2336
  def unmarshal(self, o: ta.Any) -> ta.Any:
2159
- return self.ty(**{k: self.fs[k].unmarshal(v) for k, v in o.items()})
2337
+ return self.ty(**{k: self.fs[k].unmarshal(v) for k, v in o.items() if self.nonstrict or k in self.fs})
2160
2338
 
2161
2339
 
2162
2340
  @dc.dataclass(frozen=True)
@@ -2447,8 +2625,9 @@ class VenvConfig:
2447
2625
 
2448
2626
  @dc.dataclass(frozen=True)
2449
2627
  class PyprojectConfig:
2450
- srcs: ta.Mapping[str, ta.Sequence[str]]
2451
- venvs: ta.Mapping[str, VenvConfig]
2628
+ pkgs: ta.Sequence[str] = dc.field(default_factory=list)
2629
+ srcs: ta.Mapping[str, ta.Sequence[str]] = dc.field(default_factory=dict)
2630
+ venvs: ta.Mapping[str, VenvConfig] = dc.field(default_factory=dict)
2452
2631
 
2453
2632
  venvs_dir: str = '.venvs'
2454
2633
  versions_file: ta.Optional[str] = '.versions'
@@ -2528,6 +2707,196 @@ class PyprojectConfigPreparer:
2528
2707
  return pcfg
2529
2708
 
2530
2709
 
2710
+ ########################################
2711
+ # ../pkg.py
2712
+ """
2713
+ TODO:
2714
+ - ext scanning
2715
+ - __revision__
2716
+ - entry_points
2717
+
2718
+ https://setuptools.pypa.io/en/latest/references/keywords.html
2719
+ https://packaging.python.org/en/latest/specifications/pyproject-toml
2720
+
2721
+ How to build a C extension in keeping with PEP 517, i.e. with pyproject.toml instead of setup.py?
2722
+ https://stackoverflow.com/a/66479252
2723
+
2724
+ https://github.com/pypa/sampleproject/blob/db5806e0a3204034c51b1c00dde7d5eb3fa2532e/setup.py
2725
+
2726
+ https://pip.pypa.io/en/stable/cli/pip_install/#vcs-support
2727
+ vcs+protocol://repo_url/#egg=pkg&subdirectory=pkg_dir
2728
+ 'git+https://github.com/wrmsr/omlish@master#subdirectory=.pip/omlish'
2729
+ """
2730
+ # ruff: noqa: UP006 UP007
2731
+
2732
+
2733
+ class PyprojectPackageGenerator:
2734
+ def __init__(
2735
+ self,
2736
+ dir_name: str,
2737
+ build_root: str,
2738
+ ) -> None:
2739
+ super().__init__()
2740
+ self._dir_name = dir_name
2741
+ self._build_root = build_root
2742
+
2743
+ #
2744
+
2745
+ @cached_nullary
2746
+ def about(self) -> types.ModuleType:
2747
+ return importlib.import_module(f'{self._dir_name}.__about__')
2748
+
2749
+ @cached_nullary
2750
+ def project_cls(self) -> type:
2751
+ return self.about().Project
2752
+
2753
+ @cached_nullary
2754
+ def setuptools_cls(self) -> type:
2755
+ return self.about().Setuptools
2756
+
2757
+ #
2758
+
2759
+ @cached_nullary
2760
+ def _build_dir(self) -> str:
2761
+ build_dir: str = os.path.join(self._build_root, self._dir_name)
2762
+ if os.path.isdir(build_dir):
2763
+ shutil.rmtree(build_dir)
2764
+ os.makedirs(build_dir)
2765
+ return build_dir
2766
+
2767
+ #
2768
+
2769
+ def _write_git_ignore(self) -> None:
2770
+ git_ignore = [
2771
+ '/*.egg-info/',
2772
+ '/dist',
2773
+ ]
2774
+ with open(os.path.join(self._build_dir(), '.gitignore'), 'w') as f:
2775
+ f.write('\n'.join(git_ignore))
2776
+
2777
+ #
2778
+
2779
+ def _symlink_source_dir(self) -> None:
2780
+ os.symlink(
2781
+ os.path.relpath(self._dir_name, self._build_dir()),
2782
+ os.path.join(self._build_dir(), self._dir_name),
2783
+ )
2784
+
2785
+ #
2786
+
2787
+ @dc.dataclass(frozen=True)
2788
+ class FileContents:
2789
+ pyproject_dct: ta.Mapping[str, ta.Any]
2790
+ manifest_in: ta.Optional[ta.Sequence[str]]
2791
+
2792
+ @staticmethod
2793
+ def _build_cls_dct(cls: type) -> ta.Dict[str, ta.Any]: # noqa
2794
+ dct = {}
2795
+ for b in reversed(cls.__mro__):
2796
+ for k, v in b.__dict__.items():
2797
+ if k.startswith('_'):
2798
+ continue
2799
+ dct[k] = v
2800
+ return dct
2801
+
2802
+ @staticmethod
2803
+ def _move_dict_key(
2804
+ sd: ta.Dict[str, ta.Any],
2805
+ sk: str,
2806
+ dd: ta.Dict[str, ta.Any],
2807
+ dk: str,
2808
+ ) -> None:
2809
+ if sk in sd:
2810
+ dd[dk] = sd.pop(sk)
2811
+
2812
+ @cached_nullary
2813
+ def file_contents(self) -> FileContents:
2814
+ pyp_dct = {}
2815
+
2816
+ pyp_dct['build-system'] = {
2817
+ 'requires': ['setuptools'],
2818
+ 'build-backend': 'setuptools.build_meta',
2819
+ }
2820
+
2821
+ prj = self._build_cls_dct(self.project_cls())
2822
+ pyp_dct['project'] = prj
2823
+ self._move_dict_key(prj, 'optional_dependencies', pyp_dct, 'project.optional-dependencies')
2824
+
2825
+ st = self._build_cls_dct(self.setuptools_cls())
2826
+ pyp_dct['tool.setuptools'] = st
2827
+ self._move_dict_key(st, 'find_packages', pyp_dct, 'tool.setuptools.packages.find')
2828
+
2829
+ mani_in = st.pop('manifest_in', None)
2830
+
2831
+ return self.FileContents(
2832
+ pyp_dct,
2833
+ mani_in,
2834
+ )
2835
+
2836
+ def _write_file_contents(self) -> None:
2837
+ fc = self.file_contents()
2838
+
2839
+ with open(os.path.join(self._build_dir(), 'pyproject.toml'), 'w') as f:
2840
+ TomlWriter(f).write_root(fc.pyproject_dct)
2841
+
2842
+ if fc.manifest_in:
2843
+ with open(os.path.join(self._build_dir(), 'MANIFEST.in'), 'w') as f:
2844
+ f.write('\n'.join(fc.manifest_in)) # noqa
2845
+
2846
+ #
2847
+
2848
+ _STANDARD_FILES: ta.Sequence[str] = [
2849
+ 'LICENSE',
2850
+ 'README.rst',
2851
+ ]
2852
+
2853
+ def _symlink_standard_files(self) -> None:
2854
+ for fn in self._STANDARD_FILES:
2855
+ if os.path.exists(fn):
2856
+ os.symlink(os.path.relpath(fn, self._build_dir()), os.path.join(self._build_dir(), fn))
2857
+
2858
+ #
2859
+
2860
+ def _run_build(
2861
+ self,
2862
+ build_output_dir: ta.Optional[str] = None,
2863
+ ) -> None:
2864
+ subprocess.check_call(
2865
+ [
2866
+ sys.executable,
2867
+ '-m',
2868
+ 'build',
2869
+ ],
2870
+ cwd=self._build_dir(),
2871
+ )
2872
+
2873
+ if build_output_dir is not None:
2874
+ dist_dir = os.path.join(self._build_dir(), 'dist')
2875
+ for fn in os.listdir(dist_dir):
2876
+ shutil.copyfile(os.path.join(dist_dir, fn), os.path.join(build_output_dir, fn))
2877
+
2878
+ #
2879
+
2880
+ def gen(
2881
+ self,
2882
+ *,
2883
+ run_build: bool = False,
2884
+ build_output_dir: ta.Optional[str] = None,
2885
+ ) -> str:
2886
+ log.info('Generating pyproject package: %s -> %s', self._dir_name, self._build_root)
2887
+
2888
+ self._build_dir()
2889
+ self._write_git_ignore()
2890
+ self._symlink_source_dir()
2891
+ self._write_file_contents()
2892
+ self._symlink_standard_files()
2893
+
2894
+ if run_build:
2895
+ self._run_build(build_output_dir)
2896
+
2897
+ return self._build_dir()
2898
+
2899
+
2531
2900
  ########################################
2532
2901
  # ../../../omlish/lite/subprocesses.py
2533
2902
  # ruff: noqa: UP006 UP007
@@ -3552,6 +3921,46 @@ def _venv_cmd(args) -> None:
3552
3921
  ##
3553
3922
 
3554
3923
 
3924
+ def _pkg_cmd(args) -> None:
3925
+ run = Run()
3926
+
3927
+ cmd = args.cmd
3928
+ if cmd == 'gen':
3929
+ build_root = os.path.join('.pkg')
3930
+
3931
+ if os.path.exists(build_root):
3932
+ shutil.rmtree(build_root)
3933
+
3934
+ build_output_dir = 'dist'
3935
+ run_build = bool(args.build)
3936
+
3937
+ num_threads = 8
3938
+
3939
+ if run_build:
3940
+ os.makedirs(build_output_dir, exist_ok=True)
3941
+
3942
+ with cf.ThreadPoolExecutor(num_threads) as ex:
3943
+ futs = [
3944
+ ex.submit(functools.partial(
3945
+ PyprojectPackageGenerator(
3946
+ dir_name,
3947
+ build_root,
3948
+ ).gen,
3949
+ run_build=run_build,
3950
+ build_output_dir=build_output_dir,
3951
+ ))
3952
+ for dir_name in run.cfg().pkgs
3953
+ ]
3954
+ for fut in futs:
3955
+ fut.result()
3956
+
3957
+ else:
3958
+ raise Exception(f'unknown subcommand: {cmd}')
3959
+
3960
+
3961
+ ##
3962
+
3963
+
3555
3964
  def _build_parser() -> argparse.ArgumentParser:
3556
3965
  parser = argparse.ArgumentParser()
3557
3966
  parser.add_argument('--_docker_container', help=argparse.SUPPRESS)
@@ -3565,6 +3974,12 @@ def _build_parser() -> argparse.ArgumentParser:
3565
3974
  parser_resolve.add_argument('args', nargs=argparse.REMAINDER)
3566
3975
  parser_resolve.set_defaults(func=_venv_cmd)
3567
3976
 
3977
+ parser_resolve = subparsers.add_parser('pkg')
3978
+ parser_resolve.add_argument('-b', '--build', action='store_true')
3979
+ parser_resolve.add_argument('cmd', nargs='?')
3980
+ parser_resolve.add_argument('args', nargs=argparse.REMAINDER)
3981
+ parser_resolve.set_defaults(func=_pkg_cmd)
3982
+
3568
3983
  return parser
3569
3984
 
3570
3985
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omdev
3
- Version: 0.0.0.dev7
3
+ Version: 0.0.0.dev9
4
4
  Summary: omdev
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Operating System :: POSIX
13
13
  Requires-Python: >=3.12
14
14
  License-File: LICENSE
15
- Requires-Dist: omlish==0.0.0.dev7
15
+ Requires-Dist: omlish==0.0.0.dev9
16
16
  Provides-Extra: c
17
17
  Requires-Dist: pycparser>=2.22; extra == "c"
18
18
  Requires-Dist: cffi>=1.17; extra == "c"
@@ -1,4 +1,4 @@
1
- omlish==0.0.0.dev7
1
+ omlish==0.0.0.dev9
2
2
 
3
3
  [c]
4
4
  pycparser>=2.22
@@ -12,7 +12,7 @@ authors = [
12
12
  urls = {source = 'https://github.com/wrmsr/omlish'}
13
13
  license = {text = 'BSD-3-Clause'}
14
14
  requires-python = '>=3.12'
15
- version = '0.0.0.dev7'
15
+ version = '0.0.0.dev9'
16
16
  classifiers = [
17
17
  'License :: OSI Approved :: BSD License',
18
18
  'Development Status :: 2 - Pre-Alpha',
@@ -22,7 +22,7 @@ classifiers = [
22
22
  ]
23
23
  description = 'omdev'
24
24
  dependencies = [
25
- 'omlish == 0.0.0.dev7',
25
+ 'omlish == 0.0.0.dev9',
26
26
  ]
27
27
 
28
28
  [project.optional-dependencies]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes