record rich results in verfile and get rid of Result & VersData types

Use RichResult to replace Result; Result was RichResult plus entry name.
This commit is contained in:
lilydjwg 2024-03-14 18:28:25 +08:00
parent f29bdee6a3
commit de1a3c6fc2
5 changed files with 56 additions and 37 deletions

View file

@ -88,7 +88,7 @@ def main() -> None:
if options.ver_files is not None: if options.ver_files is not None:
newverf = options.ver_files[1] newverf = options.ver_files[1]
vers = core.read_verfile(newverf) vers = core.read_verfile(newverf)
vers.update({k: r.version for k, r in results.items()}) vers.update(results)
core.write_verfile(newverf, vers) core.write_verfile(newverf, vers)
if args.failures and has_failures: if args.failures and has_failures:

View file

@ -1,5 +1,5 @@
# MIT licensed # MIT licensed
# Copyright (c) 2013-2020 lilydjwg <lilydjwg@gmail.com>, et al. # Copyright (c) 2013-2020, 2024 lilydjwg <lilydjwg@gmail.com>, et al.
from __future__ import annotations from __future__ import annotations
@ -20,6 +20,7 @@ from importlib import import_module
import re import re
import contextvars import contextvars
import json import json
import dataclasses
import structlog import structlog
@ -36,7 +37,7 @@ import platformdirs
from .lib import nicelogger from .lib import nicelogger
from . import slogconf from . import slogconf
from .util import ( from .util import (
Entry, Entries, KeyManager, RawResult, RichResult, Result, VersData, Entry, Entries, KeyManager, RawResult, RichResult, ResultData,
FunctionWorker, GetVersionError, FunctionWorker, GetVersionError,
FileLoadError, EntryWaiter, FileLoadError, EntryWaiter,
) )
@ -126,7 +127,7 @@ def safe_overwrite(file: Path, data: Union[bytes, str], *,
# if the above write failed (because disk is full etc), the old data should be kept # if the above write failed (because disk is full etc), the old data should be kept
os.rename(tmpname, resolved_path) os.rename(tmpname, resolved_path)
def read_verfile(file: Path) -> VersData: def read_verfile(file: Path) -> ResultData:
try: try:
with open(file) as f: with open(file) as f:
data = f.read() data = f.read()
@ -142,17 +143,35 @@ def read_verfile(file: Path) -> VersData:
name, ver = l.rstrip().split(None, 1) name, ver = l.rstrip().split(None, 1)
v[name] = ver v[name] = ver
if v.get('version') is None:
v = {k: RichResult(version=a) for k, a in v.items()}
elif v['version'] == 2:
v = {k: RichResult(**a) for k, a in v['data'].items()}
else:
raise Exception('unknown verfile version', v['version'])
return v return v
def write_verfile(file: Path, versions: VersData) -> None: def write_verfile(file: Path, versions: ResultData) -> None:
# sort and indent to make it friendly to human and git d = {
'version': 2,
# sort and indent to make it friendly to human and git
'data': dict(sorted(versions.items())),
}
data = json.dumps( data = json.dumps(
dict(sorted(versions.items())), d,
indent=2, indent = 2,
ensure_ascii=False, ensure_ascii = False,
default = json_encode,
) + '\n' ) + '\n'
safe_overwrite(file, data) safe_overwrite(file, data)
def json_encode(obj):
if isinstance(obj, RichResult):
d = {k: v for k, v in dataclasses.asdict(obj).items() if v is not None}
return d
raise TypeError(obj)
class Options(NamedTuple): class Options(NamedTuple):
ver_files: Optional[Tuple[Path, Path]] ver_files: Optional[Tuple[Path, Path]]
max_concurrency: int max_concurrency: int
@ -325,7 +344,7 @@ def apply_list_options(
return versions[-1] return versions[-1]
def _process_result(r: RawResult) -> Union[Result, Exception]: def _process_result(r: RawResult) -> Union[RichResult, Exception]:
version = r.version version = r.version
conf = r.conf conf = r.conf
name = r.name name = r.name
@ -362,7 +381,12 @@ def _process_result(r: RawResult) -> Union[Result, Exception]:
try: try:
version_str = substitute_version(version_str, conf) version_str = substitute_version(version_str, conf)
return Result(name, version_str, conf, url, gitref, revision) return RichResult(
version = version_str,
url = url,
gitref = gitref,
revision = revision,
)
except (ValueError, re.error) as e: except (ValueError, re.error) as e:
logger.exception('error occurred in version substitutions', name=name) logger.exception('error occurred in version substitutions', name=name)
return e return e
@ -371,15 +395,19 @@ def _process_result(r: RawResult) -> Union[Result, Exception]:
return ValueError('no version returned') return ValueError('no version returned')
def check_version_update( def check_version_update(
oldvers: VersData, oldvers: ResultData,
r: Result, name: str,
r: RichResult,
verbose: bool, verbose: bool,
) -> None: ) -> None:
oldver = oldvers.get(r.name, None) if old_result := oldvers.get(name):
oldver = old_result.version
else:
oldver = None
if not oldver or oldver != r.version: if not oldver or oldver != r.version:
logger.info( logger.info(
'updated', 'updated',
name = r.name, name = name,
version = r.version, version = r.version,
old_version = oldver, old_version = oldver,
url = r.url, url = r.url,
@ -387,10 +415,10 @@ def check_version_update(
else: else:
# provide visible user feedback if it was the only entry # provide visible user feedback if it was the only entry
level = logging.INFO if verbose else logging.DEBUG level = logging.INFO if verbose else logging.DEBUG
logger.log(level, 'up-to-date', name=r.name, version=r.version, url=r.url) logger.log(level, 'up-to-date', name=name, version=r.version, url=r.url)
async def process_result( async def process_result(
oldvers: VersData, oldvers: ResultData,
result_q: Queue[RawResult], result_q: Queue[RawResult],
entry_waiter: EntryWaiter, entry_waiter: EntryWaiter,
verbose: bool = False, verbose: bool = False,
@ -409,9 +437,9 @@ async def process_result(
entry_waiter.set_exception(r.name, r1) entry_waiter.set_exception(r.name, r1)
has_failures = True has_failures = True
continue continue
check_version_update(oldvers, r1, verbose) check_version_update(oldvers, r.name, r1, verbose)
entry_waiter.set_result(r1.name, r1.version) entry_waiter.set_result(r.name, r1.version)
ret[r1.name] = r1 ret[r.name] = r1
except asyncio.CancelledError: except asyncio.CancelledError:
return ret, has_failures return ret, has_failures

View file

@ -106,8 +106,8 @@ def cmp() -> None:
oldverf = opt.ver_files[0] oldverf = opt.ver_files[0]
newverf = opt.ver_files[1] newverf = opt.ver_files[1]
oldvers = core.read_verfile(oldverf) oldvers = {k: v.version for k, v in core.read_verfile(oldverf).items()}
newvers = core.read_verfile(newverf) newvers = {k: v.version for k, v in core.read_verfile(newverf).items()}
differences = [] differences = []

View file

@ -39,7 +39,6 @@ logger = structlog.get_logger(logger_name=__name__)
Entry = Dict[str, Any] Entry = Dict[str, Any]
Entry.__doc__ = '''The configuration `dict` for an entry.''' Entry.__doc__ = '''The configuration `dict` for an entry.'''
Entries = Dict[str, Entry] Entries = Dict[str, Entry]
VersData = Dict[str, str]
if sys.version_info[:2] >= (3, 11): if sys.version_info[:2] >= (3, 11):
from typing import LiteralString from typing import LiteralString
@ -145,15 +144,7 @@ RawResult.name.__doc__ = 'The name (table name) of the entry.'
RawResult.version.__doc__ = 'The result from the check.' RawResult.version.__doc__ = 'The result from the check.'
RawResult.conf.__doc__ = 'The entry configuration (table content) of the entry.' RawResult.conf.__doc__ = 'The entry configuration (table content) of the entry.'
class Result(NamedTuple): ResultData = Dict[str, RichResult]
name: str
version: str
conf: Entry
url: Optional[str]
gitref: Optional[str]
revision: Optional[str]
ResultData = Dict[str, Result]
class BaseWorker: class BaseWorker:
'''The base class for defining `Worker` classes for source plugins. '''The base class for defining `Worker` classes for source plugins.

View file

@ -1,11 +1,11 @@
# MIT licensed # MIT licensed
# Copyright (c) 2020 lilydjwg <lilydjwg@gmail.com>, et al. # Copyright (c) 2020, 2024 lilydjwg <lilydjwg@gmail.com>, et al.
import asyncio import asyncio
import structlog import structlog
import os import os
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Dict
if TYPE_CHECKING: if TYPE_CHECKING:
import tomli as tomllib import tomli as tomllib
@ -20,13 +20,13 @@ import pytest_asyncio
from nvchecker import core from nvchecker import core
from nvchecker import __main__ as main from nvchecker import __main__ as main
from nvchecker.util import Entries, VersData, RawResult from nvchecker.util import Entries, ResultData, RawResult
use_keyfile = False use_keyfile = False
async def run( async def run(
entries: Entries, max_concurrency: int = 20, entries: Entries, max_concurrency: int = 20,
) -> VersData: ) -> Dict[str, str]:
task_sem = asyncio.Semaphore(max_concurrency) task_sem = asyncio.Semaphore(max_concurrency)
result_q: asyncio.Queue[RawResult] = asyncio.Queue() result_q: asyncio.Queue[RawResult] = asyncio.Queue()
keyfile = os.environ.get('KEYFILE') keyfile = os.environ.get('KEYFILE')
@ -43,7 +43,7 @@ async def run(
keymanager, entry_waiter, 1, {}, keymanager, entry_waiter, 1, {},
) )
oldvers: VersData = {} oldvers: ResultData = {}
result_coro = core.process_result(oldvers, result_q, entry_waiter) result_coro = core.process_result(oldvers, result_q, entry_waiter)
runner_coro = core.run_tasks(futures) runner_coro = core.run_tasks(futures)