freelance-project-34-market.../python/cli.py
LLM 12c9ad8fe0 [+] refactor test runner into commands_typed/tests.py with pyproject.toml config
1. add commands_typed/tests.py with run_tests(), collect_tests(), filter_tests();
  2. support test_names as dotted prefixes or * glob patterns via fnmatch;
  3. add dry-run mode via collect subcommand in __main__ with argparse;
  4. add [tool.online-fxreader-pr34.tests] section: search_paths, test_names, discovery_paths;
  5. extend PyProject dataclass with Tests nested class in cli_bootstrap.py;
  6. parse tests config in pyproject_load via check_dict/check_list;
  7. add tests() method to commands_typed/cli.py base CLI class;
  8. simplify python/cli.py to delegate to self.tests(project_name, argv);
  9. auto-patch PYTHONPATH from search_paths before running tests;
  10. narrow unittest discover start_dir from common prefix of test_names;
2026-04-09 09:00:00 +00:00

265 lines
6.0 KiB
Python

import sys
import shutil
import glob
import io
import copy
import subprocess
import pathlib
import logging
import enum
import argparse
import dataclasses
from typing import (
Optional,
# override,
)
from typing_extensions import (
override,
)
from online.fxreader.pr34.commands_typed.logging import setup as logging_setup
from online.fxreader.pr34.commands_typed import cli as _cli
from online.fxreader.pr34.commands_typed import cli_bootstrap
logging_setup()
logger = logging.getLogger(__name__)
class Command(enum.Enum):
mypy = 'mypy'
pyright = 'pyright'
ruff = 'ruff'
deploy_wheel = 'deploy:wheel'
tests = 'tests'
meson_setup = 'meson:setup'
meson_install_list = 'meson:install:list'
module_switch = 'module:switch'
pyrefly = 'pyrefly'
@dataclasses.dataclass
class Settings(
_cli.DistSettings,
):
base_dir: pathlib.Path = pathlib.Path(__file__).parent.parent
build_dir: pathlib.Path = base_dir / 'tmp' / 'build'
wheel_dir: pathlib.Path = base_dir / 'deps' / 'dist'
env_path: pathlib.Path = cli_bootstrap.BootstrapSettings.get().env_path
python_path: pathlib.Path = pathlib.Path(sys.executable)
class CLI(_cli.CLI):
def __init__(self) -> None:
self.settings = Settings()
self._projects: dict[str, _cli.Project] = {
'online.fxreader.pr34': _cli.Project(
source_dir=self.settings.base_dir / 'python',
build_dir=self.settings.base_dir / 'tmp' / 'online' / 'fxreader' / 'pr34' / 'build',
dest_dir=self.settings.base_dir
/ 'tmp'
/ 'online'
/ 'fxreader'
/ 'pr34'
/ 'install',
meson_path=self.settings.base_dir / 'python' / 'meson.build',
),
'online.fxreader.pr34.commands_typed.archlinux': _cli.Project(
source_dir=self.settings.base_dir
/ 'meson'
/ 'online'
/ 'fxreader'
/ 'pr34'
/ 'commands_typed'
/ 'archlinux',
build_dir=self.settings.base_dir
/ 'tmp'
/ 'online'
/ 'fxreader'
/ 'pr34'
/ 'commands_typed'
/ 'archlinux'
/ 'build',
dest_dir=self.settings.base_dir
/ 'tmp'
/ 'online'
/ 'fxreader'
/ 'pr34'
/ 'commands_typed'
/ 'archlinux'
/ 'install',
meson_path=self.settings.base_dir
/ 'meson'
/ 'online'
/ 'fxreader'
/ 'pr34'
/ 'commands_typed'
/ 'archlinux'
/ 'meson.build',
),
}
self._dependencies: dict[str, _cli.Dependency] = dict()
@override
@property
def dist_settings(self) -> _cli.DistSettings:
return self.settings
@override
@property
def projects(self) -> dict[str, _cli.Project]:
return self._projects
def mypy(
self,
argv: list[str],
) -> None:
import online.fxreader.pr34.commands_typed.mypy as _mypy
project = self._projects['online.fxreader.pr34']
_mypy.run(
argv,
settings=_mypy.MypySettings(
paths=[
# Settings.settings().project_root / 'dotfiles/.local/bin/commands',
# project.source_dir / 'm.py',
project.source_dir / '_m.py',
project.source_dir / 'online',
project.source_dir / 'cli.py',
project.source_dir / 'm.py',
# Settings.settings().project_root / 'deps/com.github.aiortc.aiortc/src',
# Settings.settings().project_root / 'm.py',
],
max_errors={
'online/fxreader/pr34/commands_typed': 0,
# 'online/fxreader/pr34/commands': 0,
'cli.py': 0,
'm.py': 0,
'../deps/com.github.aiortc.aiortc/src/online_fxreader': 0,
'../deps/com.github.aiortc.aiortc/src/aiortc/contrib/signaling': 0,
},
),
)
@override
@property
def dependencies(self) -> dict[str, _cli.Dependency]:
return self._dependencies
def run(self, argv: Optional[list[str]] = None) -> None:
if argv is None:
argv = copy.deepcopy(sys.argv)
parser = argparse.ArgumentParser()
parser.add_argument('command', choices=[o.value for o in Command])
parser.add_argument('-p', '--project', choices=[o for o in self.projects])
parser.add_argument(
'-o',
'--output_dir',
default=None,
help='wheel output dir for deploy:wheel',
)
parser.add_argument(
'-f',
'--force',
default=False,
action='store_true',
help='remove install dir, before installing, default = false',
)
options, args = parser.parse_known_args(argv[1:])
default_project: Optional[str] = None
for k, v in self.projects.items():
if cli_bootstrap.paths_equal(
v.source_dir.resolve(),
# pathlib.Path(__file__).parent.resolve(),
pathlib.Path.cwd(),
):
default_project = k
if options.project is None:
if not default_project is None:
options.project = default_project
else:
logger.error(dict(msg='not provided project name'))
raise NotImplementedError
options.command = Command(options.command)
if options.command is Command.deploy_wheel:
assert not options.project is None
self.deploy_wheel(
project_name=options.project,
argv=args,
output_dir=options.output_dir,
# mypy=True,
ruff=True,
pyright=False,
pyrefly=True,
)
elif options.command is Command.pyright:
self.pyright(
project_name=options.project,
argv=args,
)
elif options.command is Command.ruff:
self.ruff(
project_name=options.project,
argv=args,
)
elif options.command is Command.meson_setup:
assert not options.project is None
self.meson_setup(
project_name=options.project,
argv=args,
force=options.force,
)
elif options.command is Command.meson_install_list:
assert not options.project is None
self.meson_install_list(
project_name=options.project,
argv=args,
)
elif options.command is Command.mypy:
self.mypy(
argv=args,
)
elif options.command is Command.tests:
assert not options.project is None
self.tests(
project_name=options.project,
argv=args,
)
elif options.command is Command.module_switch:
assert not options.project is None
self.module_switch(
project_name=options.project,
argv=args,
)
elif options.command is Command.pyrefly:
assert not options.project is None
self.pyrefly(
project_name=options.project,
argv=args,
)
else:
raise NotImplementedError
if __name__ == '__main__':
CLI().run()