omdev 0.0.0.dev12__py3-none-any.whl → 0.0.0.dev14__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.

Potentially problematic release.


This version of omdev might be problematic. Click here for more details.

@@ -0,0 +1,316 @@
1
+ """
2
+ Tiny pre-commit
3
+
4
+ TODO:
5
+ - global config
6
+ - global analyses - FilesWithShebang
7
+ - shebang files have no relative imports
8
+ - parallelize (asyncio)
9
+ - anyio? aiofiles? :| nonblock open().read()
10
+ - debug log
11
+ - omlish-lite - no non-lite deps, etc etc
12
+ - omlish-script - no deps, shebang, executable, can be 3.12
13
+ - big git files https://github.com/pre-commit/pre-commit-hooks?tab=readme-ov-file#check-added-large-files
14
+ - https://github.com/pre-commit/pre-commit-hooks?tab=readme-ov-file#check-case-conflict
15
+ - https://github.com/pre-commit/pre-commit-hooks?tab=readme-ov-file#check-symlinks
16
+ - https://github.com/pre-commit/pre-commit-hooks?tab=readme-ov-file#detect-aws-credentials
17
+ - https://github.com/pre-commit/pre-commit-hooks?tab=readme-ov-file#forbid-new-submodules
18
+ - don't check in .o's (omdev.ext import hook is dumb w build dir)
19
+ """
20
+ import abc
21
+ import argparse
22
+ import asyncio
23
+ import dataclasses as dc
24
+ import glob
25
+ import inspect
26
+ import logging
27
+ import os.path
28
+ import stat
29
+ import subprocess
30
+ import sys
31
+ import textwrap
32
+ import typing as ta
33
+
34
+ from omdev import findimports
35
+ from omdev import findmagic
36
+ from omlish import cached
37
+ from omlish import logs
38
+
39
+
40
+ T = ta.TypeVar('T')
41
+ PrecheckConfigT = ta.TypeVar('PrecheckConfigT', bound='Precheck.Config')
42
+
43
+
44
+ log = logging.getLogger(__name__)
45
+
46
+
47
+ ##
48
+
49
+
50
+ @dc.dataclass(frozen=True, kw_only=True)
51
+ class PrecheckContext:
52
+ src_roots: ta.Sequence[str]
53
+
54
+
55
+ ##
56
+
57
+
58
+ class Precheck(abc.ABC, ta.Generic[PrecheckConfigT]):
59
+ @dc.dataclass(frozen=True)
60
+ class Config:
61
+ pass
62
+
63
+ def __init__(self, context: PrecheckContext, config: PrecheckConfigT) -> None:
64
+ super().__init__()
65
+ self._context = context
66
+ self._config = config
67
+
68
+ @dc.dataclass(frozen=True)
69
+ class Violation:
70
+ pc: 'Precheck'
71
+ msg: str
72
+
73
+ @abc.abstractmethod
74
+ def run(self) -> ta.AsyncIterator[Violation]:
75
+ raise NotImplementedError
76
+
77
+
78
+ ##
79
+
80
+
81
+ class GitBlacklistPrecheck(Precheck['GitBlacklistPrecheck.Config']):
82
+ """
83
+ TODO:
84
+ - globs
85
+ - regex
86
+ """
87
+
88
+ @dc.dataclass(frozen=True)
89
+ class Config(Precheck.Config):
90
+ files: ta.Sequence[str] = (
91
+ '.env',
92
+ 'secrets.yml',
93
+ )
94
+
95
+ def __init__(self, context: PrecheckContext, config: Config = Config()) -> None:
96
+ super().__init__(context, config)
97
+
98
+ async def run(self) -> ta.AsyncGenerator[Precheck.Violation, None]:
99
+ for f in self._config.files:
100
+ proc = await asyncio.create_subprocess_exec('git', 'status', '-s', f)
101
+ await proc.communicate()
102
+ if proc.returncode:
103
+ yield Precheck.Violation(self, f)
104
+
105
+
106
+ ##
107
+
108
+
109
+ class ScriptDepsPrecheck(Precheck['ScriptDepsPrecheck.Config']):
110
+ @dc.dataclass(frozen=True)
111
+ class Config(Precheck.Config):
112
+ pass
113
+
114
+ def __init__(self, context: PrecheckContext, config: Config = Config()) -> None:
115
+ super().__init__(context, config)
116
+
117
+ async def run(self) -> ta.AsyncGenerator[Precheck.Violation, None]:
118
+ for fp in findmagic.find_magic(
119
+ self._context.src_roots,
120
+ ['# @omlish-script'],
121
+ ['py'],
122
+ ):
123
+ if not (stat.S_IXUSR & os.stat(fp).st_mode):
124
+ yield Precheck.Violation(self, f'script {fp} is not executable')
125
+
126
+ with open(fp) as f: # noqa # FIXME
127
+ src = f.read()
128
+
129
+ if not src.startswith('#!/usr/bin/env python3\n'):
130
+ yield Precheck.Violation(self, f'script {fp} lacks correct shebang')
131
+
132
+ imps = findimports.find_imports(fp)
133
+ deps = findimports.get_import_deps(imps)
134
+ if deps:
135
+ yield Precheck.Violation(self, f'script {fp} has deps: {deps}')
136
+
137
+
138
+ ##
139
+
140
+
141
+ class LitePython8Precheck(Precheck['LitePython8Precheck.Config']):
142
+ @dc.dataclass(frozen=True)
143
+ class Config(Precheck.Config):
144
+ pass
145
+
146
+ def __init__(self, context: PrecheckContext, config: Config = Config()) -> None:
147
+ super().__init__(context, config)
148
+
149
+ #
150
+
151
+ @staticmethod
152
+ def _load_file_module(fp: str) -> None:
153
+ import os.path # noqa
154
+ import types # noqa
155
+
156
+ fp = os.path.abspath(fp)
157
+
158
+ with open(fp) as f:
159
+ src = f.read()
160
+
161
+ mn = os.path.basename(fp).rpartition('.')[0]
162
+
163
+ mod = types.ModuleType(mn)
164
+ mod.__name__ = mn
165
+ mod.__file__ = fp
166
+ mod.__builtins__ = __builtins__ # type: ignore
167
+ mod.__spec__ = None
168
+
169
+ code = compile(src, fp, 'exec')
170
+ exec(code, mod.__dict__, mod.__dict__)
171
+
172
+ @cached.function
173
+ def _load_file_module_payload(self) -> str:
174
+ return '\n'.join([
175
+ 'import sys',
176
+ 'fp = sys.argv[-1]',
177
+ '',
178
+ textwrap.dedent('\n'.join(inspect.getsource(LitePython8Precheck._load_file_module).splitlines()[2:])),
179
+ ])
180
+
181
+ #
182
+
183
+ async def _run_script(self, fp: str) -> list[Precheck.Violation]:
184
+ log.debug('%s: loading script %s', self.__class__.__name__, fp)
185
+
186
+ vs: list[Precheck.Violation] = []
187
+
188
+ proc = await asyncio.create_subprocess_exec(
189
+ '.venvs/8/bin/python',
190
+ '-c',
191
+ self._load_file_module_payload(),
192
+ fp,
193
+ stderr=subprocess.PIPE,
194
+ )
195
+
196
+ _, stderr = await proc.communicate()
197
+ if proc.returncode != 0:
198
+ vs.append(Precheck.Violation(self, f'lite script {fp} failed to load in python8: {stderr.decode()}'))
199
+
200
+ return vs
201
+
202
+ async def _run_one_module(self, fp: str) -> list[Precheck.Violation]:
203
+ vs: list[Precheck.Violation] = []
204
+
205
+ mod = fp.rpartition('.')[0].replace(os.sep, '.')
206
+
207
+ log.debug('%s: loading module %s', self.__class__.__name__, mod)
208
+
209
+ proc = await asyncio.create_subprocess_exec(
210
+ '.venvs/8/bin/python',
211
+ '-c',
212
+ f'import {mod}',
213
+ stderr=subprocess.PIPE,
214
+ )
215
+
216
+ _, stderr = await proc.communicate()
217
+ if proc.returncode != 0:
218
+ vs.append(Precheck.Violation(self, f'lite module {fp} failed to import in python8: {stderr.decode()}')) # noqa
219
+
220
+ return vs
221
+
222
+ async def _run_module(self, fp: str) -> list[Precheck.Violation]:
223
+ vs: list[Precheck.Violation] = []
224
+
225
+ if fp.endswith('__init__.py'):
226
+ pfps = glob.glob(os.path.join(os.path.dirname(fp), '**/*.py'), recursive=True)
227
+ else:
228
+ pfps = [fp]
229
+
230
+ for pfp in pfps:
231
+ vs.extend(await self._run_one_module(pfp))
232
+
233
+ return vs
234
+
235
+ async def run(self) -> ta.AsyncGenerator[Precheck.Violation, None]:
236
+ for fp in findmagic.find_magic(
237
+ self._context.src_roots,
238
+ ['# @omlish-lite'],
239
+ ['py'],
240
+ ):
241
+ with open(fp) as f: # noqa # FIXME
242
+ src = f.read()
243
+
244
+ is_script = '# @omlish-script' in src.splitlines()
245
+
246
+ if is_script:
247
+ for v in await self._run_script(fp):
248
+ yield v
249
+
250
+ else:
251
+ for v in await self._run_module(fp):
252
+ yield v
253
+
254
+
255
+ ##
256
+
257
+
258
+ def _check_cmd(args) -> None:
259
+ if not os.path.isfile('pyproject.toml'):
260
+ raise RuntimeError('must run in project root')
261
+
262
+ ctx = PrecheckContext(
263
+ src_roots=args.roots,
264
+ )
265
+
266
+ pcs: list[Precheck] = [
267
+ GitBlacklistPrecheck(ctx),
268
+ ScriptDepsPrecheck(ctx),
269
+ LitePython8Precheck(ctx),
270
+ ]
271
+
272
+ async def run() -> list[Precheck.Violation]:
273
+ vs: list[Precheck.Violation] = []
274
+
275
+ for pc in pcs:
276
+ async for v in pc.run():
277
+ vs.append(v)
278
+ print(v)
279
+
280
+ return vs
281
+
282
+ vs = asyncio.run(run())
283
+
284
+ if vs:
285
+ print(f'{len(vs)} violations found')
286
+ sys.exit(1)
287
+
288
+
289
+ ##
290
+
291
+
292
+ def _build_parser() -> argparse.ArgumentParser:
293
+ parser = argparse.ArgumentParser()
294
+
295
+ subparsers = parser.add_subparsers()
296
+
297
+ parser_check = subparsers.add_parser('check')
298
+ parser_check.add_argument('roots', nargs='+')
299
+ parser_check.set_defaults(func=_check_cmd)
300
+
301
+ return parser
302
+
303
+
304
+ def _main(argv: ta.Sequence[str] | None = None) -> None:
305
+ logs.configure_standard_logging('INFO')
306
+
307
+ parser = _build_parser()
308
+ args = parser.parse_args(argv)
309
+ if not getattr(args, 'func', None):
310
+ parser.print_help()
311
+ else:
312
+ args.func(args)
313
+
314
+
315
+ if __name__ == '__main__':
316
+ _main()
omdev/pyproject/cli.py CHANGED
@@ -308,6 +308,7 @@ def _pkg_cmd(args) -> None:
308
308
 
