omdev 0.0.0.dev7__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 (67) hide show
  1. omdev/__about__.py +35 -0
  2. omdev/__init__.py +0 -0
  3. omdev/amalg/__init__.py +0 -0
  4. omdev/amalg/__main__.py +4 -0
  5. omdev/amalg/amalg.py +513 -0
  6. omdev/classdot.py +61 -0
  7. omdev/cmake.py +164 -0
  8. omdev/exts/__init__.py +0 -0
  9. omdev/exts/_distutils/__init__.py +10 -0
  10. omdev/exts/_distutils/build_ext.py +367 -0
  11. omdev/exts/_distutils/compilers/__init__.py +3 -0
  12. omdev/exts/_distutils/compilers/ccompiler.py +1032 -0
  13. omdev/exts/_distutils/compilers/options.py +80 -0
  14. omdev/exts/_distutils/compilers/unixccompiler.py +385 -0
  15. omdev/exts/_distutils/dir_util.py +76 -0
  16. omdev/exts/_distutils/errors.py +62 -0
  17. omdev/exts/_distutils/extension.py +107 -0
  18. omdev/exts/_distutils/file_util.py +216 -0
  19. omdev/exts/_distutils/modified.py +47 -0
  20. omdev/exts/_distutils/spawn.py +103 -0
  21. omdev/exts/_distutils/sysconfig.py +349 -0
  22. omdev/exts/_distutils/util.py +201 -0
  23. omdev/exts/_distutils/version.py +308 -0
  24. omdev/exts/build.py +43 -0
  25. omdev/exts/cmake.py +195 -0
  26. omdev/exts/importhook.py +88 -0
  27. omdev/exts/scan.py +74 -0
  28. omdev/interp/__init__.py +1 -0
  29. omdev/interp/__main__.py +4 -0
  30. omdev/interp/cli.py +63 -0
  31. omdev/interp/inspect.py +105 -0
  32. omdev/interp/providers.py +67 -0
  33. omdev/interp/pyenv.py +353 -0
  34. omdev/interp/resolvers.py +76 -0
  35. omdev/interp/standalone.py +187 -0
  36. omdev/interp/system.py +125 -0
  37. omdev/interp/types.py +92 -0
  38. omdev/mypy/__init__.py +0 -0
  39. omdev/mypy/debug.py +86 -0
  40. omdev/pyproject/__init__.py +1 -0
  41. omdev/pyproject/__main__.py +4 -0
  42. omdev/pyproject/cli.py +319 -0
  43. omdev/pyproject/configs.py +97 -0
  44. omdev/pyproject/ext.py +107 -0
  45. omdev/pyproject/pkg.py +196 -0
  46. omdev/scripts/__init__.py +0 -0
  47. omdev/scripts/execrss.py +19 -0
  48. omdev/scripts/findimports.py +62 -0
  49. omdev/scripts/findmagic.py +70 -0
  50. omdev/scripts/interp.py +2118 -0
  51. omdev/scripts/pyproject.py +3584 -0
  52. omdev/scripts/traceimport.py +502 -0
  53. omdev/tokens.py +42 -0
  54. omdev/toml/__init__.py +1 -0
  55. omdev/toml/parser.py +823 -0
  56. omdev/toml/writer.py +104 -0
  57. omdev/tools/__init__.py +0 -0
  58. omdev/tools/dockertools.py +81 -0
  59. omdev/tools/sqlrepl.py +193 -0
  60. omdev/versioning/__init__.py +1 -0
  61. omdev/versioning/specifiers.py +531 -0
  62. omdev/versioning/versions.py +416 -0
  63. omdev-0.0.0.dev7.dist-info/LICENSE +21 -0
  64. omdev-0.0.0.dev7.dist-info/METADATA +24 -0
  65. omdev-0.0.0.dev7.dist-info/RECORD +67 -0
  66. omdev-0.0.0.dev7.dist-info/WHEEL +5 -0
  67. omdev-0.0.0.dev7.dist-info/top_level.txt +1 -0
