omdev 0.0.0.dev486__py3-none-any.whl → 0.0.0.dev500__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.
- omdev/.omlish-manifests.json +1 -1
- omdev/README.md +51 -0
- omdev/__about__.py +4 -2
- omdev/ci/cli.py +1 -1
- omdev/cli/clicli.py +37 -7
- omdev/dataclasses/cli.py +1 -1
- omdev/interp/cli.py +1 -1
- omdev/interp/types.py +3 -2
- omdev/interp/uv/provider.py +36 -0
- omdev/manifests/main.py +1 -1
- omdev/packaging/revisions.py +1 -1
- omdev/py/tools/pipdepup.py +150 -93
- omdev/pyproject/cli.py +1 -1
- omdev/pyproject/pkg.py +1 -1
- omdev/pyproject/reqs.py +8 -7
- omdev/pyproject/tools/aboutdeps.py +5 -0
- omdev/scripts/ci.py +361 -25
- omdev/scripts/interp.py +43 -8
- omdev/scripts/lib/logs.py +117 -21
- omdev/scripts/pyproject.py +415 -39
- omdev/tools/git/cli.py +43 -13
- omdev/tools/json/formats.py +2 -0
- omdev/tools/jsonview/cli.py +19 -61
- omdev/tools/jsonview/resources/jsonview.html.j2 +43 -0
- omdev/tools/pawk/README.md +195 -0
- omdev/tui/apps/edit/main.py +5 -1
- omdev/tui/apps/irc/app.py +28 -20
- omdev/tui/apps/irc/commands.py +1 -1
- omdev/tui/rich/__init__.py +12 -0
- omdev/tui/textual/__init__.py +41 -2
- omdev/tui/textual/app2.py +6 -1
- omdev/tui/textual/debug/__init__.py +10 -0
- omdev/tui/textual/debug/dominfo.py +151 -0
- omdev/tui/textual/debug/screen.py +24 -0
- omdev/tui/textual/devtools.py +187 -0
- omdev/tui/textual/logging2.py +20 -0
- omdev/tui/textual/types.py +45 -0
- {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev500.dist-info}/METADATA +10 -6
- {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev500.dist-info}/RECORD +43 -34
- {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev500.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev500.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev500.dist-info}/licenses/LICENSE +0 -0
- {omdev-0.0.0.dev486.dist-info → omdev-0.0.0.dev500.dist-info}/top_level.txt +0 -0
omdev/.omlish-manifests.json
CHANGED
omdev/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Overview
|
|
2
|
+
|
|
3
|
+
Development utilities and support code.
|
|
4
|
+
|
|
5
|
+
# Notable packages
|
|
6
|
+
|
|
7
|
+
- **[cli](https://github.com/wrmsr/omlish/blob/master/omdev/cli)** - The codebase's all-in-one CLI. This is not
|
|
8
|
+
installed as an entrypoint / command when this package is itself installed - that is separated into the `omdev-cli`
|
|
9
|
+
installable package so as to not pollute users' bin/ directories when depping this lib for its utility code.
|
|
10
|
+
|
|
11
|
+
- **[amalg](https://github.com/wrmsr/omlish/blob/master/omdev/amalg)** - The [amalgamator](#amalgamation).
|
|
12
|
+
|
|
13
|
+
- **[pyproject](https://github.com/wrmsr/omlish/blob/master/omdev/pyproject)**
|
|
14
|
+
([amalg](https://github.com/wrmsr/omlish/blob/master/omdev/scripts/pyproject.py)) - python project management tool.
|
|
15
|
+
wrangles but does not replace tools like venv, pip, setuptools, and uv. does things like sets up venvs, generates
|
|
16
|
+
[`.pkg`](https://github.com/wrmsr/omlish/blob/master/.pkg) directories and their `pyproject.toml`'s (from their
|
|
17
|
+
`__about__.py`'s), and packages them. this should grow to eat more and more of the Makefile. as it is amalgamated it
|
|
18
|
+
requires no installation and can just be dropped into other projects / repos.
|
|
19
|
+
|
|
20
|
+
- **[ci](https://github.com/wrmsr/omlish/blob/master/omdev/ci)**
|
|
21
|
+
([amalg](https://github.com/wrmsr/omlish/blob/master/omdev/scripts/ci.py)) - ci runner. given a
|
|
22
|
+
[`compose.yml`](https://github.com/wrmsr/omlish/blob/master/docker/compose.yml)
|
|
23
|
+
and requirements.txt files, takes care of building and caching of containers and venvs and execution of required ci
|
|
24
|
+
commands. detects and [natively uses](https://github.com/wrmsr/omlish/blob/master/omdev/ci/github/api/v2)
|
|
25
|
+
github-action's caching system. unifies ci execution between local dev and github runners.
|
|
26
|
+
|
|
27
|
+
- **[tools.json](https://github.com/wrmsr/omlish/blob/master/omdev/tools/json)** (cli: `om j`) - a tool for json-like
|
|
28
|
+
data, obviously in the vein of [jq](https://github.com/jqlang/jq) but using the internal
|
|
29
|
+
[jmespath](https://github.com/wrmsr/omlish/blob/master/omlish/specs/jmespath) engine. supports
|
|
30
|
+
[true streaming](https://github.com/wrmsr/omlish/blob/master/omlish/formats/json/stream) json input and output, as
|
|
31
|
+
well as [various other](https://github.com/wrmsr/omlish/blob/master/omdev/tools/json/formats.py) non-streaming input
|
|
32
|
+
formats.
|
|
33
|
+
|
|
34
|
+
- **[tools.git](https://github.com/wrmsr/omlish/blob/master/omdev/tools/git)** (cli: `om git`) - a tool for various lazy
|
|
35
|
+
git operations, including the one that (poorly) writes all of these commit messages.
|
|
36
|
+
|
|
37
|
+
# Amalgamation
|
|
38
|
+
|
|
39
|
+
Amalgamation is the process of stitching together multiple python source files into a single self-contained python
|
|
40
|
+
script. ['lite'](https://github.com/wrmsr/omlish/blob/master/omlish#lite-code) code is written in a style conducive to
|
|
41
|
+
this.
|
|
42
|
+
|
|
43
|
+
# Local storage
|
|
44
|
+
|
|
45
|
+
Some of this code, when asked, will store things on the local filesystem. The only directories used (outside of ones
|
|
46
|
+
explicitly specified as command or function arguments) are managed in
|
|
47
|
+
[home.paths](https://github.com/wrmsr/omlish/blob/master/omdev/home/paths.py), and are the following:
|
|
48
|
+
|
|
49
|
+
- `$OMLISH_HOME`, default of `~/.omlish` - persistent things like config and state.
|
|
50
|
+
- `$OMLISH_CACHE`, default of `~/.cache/omlish` - used for things like the local ci cache and
|
|
51
|
+
[various other](https://github.com/search?q=repo%3Awrmsr%2Fomlish+%22dcache.%22&type=code) cached data.
|
omdev/__about__.py
CHANGED
|
@@ -13,7 +13,7 @@ class Project(ProjectBase):
|
|
|
13
13
|
|
|
14
14
|
optional_dependencies = {
|
|
15
15
|
'black': [
|
|
16
|
-
'black ~= 25.
|
|
16
|
+
'black ~= 25.12',
|
|
17
17
|
],
|
|
18
18
|
|
|
19
19
|
'c': [
|
|
@@ -44,7 +44,9 @@ class Project(ProjectBase):
|
|
|
44
44
|
|
|
45
45
|
'tui': [
|
|
46
46
|
'rich ~= 14.2',
|
|
47
|
-
'textual ~=
|
|
47
|
+
'textual ~= 7.0', # [syntax]
|
|
48
|
+
'textual-dev ~= 1.8',
|
|
49
|
+
'textual-speedups ~= 0.2',
|
|
48
50
|
],
|
|
49
51
|
}
|
|
50
52
|
|
omdev/ci/cli.py
CHANGED
|
@@ -22,7 +22,7 @@ from omlish.argparse.cli import argparse_cmd
|
|
|
22
22
|
from omlish.lite.check import check
|
|
23
23
|
from omlish.lite.inject import inj
|
|
24
24
|
from omlish.logs.modules import get_module_logger
|
|
25
|
-
from omlish.logs.standard import configure_standard_logging
|
|
25
|
+
from omlish.logs.std.standard import configure_standard_logging
|
|
26
26
|
|
|
27
27
|
from .cache import DirectoryFileCache
|
|
28
28
|
from .ci import Ci
|
omdev/cli/clicli.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import dataclasses as dc
|
|
1
2
|
import inspect
|
|
2
3
|
import os
|
|
3
4
|
import re
|
|
4
5
|
import shlex
|
|
5
6
|
import subprocess
|
|
6
7
|
import sys
|
|
8
|
+
import time
|
|
7
9
|
import typing as ta
|
|
8
10
|
import urllib.parse
|
|
9
11
|
import urllib.request
|
|
@@ -13,6 +15,7 @@ from omlish import lang
|
|
|
13
15
|
from omlish.argparse import all as ap
|
|
14
16
|
from omlish.os.temp import temp_dir_context
|
|
15
17
|
|
|
18
|
+
from ..packaging.versions import Version
|
|
16
19
|
from ..pip import get_root_dists
|
|
17
20
|
from ..pip import lookup_latest_package_version
|
|
18
21
|
from . import install
|
|
@@ -87,22 +90,49 @@ class CliCli(ap.Cli):
|
|
|
87
90
|
|
|
88
91
|
#
|
|
89
92
|
|
|
93
|
+
@dc.dataclass()
|
|
94
|
+
class ReinstallWouldNotUpgradeError(Exception):
|
|
95
|
+
current_version: str
|
|
96
|
+
target_version: str
|
|
97
|
+
|
|
98
|
+
def __str__(self) -> str:
|
|
99
|
+
return f'Current version {self.current_version} is not older than target version {self.target_version} '
|
|
100
|
+
|
|
90
101
|
@ap.cmd(
|
|
91
102
|
ap.arg('--url', default=DEFAULT_REINSTALL_URL),
|
|
92
103
|
ap.arg('--local', action='store_true'),
|
|
93
104
|
ap.arg('--no-deps', action='store_true'),
|
|
94
105
|
ap.arg('--no-uv', action='store_true'),
|
|
95
106
|
ap.arg('--dry-run', action='store_true'),
|
|
107
|
+
ap.arg('--must-upgrade', action='store_true'),
|
|
108
|
+
ap.arg('--must-upgrade-loop', action='store_true'),
|
|
96
109
|
ap.arg('--version'),
|
|
97
110
|
ap.arg('extra_deps', nargs='*'),
|
|
98
111
|
)
|
|
99
112
|
def reinstall(self) -> None:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
113
|
+
current_version = __about__.__version__
|
|
114
|
+
|
|
115
|
+
while True:
|
|
116
|
+
latest_version = _parse_latest_version_str(lookup_latest_package_version(__package__.split('.')[0]))
|
|
117
|
+
|
|
118
|
+
if self.args.version is not None:
|
|
119
|
+
target_version: str = self.args.version
|
|
120
|
+
else:
|
|
121
|
+
target_version = latest_version
|
|
122
|
+
|
|
123
|
+
if self.args.must_upgrade or self.args.must_upgrade_loop:
|
|
124
|
+
current_vo = Version(current_version)
|
|
125
|
+
target_vo = Version(target_version)
|
|
126
|
+
if not (target_vo > current_vo):
|
|
127
|
+
ex = CliCli.ReinstallWouldNotUpgradeError(current_version, target_version)
|
|
128
|
+
if self.args.must_upgrade_loop:
|
|
129
|
+
print(ex)
|
|
130
|
+
time.sleep(1)
|
|
131
|
+
continue
|
|
132
|
+
else:
|
|
133
|
+
raise ex
|
|
134
|
+
|
|
135
|
+
break
|
|
106
136
|
|
|
107
137
|
#
|
|
108
138
|
|
|
@@ -186,7 +216,7 @@ class CliCli(ap.Cli):
|
|
|
186
216
|
|
|
187
217
|
#
|
|
188
218
|
|
|
189
|
-
print(f'Current version: {
|
|
219
|
+
print(f'Current version: {current_version}')
|
|
190
220
|
print(f'Latest version: {latest_version}')
|
|
191
221
|
print(f'Target version: {target_version}')
|
|
192
222
|
print()
|
omdev/dataclasses/cli.py
CHANGED
omdev/interp/cli.py
CHANGED
|
@@ -17,7 +17,7 @@ from omlish.lite.check import check
|
|
|
17
17
|
from omlish.lite.inject import Injector
|
|
18
18
|
from omlish.lite.inject import inj
|
|
19
19
|
from omlish.lite.runtime import check_lite_runtime_version
|
|
20
|
-
from omlish.logs.standard import configure_standard_logging
|
|
20
|
+
from omlish.logs.std.standard import configure_standard_logging
|
|
21
21
|
|
|
22
22
|
from .inject import bind_interp
|
|
23
23
|
from .resolvers import InterpResolver
|
omdev/interp/types.py
CHANGED
|
@@ -85,9 +85,10 @@ class InterpSpecifier:
|
|
|
85
85
|
def parse(cls, s: str) -> 'InterpSpecifier':
|
|
86
86
|
s, o = InterpOpts.parse_suffix(s)
|
|
87
87
|
if not any(s.startswith(o) for o in Specifier.OPERATORS):
|
|
88
|
-
s = '~=' + s
|
|
89
88
|
if s.count('.') < 2:
|
|
90
|
-
s
|
|
89
|
+
s = '~=' + s + '.0'
|
|
90
|
+
else:
|
|
91
|
+
s = '==' + s
|
|
91
92
|
return cls(
|
|
92
93
|
specifier=Specifier(s),
|
|
93
94
|
opts=o,
|
omdev/interp/uv/provider.py
CHANGED
|
@@ -5,7 +5,9 @@ uv run --python 3.11.6 pip
|
|
|
5
5
|
uv venv --python 3.11.6 --seed barf
|
|
6
6
|
python3 -m venv barf && barf/bin/pip install uv && barf/bin/uv venv --python 3.11.6 --seed barf2
|
|
7
7
|
uv python find '3.13.10'
|
|
8
|
+
uv python list --output-format=json
|
|
8
9
|
"""
|
|
10
|
+
import dataclasses as dc
|
|
9
11
|
import typing as ta
|
|
10
12
|
|
|
11
13
|
from omlish.logs.protocols import LoggerLike
|
|
@@ -21,6 +23,34 @@ from .uv import Uv
|
|
|
21
23
|
##
|
|
22
24
|
|
|
23
25
|
|
|
26
|
+
@dc.dataclass(frozen=True)
|
|
27
|
+
class UvPythonListOutput:
|
|
28
|
+
key: str
|
|
29
|
+
version: str
|
|
30
|
+
|
|
31
|
+
@dc.dataclass(frozen=True)
|
|
32
|
+
class VersionParts:
|
|
33
|
+
major: int
|
|
34
|
+
minor: int
|
|
35
|
+
patch: int
|
|
36
|
+
|
|
37
|
+
version_parts: VersionParts
|
|
38
|
+
|
|
39
|
+
path: ta.Optional[str]
|
|
40
|
+
symlink: ta.Optional[str]
|
|
41
|
+
|
|
42
|
+
url: str
|
|
43
|
+
|
|
44
|
+
os: str # emscripten linux macos
|
|
45
|
+
variant: str # default freethreaded
|
|
46
|
+
implementation: str # cpython graalpy pyodide pypy
|
|
47
|
+
arch: str # aarch64 wasm32 x86_64
|
|
48
|
+
libc: str # gnu musl none
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
##
|
|
52
|
+
|
|
53
|
+
|
|
24
54
|
class UvInterpProvider(InterpProvider):
|
|
25
55
|
def __init__(
|
|
26
56
|
self,
|
|
@@ -40,3 +70,9 @@ class UvInterpProvider(InterpProvider):
|
|
|
40
70
|
|
|
41
71
|
async def get_installed_version(self, version: InterpVersion) -> Interp:
|
|
42
72
|
raise NotImplementedError
|
|
73
|
+
|
|
74
|
+
# async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
75
|
+
# return []
|
|
76
|
+
|
|
77
|
+
# async def install_version(self, version: InterpVersion) -> Interp:
|
|
78
|
+
# raise TypeError
|
omdev/manifests/main.py
CHANGED
|
@@ -5,7 +5,7 @@ import multiprocessing as mp
|
|
|
5
5
|
import os.path
|
|
6
6
|
|
|
7
7
|
from omlish.lite.json import json_dumps_pretty
|
|
8
|
-
from omlish.logs.standard import configure_standard_logging
|
|
8
|
+
from omlish.logs.std.standard import configure_standard_logging
|
|
9
9
|
|
|
10
10
|
from .building import ManifestBuilder
|
|
11
11
|
from .building import check_package_manifests
|
omdev/packaging/revisions.py
CHANGED
|
@@ -15,7 +15,7 @@ import zipfile
|
|
|
15
15
|
from omlish.lite.cached import cached_nullary
|
|
16
16
|
from omlish.lite.check import check
|
|
17
17
|
from omlish.logs.modules import get_module_logger
|
|
18
|
-
from omlish.logs.standard import configure_standard_logging
|
|
18
|
+
from omlish.logs.std.standard import configure_standard_logging
|
|
19
19
|
|
|
20
20
|
from ..git.revisions import get_git_revision
|
|
21
21
|
from .wheelfile import WheelFile
|
omdev/py/tools/pipdepup.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
2
|
TODO:
|
|
3
|
+
- output 2 tables lol
|
|
3
4
|
- min_time_since_prev_version
|
|
5
|
+
- without this the min age is moot lol, can still catch a bad release at the same time of day just n days later
|
|
6
|
+
- * at least show 'suggested', 'suggested age', 'latest', 'latest age', 'number of releases between the 2' *
|
|
4
7
|
- how to handle non-linearity? new minor vers come out in parallel for diff major vers
|
|
5
8
|
- trie?
|
|
6
9
|
- find which reqs file + lineno to update
|
|
@@ -28,7 +31,7 @@ https://news.ycombinator.com/item?id=46005111
|
|
|
28
31
|
# ~> https://github.com/pypa/pip/blob/a52069365063ea813fe3a3f8bac90397c9426d35/src/pip/_internal/commands/list.py (25.3)
|
|
29
32
|
import dataclasses as dc
|
|
30
33
|
import datetime
|
|
31
|
-
import
|
|
34
|
+
import itertools
|
|
32
35
|
import os.path
|
|
33
36
|
import ssl
|
|
34
37
|
import typing as ta
|
|
@@ -310,16 +313,42 @@ class Package:
|
|
|
310
313
|
|
|
311
314
|
return datetime.datetime.fromisoformat(check.isinstance(ut, str)) # noqa
|
|
312
315
|
|
|
316
|
+
@cached.property
|
|
317
|
+
def version(self) -> Version:
|
|
318
|
+
return self.install.version
|
|
319
|
+
|
|
320
|
+
@cached.property
|
|
321
|
+
def filetype(self) -> ta.Literal['wheel', 'sdist']:
|
|
322
|
+
if self.install.link.is_wheel:
|
|
323
|
+
return 'wheel'
|
|
324
|
+
else:
|
|
325
|
+
return 'sdist'
|
|
326
|
+
|
|
327
|
+
unfiltered_candidates: ta.Sequence[Candidate] | None = None
|
|
313
328
|
candidates: ta.Sequence[Candidate] | None = None
|
|
314
329
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
330
|
+
latest_candidate: Candidate | None = None
|
|
331
|
+
suggested_candidate: Candidate | None = None
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def get_best_candidate(
|
|
335
|
+
pkg: Package,
|
|
336
|
+
finder: MyPackageFinder,
|
|
337
|
+
candidates: ta.Sequence[Package.Candidate],
|
|
338
|
+
) -> Package.Candidate | None:
|
|
339
|
+
candidates_by_install: ta.Mapping[InstallationCandidate, Package.Candidate] = col.make_map((
|
|
340
|
+
(c.install, c)
|
|
341
|
+
for c in candidates
|
|
342
|
+
), strict=True, identity=True)
|
|
318
343
|
|
|
319
|
-
|
|
320
|
-
|
|
344
|
+
evaluator = finder.make_candidate_evaluator(
|
|
345
|
+
project_name=pkg.dist.canonical_name,
|
|
346
|
+
)
|
|
347
|
+
best_install = evaluator.sort_best_candidate([c.install for c in candidates])
|
|
348
|
+
if best_install is None:
|
|
349
|
+
return None
|
|
321
350
|
|
|
322
|
-
|
|
351
|
+
return candidates_by_install[best_install]
|
|
323
352
|
|
|
324
353
|
|
|
325
354
|
def set_package_finder_info(
|
|
@@ -330,17 +359,18 @@ def set_package_finder_info(
|
|
|
330
359
|
max_uploaded_at: datetime.datetime | None = None,
|
|
331
360
|
min_time_since_prev_version: datetime.timedelta | None = None,
|
|
332
361
|
) -> None:
|
|
333
|
-
|
|
362
|
+
pkg.unfiltered_candidates = [
|
|
334
363
|
Package.Candidate(
|
|
335
364
|
c,
|
|
336
365
|
finder.get_link_pypi_dict(c.link),
|
|
337
366
|
)
|
|
338
367
|
for c in finder.find_all_candidates(pkg.dist.canonical_name)
|
|
339
368
|
]
|
|
340
|
-
pkg.candidates = candidates
|
|
341
369
|
|
|
342
370
|
#
|
|
343
371
|
|
|
372
|
+
candidates = pkg.unfiltered_candidates
|
|
373
|
+
|
|
344
374
|
if not pre:
|
|
345
375
|
# Remove prereleases
|
|
346
376
|
candidates = [
|
|
@@ -349,8 +379,16 @@ def set_package_finder_info(
|
|
|
349
379
|
if not c.install.version.is_prerelease
|
|
350
380
|
]
|
|
351
381
|
|
|
382
|
+
pkg.candidates = candidates
|
|
383
|
+
|
|
352
384
|
#
|
|
353
385
|
|
|
386
|
+
pkg.latest_candidate = get_best_candidate(pkg, finder, pkg.candidates)
|
|
387
|
+
|
|
388
|
+
#
|
|
389
|
+
|
|
390
|
+
suggested_candidates = candidates
|
|
391
|
+
|
|
354
392
|
if min_time_since_prev_version is not None:
|
|
355
393
|
# candidates_by_version = col.multi_map((c.install.version, c) for c in candidates)
|
|
356
394
|
# uploaded_at_by_version = {
|
|
@@ -359,46 +397,17 @@ def set_package_finder_info(
|
|
|
359
397
|
# }
|
|
360
398
|
raise NotImplementedError
|
|
361
399
|
|
|
362
|
-
#
|
|
363
|
-
|
|
364
400
|
if max_uploaded_at is not None:
|
|
365
|
-
|
|
401
|
+
suggested_candidates = [
|
|
366
402
|
c
|
|
367
|
-
for c in
|
|
403
|
+
for c in suggested_candidates
|
|
368
404
|
if not (
|
|
369
405
|
(c_dt := c.upload_time()) is not None and
|
|
370
406
|
c_dt > max_uploaded_at
|
|
371
407
|
)
|
|
372
408
|
]
|
|
373
409
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
candidates_by_install: ta.Mapping[InstallationCandidate, Package.Candidate] = col.make_map((
|
|
377
|
-
(c.install, c)
|
|
378
|
-
for c in candidates
|
|
379
|
-
), strict=True, identity=True)
|
|
380
|
-
|
|
381
|
-
evaluator = finder.make_candidate_evaluator(
|
|
382
|
-
project_name=pkg.dist.canonical_name,
|
|
383
|
-
)
|
|
384
|
-
best_install = evaluator.sort_best_candidate([c.install for c in candidates])
|
|
385
|
-
if best_install is None:
|
|
386
|
-
return
|
|
387
|
-
best_candidate = candidates_by_install[best_install]
|
|
388
|
-
|
|
389
|
-
#
|
|
390
|
-
|
|
391
|
-
remote_version = best_candidate.install.version
|
|
392
|
-
if best_candidate.install.link.is_wheel:
|
|
393
|
-
typ = 'wheel'
|
|
394
|
-
else:
|
|
395
|
-
typ = 'sdist'
|
|
396
|
-
|
|
397
|
-
pkg.latest_info = Package.LatestInfo(
|
|
398
|
-
candidate=best_candidate,
|
|
399
|
-
version=remote_version,
|
|
400
|
-
filetype=typ,
|
|
401
|
-
)
|
|
410
|
+
pkg.suggested_candidate = get_best_candidate(pkg, finder, suggested_candidates)
|
|
402
411
|
|
|
403
412
|
|
|
404
413
|
##
|
|
@@ -472,20 +481,20 @@ def format_for_json(
|
|
|
472
481
|
infos: list[dict[str, ta.Any]] = []
|
|
473
482
|
|
|
474
483
|
for pkg in pkgs:
|
|
475
|
-
|
|
484
|
+
latest = check.not_none(pkg.latest_candidate)
|
|
485
|
+
suggested = check.not_none(pkg.suggested_candidate)
|
|
476
486
|
|
|
477
487
|
info = {
|
|
478
488
|
'name': pkg.dist.raw_name,
|
|
479
489
|
'version': pkg.version(),
|
|
480
490
|
'location': pkg.dist.location or '',
|
|
481
491
|
'installer': pkg.dist.installer,
|
|
482
|
-
'latest_version': str(
|
|
483
|
-
'
|
|
492
|
+
'latest_version': str(latest.install.version),
|
|
493
|
+
'latest_upload_time': lut.isoformat() if (lut := latest.upload_time()) is not None else None,
|
|
494
|
+
'suggested_version': str(suggested.install.version),
|
|
495
|
+
'suggested_upload_time': sut.isoformat() if (sut := suggested.upload_time()) is not None else None,
|
|
484
496
|
}
|
|
485
497
|
|
|
486
|
-
if (l_ut := latest_info.candidate.upload_time()) is not None:
|
|
487
|
-
info['latest_age'] = human_round_td(now_utc() - l_ut)
|
|
488
|
-
|
|
489
498
|
if editable_project_location := pkg.dist.editable_project_location:
|
|
490
499
|
info['editable_project_location'] = editable_project_location
|
|
491
500
|
|
|
@@ -500,46 +509,87 @@ def format_for_json(
|
|
|
500
509
|
def format_for_columns(pkgs: ta.Sequence[Package]) -> tuple[list[list[str]], list[str]]:
|
|
501
510
|
"""Convert the package data into something usable by output_package_listing_columns."""
|
|
502
511
|
|
|
503
|
-
header = [
|
|
512
|
+
header = [
|
|
513
|
+
'Package',
|
|
514
|
+
'Current',
|
|
504
515
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
wheel_file = dist.read_text('WHEEL')
|
|
508
|
-
except FileNotFoundError:
|
|
509
|
-
return None
|
|
510
|
-
return email.parser.Parser().parsestr(wheel_file).get('Build')
|
|
511
|
-
|
|
512
|
-
build_tags = [wheel_build_tag(p.dist) for p in pkgs]
|
|
513
|
-
has_build_tags = any(build_tags)
|
|
514
|
-
if has_build_tags:
|
|
515
|
-
header.append('Build')
|
|
516
|
-
|
|
517
|
-
has_editables = any(x.dist.editable for x in pkgs)
|
|
518
|
-
if has_editables:
|
|
519
|
-
header.append('Editable project location')
|
|
520
|
-
|
|
521
|
-
data = []
|
|
522
|
-
for i, proj in enumerate(pkgs):
|
|
523
|
-
# if we're working on the 'outdated' list, separate out the latest_version and type
|
|
524
|
-
row = [proj.dist.raw_name, proj.dist.raw_version]
|
|
525
|
-
|
|
526
|
-
latest_info = check.not_none(proj.latest_info)
|
|
527
|
-
row.append(str(latest_info.version))
|
|
528
|
-
if (l_ut := latest_info.candidate.upload_time()) is not None:
|
|
529
|
-
row.append(human_round_td(now_utc() - l_ut))
|
|
530
|
-
else:
|
|
531
|
-
row.append('')
|
|
532
|
-
row.append(latest_info.filetype)
|
|
516
|
+
'Suggested',
|
|
517
|
+
'Age',
|
|
533
518
|
|
|
534
|
-
|
|
535
|
-
|
|
519
|
+
'Latest',
|
|
520
|
+
'Age',
|
|
521
|
+
]
|
|
536
522
|
|
|
537
|
-
|
|
538
|
-
|
|
523
|
+
# def wheel_build_tag(dist: BaseDistribution) -> str | None:
|
|
524
|
+
# try:
|
|
525
|
+
# wheel_file = dist.read_text('WHEEL')
|
|
526
|
+
# except FileNotFoundError:
|
|
527
|
+
# return None
|
|
528
|
+
# return email.parser.Parser().parsestr(wheel_file).get('Build')
|
|
539
529
|
|
|
540
|
-
|
|
530
|
+
# build_tags = [wheel_build_tag(p.dist) for p in pkgs]
|
|
531
|
+
# has_build_tags = any(build_tags)
|
|
532
|
+
# if has_build_tags:
|
|
533
|
+
# header.append('Build')
|
|
541
534
|
|
|
542
|
-
|
|
535
|
+
# has_editables = any(x.dist.editable for x in pkgs)
|
|
536
|
+
# if has_editables:
|
|
537
|
+
# header.append('Editable project location')
|
|
538
|
+
|
|
539
|
+
rows = []
|
|
540
|
+
for pkg in pkgs:
|
|
541
|
+
sc = check.not_none(pkg.suggested_candidate)
|
|
542
|
+
lc = check.not_none(pkg.latest_candidate)
|
|
543
|
+
|
|
544
|
+
row = [
|
|
545
|
+
pkg.dist.raw_name,
|
|
546
|
+
pkg.dist.raw_version,
|
|
547
|
+
]
|
|
548
|
+
|
|
549
|
+
def add_c(c):
|
|
550
|
+
if c is None:
|
|
551
|
+
row.extend(['', ''])
|
|
552
|
+
return
|
|
553
|
+
|
|
554
|
+
row.append(str(c.version))
|
|
555
|
+
|
|
556
|
+
if (l_ut := c.upload_time()) is not None:
|
|
557
|
+
row.append(human_round_td(now_utc() - l_ut))
|
|
558
|
+
else:
|
|
559
|
+
row.append('')
|
|
560
|
+
|
|
561
|
+
add_c(sc if sc.version != pkg.dist.version else None)
|
|
562
|
+
add_c(lc if sc is not lc else None)
|
|
563
|
+
|
|
564
|
+
# if has_build_tags:
|
|
565
|
+
# row.append(build_tags[i] or '')
|
|
566
|
+
|
|
567
|
+
# if has_editables:
|
|
568
|
+
# row.append(pkg.dist.editable_project_location or '')
|
|
569
|
+
|
|
570
|
+
rows.append(row)
|
|
571
|
+
|
|
572
|
+
return rows, header
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _tabulate(
|
|
576
|
+
rows: ta.Iterable[ta.Iterable[ta.Any]],
|
|
577
|
+
*,
|
|
578
|
+
sep: str = ' ',
|
|
579
|
+
) -> tuple[list[str], list[int]]:
|
|
580
|
+
"""
|
|
581
|
+
Return a list of formatted rows and a list of column sizes.
|
|
582
|
+
|
|
583
|
+
For example::
|
|
584
|
+
|
|
585
|
+
>>> tabulate([['foobar', 2000], [0xdeadbeef]])
|
|
586
|
+
(['foobar 2000', '3735928559'], [10, 4])
|
|
587
|
+
"""
|
|
588
|
+
|
|
589
|
+
rows = [tuple(map(str, row)) for row in rows]
|
|
590
|
+
sizes = [max(map(len, col)) for col in itertools.zip_longest(*rows, fillvalue='')]
|
|
591
|
+
table = [sep.join(map(str.ljust, row, sizes)).rstrip() for row in rows]
|
|
592
|
+
return table, sizes
|
|
543
593
|
|
|
544
594
|
|
|
545
595
|
def render_package_listing_columns(data: list[list[str]], header: list[str]) -> list[str]:
|
|
@@ -547,12 +597,11 @@ def render_package_listing_columns(data: list[list[str]], header: list[str]) ->
|
|
|
547
597
|
if len(data) > 0:
|
|
548
598
|
data.insert(0, header)
|
|
549
599
|
|
|
550
|
-
|
|
551
|
-
pkg_strings, sizes = tabulate(data)
|
|
600
|
+
pkg_strings, sizes = _tabulate(data, sep=' ')
|
|
552
601
|
|
|
553
602
|
# Create and add a separator.
|
|
554
603
|
if len(data) > 0:
|
|
555
|
-
pkg_strings.insert(1, '
|
|
604
|
+
pkg_strings.insert(1, ' '.join('-' * x for x in sizes))
|
|
556
605
|
|
|
557
606
|
return pkg_strings
|
|
558
607
|
|
|
@@ -565,12 +614,14 @@ def _main() -> None:
|
|
|
565
614
|
|
|
566
615
|
parser = argparse.ArgumentParser()
|
|
567
616
|
parser.add_argument('--exclude', action='append', dest='excludes')
|
|
568
|
-
parser.add_argument('--min-age-h', type=float, default=
|
|
569
|
-
parser.add_argument('-P', '--parallelism', type=int, default=
|
|
617
|
+
parser.add_argument('--min-age-h', type=float, default=24)
|
|
618
|
+
parser.add_argument('-P', '--parallelism', type=int, default=3)
|
|
570
619
|
parser.add_argument('--json', action='store_true')
|
|
571
620
|
args = parser.parse_args()
|
|
572
621
|
|
|
573
|
-
max_uploaded_at: datetime.datetime | None =
|
|
622
|
+
max_uploaded_at: datetime.datetime | None = None
|
|
623
|
+
if args.min_age_h is not None:
|
|
624
|
+
max_uploaded_at = now_utc() - datetime.timedelta(hours=args.min_age_h)
|
|
574
625
|
min_time_since_prev_version: datetime.timedelta | None = None # datetime.timedelta(days=1)
|
|
575
626
|
|
|
576
627
|
#
|
|
@@ -608,21 +659,27 @@ def _main() -> None:
|
|
|
608
659
|
|
|
609
660
|
#
|
|
610
661
|
|
|
611
|
-
|
|
662
|
+
outdated_pkgs = [
|
|
612
663
|
pkg
|
|
613
664
|
for pkg in pkgs
|
|
614
|
-
if (li := pkg.
|
|
665
|
+
if (li := pkg.latest_candidate) is not None
|
|
615
666
|
and li.version > pkg.dist.version
|
|
616
667
|
]
|
|
617
668
|
|
|
618
|
-
|
|
669
|
+
outdated_pkgs.sort(key=lambda x: x.dist.raw_name)
|
|
619
670
|
|
|
620
671
|
#
|
|
621
672
|
|
|
622
673
|
if args.json:
|
|
623
|
-
print(json.dumps_pretty(format_for_json(
|
|
674
|
+
print(json.dumps_pretty(format_for_json(outdated_pkgs)))
|
|
675
|
+
|
|
624
676
|
else:
|
|
625
|
-
|
|
677
|
+
# stable_pkgs, unstable_pkgs = col.partition(
|
|
678
|
+
# outdated_pkgs,
|
|
679
|
+
# lambda pkg: pkg.latest_candidate is pkg.suggested_candidate,
|
|
680
|
+
# )
|
|
681
|
+
|
|
682
|
+
print('\n'.join(render_package_listing_columns(*format_for_columns(outdated_pkgs))))
|
|
626
683
|
|
|
627
684
|
|
|
628
685
|
if __name__ == '__main__':
|
omdev/pyproject/cli.py
CHANGED
|
@@ -40,7 +40,7 @@ from omlish.formats.toml.parser import toml_loads
|
|
|
40
40
|
from omlish.lite.cached import cached_nullary
|
|
41
41
|
from omlish.lite.check import check
|
|
42
42
|
from omlish.lite.runtime import check_lite_runtime_version
|
|
43
|
-
from omlish.logs.standard import configure_standard_logging
|
|
43
|
+
from omlish.logs.std.standard import configure_standard_logging
|
|
44
44
|
|
|
45
45
|
from .configs import PyprojectConfig
|
|
46
46
|
from .configs import PyprojectConfigPreparer
|
omdev/pyproject/pkg.py
CHANGED
|
@@ -587,7 +587,7 @@ class _PyprojectRsPackageGenerator(_PyprojectExtensionPackageGenerator):
|
|
|
587
587
|
# `sdist.add_defaults` as an unbound function, not a bound method:
|
|
588
588
|
# https://github.com/pypa/setuptools/blob/9c4d383631d3951fcae0afd73b5d08ff5a262976/setuptools/command/egg_info.py#L581
|
|
589
589
|
from setuptools.command.sdist import sdist # noqa
|
|
590
|
-
sdist
|
|
590
|
+
setattr(sdist, 'add_defaults', (lambda old: lambda sdist: _sdist_add_defaults(old, sdist))(sdist.add_defaults)) # noqa
|
|
591
591
|
|
|
592
592
|
_patch_sdist()
|
|
593
593
|
|