309
309
  build_output_dir = 'dist'
310
310
  run_build = bool(args.build)
311
+ add_revision = bool(args.revision)
311
312
 
312
313
  if run_build:
313
314
  os.makedirs(build_output_dir, exist_ok=True)
@@ -322,6 +323,7 @@ def _pkg_cmd(args) -> None:
322
323
  ).gen,
323
324
  run_build=run_build,
324
325
  build_output_dir=build_output_dir,
326
+ add_revision=add_revision,
325
327
  ))
326
328
  for dir_name in run.cfg().pkgs
327
329
  ]
@@ -350,6 +352,7 @@ def _build_parser() -> argparse.ArgumentParser:
350
352
 
351
353
  parser_resolve = subparsers.add_parser('pkg')
352
354
  parser_resolve.add_argument('-b', '--build', action='store_true')
355
+ parser_resolve.add_argument('-r', '--revision', action='store_true')
353
356
  parser_resolve.add_argument('cmd', nargs='?')
354
357
  parser_resolve.add_argument('args', nargs=argparse.REMAINDER)
355
358
  parser_resolve.set_defaults(func=_pkg_cmd)
omdev/pyproject/pkg.py CHANGED
@@ -30,6 +30,7 @@ from omlish.lite.cached import cached_nullary
30
30
  from omlish.lite.logs import log