omdev/pyproject/cli.py ADDED
@@ -0,0 +1,319 @@
1
+ #!/usr/bin/env python3
2
+ # @omdev-amalg ../scripts/pyproject.py
3
+ # ruff: noqa: UP006 UP007
4
+ """
5
+ TODO:
6
+ - check / tests, src dir sets
7
+ - ci
8
+ - build / package / publish / version roll
9
+ - {pkg_name: [src_dirs]}, default excludes, generate MANIFST.in, ...
10
+ - env vars - PYTHONPATH
11
+
12
+ lookit:
13
+ - https://pdm-project.org/en/latest/
14
+ - https://rye.astral.sh/philosophy/
15
+ - https://github.com/indygreg/python-build-standalone/blob/main/pythonbuild/cpython.py
16
+ - https://astral.sh/blog/uv
17
+ - https://github.com/jazzband/pip-tools
18
+ - https://github.com/Osiris-Team/1JPM
19
+ - https://github.com/brettcannon/microvenv
20
+ - https://github.com/pypa/pipx
21
+ - https://github.com/tox-dev/tox/
22
+ """
23
+ import argparse
24
+ import dataclasses as dc
25
+ import glob
26
+ import itertools
27
+ import os.path
28
+ import shlex
29
+ import shutil
30
+ import sys
31
+ import typing as ta
32
+
33
+ from omlish.lite.cached import cached_nullary
34
+ from omlish.lite.check import check_not
35
+ from omlish.lite.check import check_not_none
36
+ from omlish.lite.logs import configure_standard_logging
37
+ from omlish.lite.logs import log
38
+ from omlish.lite.runtime import check_runtime_version
39
+ from omlish.lite.subprocesses import subprocess_check_call
40
+
41
+ from ..interp.resolvers import DEFAULT_INTERP_RESOLVER
42
+ from ..interp.types import InterpSpecifier
43
+ from ..toml.parser import toml_loads
44
+ from .configs import PyprojectConfig
45
+ from .configs import PyprojectConfigPreparer
46
+ from .configs import VenvConfig
47
+
48
+
49
+ ##
50
+
51
+
52
+ @dc.dataclass(frozen=True)
53
+ class VersionsFile:
54
+ name: ta.Optional[str] = '.versions'
55
+
56
+ @staticmethod
57
+ def parse(s: str) -> ta.Mapping[str, str]:
58
+ return {
59
+ k: v
60
+ for l in s.splitlines()
61
+ if (sl := l.split('#')[0].strip())
62
+ for k, _, v in (sl.partition('='),)
63
+ }
64
+
65
+ @cached_nullary
66
+ def contents(self) -> ta.Mapping[str, str]:
67
+ if not self.name or not os.path.exists(self.name):
68
+ return {}
69
+ with open(self.name) as f:
70
+ s = f.read()
71
+ return self.parse(s)
72
+
73
+ @staticmethod
74
+ def get_pythons(d: ta.Mapping[str, str]) -> ta.Mapping[str, str]:
75
+ pfx = 'PYTHON_'
76
+ return {k[len(pfx):].lower(): v for k, v in d.items() if k.startswith(pfx)}
77
+
78
+ @cached_nullary
79
+ def pythons(self) -> ta.Mapping[str, str]:
80
+ return self.get_pythons(self.contents())
81
+
82
+
83
+ ##
84
+
85
+
86
+ @cached_nullary
87
+ def _script_rel_path() -> str:
88
+ cwd = os.getcwd()
89
+ if not (f := __file__).startswith(cwd):
90
+ raise OSError(f'file {f} not in {cwd}')
91
+ return f[len(cwd):].lstrip(os.sep)
92
+
93
+
94
+ ##
95
+
96
+
97
+ class Venv:
98
+ def __init__(
99
+ self,
100
+ name: str,
101
+ cfg: VenvConfig,
102
+ ) -> None:
103
+ super().__init__()
104
+ self._name = name
105
+ self._cfg = cfg
106
+
107
+ @property
108
+ def cfg(self) -> VenvConfig:
109
+ return self._cfg
110
+
111
+ DIR_NAME = '.venvs'
112
+
113
+ @property
114
+ def dir_name(self) -> str:
115
+ return os.path.join(self.DIR_NAME, self._name)
116
+
117
+ @cached_nullary
118
+ def interp_exe(self) -> str:
119
+ i = InterpSpecifier.parse(check_not_none(self._cfg.interp))
120
+ return DEFAULT_INTERP_RESOLVER.resolve(i).exe
121
+
122
+ @cached_nullary
123
+ def exe(self) -> str:
124
+ ve = os.path.join(self.dir_name, 'bin/python')
125
+ if not os.path.isfile(ve):
126
+ raise Exception(f'venv exe {ve} does not exist or is not a file!')
127
+ return ve
128
+
129
+ @cached_nullary
130
+ def create(self) -> bool:
131
+ if os.path.exists(dn := self.dir_name):
132
+ if not os.path.isdir(dn):
133
+ raise Exception(f'{dn} exists but is not a directory!')
134
+ return False
135
+
136
+ log.info('Using interpreter %s', (ie := self.interp_exe()))
137
+ subprocess_check_call(ie, '-m', 'venv', dn)
138
+
139
+ ve = self.exe()
140
+
141
+ subprocess_check_call(
142
+ ve,
143
+ '-m', 'pip',
144
+ 'install', '-v', '--upgrade',
145
+ 'pip',
146
+ 'setuptools',
147
+ 'wheel',
148
+ )
149
+
150
+ if (sr := self._cfg.requires):
151
+ subprocess_check_call(
152
+ ve,
153
+ '-m', 'pip',
154
+ 'install', '-v',
155
+ *sr,
156
+ )
157
+
158
+ return True
159
+
160
+ @staticmethod
161
+ def _resolve_srcs(raw: ta.List[str]) -> ta.List[str]:
162
+ out: list[str] = []
163
+ seen: ta.Set[str] = set()
164
+ for r in raw:
165
+ es: list[str]
166
+ if any(c in r for c in '*?'):
167
+ es = list(glob.glob(r, recursive=True))
168
+ else:
169
+ es = [r]
170
+ for e in es:
171
+ if e not in seen:
172
+ seen.add(e)
173
+ out.append(e)
174
+ return out
175
+
176
+ @cached_nullary
177
+ def srcs(self) -> ta.Sequence[str]:
178
+ return self._resolve_srcs(self._cfg.srcs or [])
179
+
180
+
181
+ ##
182
+
183
+
184
+ class Run:
185
+ def __init__(
186
+ self,
187
+ *,
188
+ raw_cfg: ta.Union[ta.Mapping[str, ta.Any], str, None] = None,
189
+ ) -> None:
190
+ super().__init__()
191
+
192
+ self._raw_cfg = raw_cfg
193
+
194
+ @cached_nullary
195
+ def raw_cfg(self) -> ta.Mapping[str, ta.Any]:
196
+ if self._raw_cfg is None:
197
+ with open('pyproject.toml') as f:
198
+ buf = f.read()
199
+ elif isinstance(self._raw_cfg, str):
200
+ buf = self._raw_cfg
201
+ else:
202
+ return self._raw_cfg
203
+ return toml_loads(buf)
204
+
205
+ @cached_nullary
206
+ def cfg(self) -> PyprojectConfig:
207
+ dct = self.raw_cfg()['tool']['omlish']['pyproject']
208
+ return PyprojectConfigPreparer(
209
+ python_versions=VersionsFile().pythons(),
210
+ ).prepare_config(dct)
211
+
212
+ @cached_nullary
213
+ def venvs(self) -> ta.Mapping[str, Venv]:
214
+ return {
215
+ n: Venv(n, c)
216
+ for n, c in self.cfg().venvs.items()
217
+ if not n.startswith('_')
218
+ }
219
+
220
+
221
+ ##
222
+
223
+
224
+ def _venv_cmd(args) -> None:
225
+ venv = Run().venvs()[args.name]
226
+ if (sd := venv.cfg.docker) is not None and sd != (cd := args._docker_container): # noqa
227
+ script = ' '.join([
228
+ 'python3',
229
+ shlex.quote(_script_rel_path()),
230
+ f'--_docker_container={shlex.quote(sd)}',
231
+ *map(shlex.quote, sys.argv[1:]),
232
+ ])
233
+ subprocess_check_call(
234
+ 'docker',
235
+ 'compose',
236
+ '-f', 'docker/compose.yml',
237
+ 'exec',
238
+ *itertools.chain.from_iterable(
239
+ ('-e', f'{e}={os.environ.get(e, "")}' if '=' not in e else e)
240
+ for e in (args.docker_env or [])
241
+ ),
242
+ '-it', sd,
243
+ 'bash', '--login', '-c', script,
244
+ )
245
+ return
246
+
247
+ venv.create()
248
+
249
+ cmd = args.cmd
250
+ if not cmd:
251
+ pass
252
+
253
+ elif cmd == 'python':
254
+ os.execl(
255
+ (exe := venv.exe()),
256
+ exe,
257
+ *args.args,
258
+ )
259
+
260
+ elif cmd == 'exe':
261
+ check_not(args.args)
262
+ print(venv.exe())
263
+
264
+ elif cmd == 'run':
265
+ sh = check_not_none(shutil.which('bash'))
266
+ script = ' '.join(args.args)
267
+ if not script:
268
+ script = sh
269
+ os.execl(
270
+ (bash := check_not_none(sh)),
271
+ bash,
272
+ '-c',
273
+ f'. {venv.dir_name}/bin/activate && ' + script,
274
+ )
275
+
276
+ elif cmd == 'srcs':
277
+ check_not(args.args)
278
+ print('\n'.join(venv.srcs()))
279
+
280
+ elif cmd == 'test':
281
+ subprocess_check_call(venv.exe(), '-m', 'pytest', *(args.args or []), *venv.srcs())
282
+
283
+ else:
284
+ raise Exception(f'unknown subcommand: {cmd}')
285
+
286
+
287
+ ##
288
+
289
+
290
+ def _build_parser() -> argparse.ArgumentParser:
291
+ parser = argparse.ArgumentParser()
292
+ parser.add_argument('--_docker_container', help=argparse.SUPPRESS)
293
+
294
+ subparsers = parser.add_subparsers()
295
+
296
+ parser_resolve = subparsers.add_parser('venv')
297
+ parser_resolve.add_argument('name')
298
+ parser_resolve.add_argument('-e', '--docker-env', action='append')
299
+ parser_resolve.add_argument('cmd', nargs='?')
300
+ parser_resolve.add_argument('args', nargs=argparse.REMAINDER)
301
+ parser_resolve.set_defaults(func=_venv_cmd)
302
+
303
+ return parser
304
+
305
+
306
+ def _main(argv: ta.Optional[ta.Sequence[str]] = None) -> None:
307
+ check_runtime_version()
308
+ configure_standard_logging()
309
+
310
+ parser = _build_parser()
311
+ args = parser.parse_args(argv)
312
+ if not getattr(args, 'func', None):
313
+ parser.print_help()
314
+ else:
315
+ args.func(args)
316
+
317
+
318
+ if __name__ == '__main__':
319
+ _main()
@@ -0,0 +1,97 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import dataclasses as dc
3
+ import typing as ta
4
+
5
+ from omlish.lite.marshal import unmarshal_obj
6
+
7
+
8
+ @dc.dataclass(frozen=True)
9
+ class VenvConfig:
10
+ inherits: ta.Optional[ta.Sequence[str]] = None
11
+ interp: ta.Optional[str] = None
12
+ requires: ta.Optional[ta.List[str]] = None
13
+ docker: ta.Optional[str] = None
14
+ srcs: ta.Optional[ta.List[str]] = None
15
+
16
+
17
+ @dc.dataclass(frozen=True)
18
+ class PyprojectConfig:
19
+ srcs: ta.Mapping[str, ta.Sequence[str]]
20
+ venvs: ta.Mapping[str, VenvConfig]
21
+
22
+ venvs_dir: str = '.venvs'
23
+ versions_file: ta.Optional[str] = '.versions'
24
+
25
+
26
+ class PyprojectConfigPreparer:
27
+ def __init__(
28
+ self,
29
+ *,
30
+ python_versions: ta.Optional[ta.Mapping[str, str]] = None,
31
+ ) -> None:
32
+ super().__init__()
33
+
34
+ self._python_versions = python_versions or {}
35
+
36
+ def _inherit_venvs(self, m: ta.Mapping[str, VenvConfig]) -> ta.Mapping[str, VenvConfig]:
37
+ done: ta.Dict[str, VenvConfig] = {}
38
+
39
+ def rec(k):
40
+ try:
41
+ return done[k]
42
+ except KeyError:
43
+ pass
44
+
45
+ c = m[k]
46
+ kw = dc.asdict(c)
47
+ for i in c.inherits or ():
48
+ ic = rec(i)
49
+ kw.update({k: v for k, v in dc.asdict(ic).items() if v is not None and kw.get(k) is None})
50
+ del kw['inherits']
51
+
52
+ d = done[k] = VenvConfig(**kw)
53
+ return d
54
+
55
+ for k in m:
56
+ rec(k)
57
+ return done
58
+
59
+ def _resolve_srcs(
60
+ self,
61
+ lst: ta.Sequence[str],
62
+ aliases: ta.Mapping[str, ta.Sequence[str]],
63
+ ) -> ta.List[str]:
64
+ todo = list(reversed(lst))
65
+ raw: ta.List[str] = []
66
+ seen: ta.Set[str] = set()
67
+
68
+ while todo:
69
+ cur = todo.pop()
70
+ if cur in seen:
71
+ continue
72
+
73
+ seen.add(cur)
74
+ if not cur.startswith('@'):
75
+ raw.append(cur)
76
+ continue
77
+
78
+ todo.extend(aliases[cur[1:]][::-1])
79
+
80
+ return raw
81
+
82
+ def _fixup_interp(self, s: ta.Optional[str]) -> ta.Optional[str]:
83
+ if not s or not s.startswith('@'):
84
+ return s
85
+ return self._python_versions[s[1:]]
86
+
87
+ def prepare_config(self, dct: ta.Mapping[str, ta.Any]) -> PyprojectConfig:
88
+ pcfg: PyprojectConfig = unmarshal_obj(dct, PyprojectConfig)
89
+
90
+ ivs = dict(self._inherit_venvs(pcfg.venvs or {}))
91
+ for k, v in ivs.items():
92
+ v = dc.replace(v, srcs=self._resolve_srcs(v.srcs or [], pcfg.srcs or {}))
93
+ v = dc.replace(v, interp=self._fixup_interp(v.interp))
94
+ ivs[k] = v
95
+
96
+ pcfg = dc.replace(pcfg, venvs=ivs)
97
+ return pcfg
omdev/pyproject/ext.py ADDED
@@ -0,0 +1,107 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import dataclasses as dc
3
+ import typing as ta
4
+
5
+
6
+ """
7
+ # https://setuptools.pypa.io/en/latest/userguide/ext_modules.html#extension-api-reference
8
+
9
+ name (str) -
10
+ the full name of the extension, including any packages - ie. not a filename or pathname, but Python dotted name
11
+
12
+ sources (list[str]) -
13
+ list of source filenames, relative to the distribution root (where the setup script lives), in Unix form
14
+ (slash-separated) for portability. Source files may be C, C++, SWIG (.i), platform-specific resource files, or
15
+ whatever else is recognized by the “build_ext” command as source for a Python extension.
16
+
17
+ include_dirs (list[str]) -
18
+ list of directories to search for C/C++ header files (in Unix form for portability)
19
+
20
+ define_macros (list[tuple[str, str|None]]) -
21
+ list of macros to define; each macro is defined using a 2-tuple: the first item corresponding to the name of the
22
+ macro and the second item either a string with its value or None to define it without a particular value (equivalent
23
+ of “#define FOO” in source or -DFOO on Unix C compiler command line)
24
+
25
+ undef_macros (list[str]) -
26
+ list of macros to undefine explicitly
27
+
28
+ library_dirs (list[str]) -
29
+ list of directories to search for C/C++ libraries at link time
30
+
31
+ libraries (list[str]) -
32
+ list of library names (not filenames or paths) to link against
33
+
34
+ runtime_library_dirs (list[str]) -
35
+ list of directories to search for C/C++ libraries at run time (for shared extensions, this is when the extension is
36
+ loaded). Setting this will cause an exception during build on Windows platforms.
37
+
38
+ extra_objects (list[str]) -
39
+ list of extra files to link with (eg. object files not implied by 'sources', static library that must be explicitly
40
+ specified, binary resource files, etc.)
41
+
42
+ extra_compile_args (list[str]) -
43
+ any extra platform- and compiler-specific information to use when compiling the source files in 'sources'. For
44
+ platforms and compilers where “command line” makes sense, this is typically a list of command-line arguments, but
45
+ for other platforms it could be anything.
46
+
47
+ extra_link_args (list[str]) -
48
+ any extra platform- and compiler-specific information to use when linking object files together to create the
49
+ extension (or to create a new static Python interpreter). Similar interpretation as for 'extra_compile_args'.
50
+
51
+ export_symbols (list[str]) -
52
+ list of symbols to be exported from a shared extension. Not used on all platforms, and not generally necessary for
53
+ Python extensions, which typically export exactly one symbol: “init” + extension_name.
54
+
55
+ swig_opts (list[str]) -
56
+ any extra options to pass to SWIG if a source file has the .i extension.
57
+
58
+ depends (list[str]) -
59
+ list of files that the extension depends on
60
+
61
+ language (str) -
62
+ extension language (i.e. “c”, “c++”, “objc”). Will be detected from the source extensions if not provided.
63
+
64
+ optional (bool) -
65
+ specifies that a build failure in the extension should not abort the build process, but simply not install the
66
+ failing extension.
67
+
68
+ py_limited_api (bool) -
69
+ opt-in flag for the usage of Python's limited API.
70
+ """
71
+
72
+
73
+ SETUP_PY_TMPL = """
74
+ import setuptools as st
75
+
76
+ st.setup(
77
+ ext_modules=[
78
+ st.Extension(
79
+ '{mod_name}',
80
+ [{mod_srcs}],
81
+ include_dirs=['lib'],
82
+ py_limited_api=True
83
+ ),
84
+ ],
85
+ )
86
+ """
87
+
88
+
89
+ @dc.dataclass(frozen=True)
90
+ class ExtModule:
91
+ name: str
92
+ sources: ta.List[str]
93
+ include_dirs: ta.Optional[ta.List[str]] = None
94
+ define_macros: ta.Optional[ta.List[ta.Tuple[str, ta.Optional[str]]]] = None
95
+ undef_macros: ta.Optional[ta.List[str]] = None
96
+ library_dirs: ta.Optional[ta.List[str]] = None
97
+ libraries: ta.Optional[ta.List[str]] = None
98
+ runtime_library_dirs: ta.Optional[ta.List[str]] = None
99
+ extra_objects: ta.Optional[ta.List[str]] = None
100
+ extra_compile_args: ta.Optional[ta.List[str]] = None
101
+ extra_link_args: ta.Optional[ta.List[str]] = None
102
+ export_symbols: ta.Optional[ta.List[str]] = None
103
+ swig_opts: ta.Optional[ta.List[str]] = None
104
+ depends: ta.Optional[ta.List[str]] = None
105
+ language: ta.Optional[str] = None
106
+ optional: ta.Optional[bool] = None
107
+ py_limited_api: ta.Optional[bool] = None