31
31
 
32
32
  from ..toml.writer import TomlWriter
33
+ from ..tools.revisions import GitRevisionAdder
33
34
 
34
35
 
35
36
  class PyprojectPackageGenerator:
@@ -179,6 +180,8 @@ class PyprojectPackageGenerator:
179
180
  def _run_build(
180
181
  self,
181
182
  build_output_dir: ta.Optional[str] = None,
183
+ *,
184
+ add_revision: bool = False,
182
185
  ) -> None:
183
186
  subprocess.check_call(
184
187
  [
@@ -189,8 +192,12 @@ class PyprojectPackageGenerator:
189
192
  cwd=self._build_dir(),
190
193
  )
191
194
 
195
+ dist_dir = os.path.join(self._build_dir(), 'dist')
196
+
197
+ if add_revision:
198
+ GitRevisionAdder().add_to(dist_dir)
199
+
192
200
  if build_output_dir is not None:
193
- dist_dir = os.path.join(self._build_dir(), 'dist')
194
201
  for fn in os.listdir(dist_dir):
195
202
  shutil.copyfile(os.path.join(dist_dir, fn), os.path.join(build_output_dir, fn))
196
203
 
@@ -201,6 +208,7 @@ class PyprojectPackageGenerator:
201
208
  *,
202
209
  run_build: bool = False,
203
210
  build_output_dir: ta.Optional[str] = None,
211
+ add_revision: bool = False,
204
212
  ) -> str:
205
213
  log.info('Generating pyproject package: %s -> %s', self._dir_name, self._build_root)
206
214
 
@@ -211,6 +219,9 @@ class PyprojectPackageGenerator:
211
219
  self._symlink_standard_files()
212
220
 
213
221
  if run_build:
214
- self._run_build(build_output_dir)
222
+ self._run_build(
223
+ build_output_dir,
224
+ add_revision=add_revision,
225
+ )
215
226
 
216
227
  return self._build_dir()
omdev/scripts/execrss.py CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ # @omlish-script
2
3
  import resource
3
4
  import sys
4
5
 
omdev/scripts/interp.py CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  # noinspection DuplicatedCode
3
+ # @omlish-lite
4
+ # @omlish-script
3
5
  # @omdev-amalg-output ../interp/cli.py
4
6
  # ruff: noqa: UP007
5
7
  """
@@ -29,13 +31,18 @@ import threading
29
31
  import typing as ta
30
32
 
31
33
 
34
+ # ../../versioning/versions.py
32
35
  VersionLocalType = ta.Tuple[ta.Union[int, str], ...]
33
36
  VersionCmpPrePostDevType = ta.Union['InfinityVersionType', 'NegativeInfinityVersionType', ta.Tuple[str, int]]
34
37
  _VersionCmpLocalType0 = ta.Tuple[ta.Union[ta.Tuple[int, str], ta.Tuple['NegativeInfinityVersionType', ta.Union[int, str]]], ...] # noqa
35
38
  VersionCmpLocalType = ta.Union['NegativeInfinityVersionType', _VersionCmpLocalType0]
36
39
  VersionCmpKey = ta.Tuple[int, ta.Tuple[int, ...], VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpLocalType] # noqa
37
40
  VersionComparisonMethod = ta.Callable[[VersionCmpKey, VersionCmpKey], bool]
41
+
42
+ # ../../../omlish/lite/check.py
38
43
  T = ta.TypeVar('T')
44
+
45
+ # ../../versioning/specifiers.py
39
46
  UnparsedVersion = ta.Union['Version', str]
40
47
  UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
41
48
  CallableVersionOperator = ta.Callable[['Version', str], bool]