diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 055c5dcf1..f96b76778 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -16,19 +16,6 @@ jobs: run: | pycodestyle --statistics --count src - pydocstyle: - runs-on: ubuntu-latest - steps: - - name: "Checkout" - uses: actions/checkout@v4 - - - name: "Set up Python environment" - uses: ./.github/actions/setup-python - - - name: "Check PyDocStyle" - run: | - pydocstyle --count src - interrogate: runs-on: ubuntu-latest steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6c3f59363..72ef33f77 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,7 +35,7 @@ repos: additional_dependencies: [types-cffi, types-Pillow, types-psutil, types-pyinstaller, types-PyYAML, types-requests, lxml] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.0 + rev: v0.7.2 hooks: - id: ruff exclude: '/qtgui_rc.py$|tests/' @@ -68,7 +68,7 @@ repos: args: [--local] - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.21 + rev: v0.22 hooks: - id: validate-pyproject diff --git a/CHANGELOG.md b/CHANGELOG.md index ebd21a846..529478726 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ ## 3.5.3 * Use symbolic link to DCS-BIOS live repository #347 (@emcek) * Compile DCS-BIOS using LuaJIT from lupa library !373 (@emcek) -* Make switching between regular and live version of DCS-BIOS more reliable (@emcek) +* Make switching between regular and live version of DCS-BIOS more reliable !390 (@emcek) * internal: * Update PySide6 framework to 6.8.0.2 * add support for Python 3.13 + * Introduce WorkerSignalsMixIn to remove code duplication ## 3.5.2 * GUI should not be hide when starting when configuration is wrong (@emcek) diff --git a/pyproject.toml b/pyproject.toml index c2cddfe5a..50b411552 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,12 +72,11 @@ test = [ 'mypy==1.13.0', 'pip-audit==2.7.3', 'pycodestyle==2.12.1', - 'pydocstyle[toml]==6.3.0', 'pytest==8.3.3', 'pytest-cov==5.0.0', 'pytest-qt==4.4.0 ; sys_platform == "win32"', - 'pytest-randomly==3.15.0', - 'ruff==0.7.0', + 'pytest-randomly==3.16.0', + 'ruff==0.7.1', 'types-cffi==1.16.0.20240331', 'types-pillow==10.2.0.20240822', 'types-psutil==6.1.0.20241022', @@ -169,11 +168,6 @@ exclude = [ module = 'dcspy.qt_gui' follow_imports = 'skip' -[tool.pydocstyle] -inherit = false -ignore = ['D100', 'D101','D104', 'D105', 'D203', 'D204', 'D212'] -match = '(?!test_|qtgui_rc).*\.py' - [tool.isort] line_length = 160 diff --git a/requirements_test.txt b/requirements_test.txt index f4d41eccb..4070dcd2e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -6,12 +6,11 @@ lxml==5.3.0 mypy==1.13.0 pip-audit==2.7.3 pycodestyle==2.12.1 -pydocstyle[toml]==6.3.0 pytest==8.3.3 pytest-cov==5.0.0 pytest-qt==4.4.0; sys_platform == "win32" -pytest-randomly==3.15.0 -ruff==0.7.0 +pytest-randomly==3.16.0 +ruff==0.7.1 types-cffi==1.16.0.20240331 types-Pillow==10.2.0.20240822 types-psutil==6.1.0.20241022 diff --git a/src/dcspy/qt_gui.py b/src/dcspy/qt_gui.py index 541817ba6..f819e05e2 100644 --- a/src/dcspy/qt_gui.py +++ b/src/dcspy/qt_gui.py @@ -12,7 +12,7 @@ from tempfile import gettempdir from threading import Event, Thread from time import sleep -from typing import Callable, Optional, Union +from typing import Any, Callable, Optional, Union from webbrowser import open_new_tab from packaging import version @@ -517,6 +517,7 @@ def _get_plane_aliases(self, plane_name: str) -> dict: generate_bios_jsons_with_lupa(dcs_save_games=Path(self.le_biosdir.text()).parents[1]) if count_files(directory=self.bios_path / 'doc' / 'json', extension='json') < 1: self._generate_bios_jsons_with_dcs_lua() + self._is_dir_dcs_bios(text=self.bios_path, widget_name='le_biosdir') return get_plane_aliases(plane=plane_name, bios_dir=self.bios_path) except FileNotFoundError as err: message = f'Folder not exists:\n{self.bios_path}\n\nCheck DCS-BIOS path.\n\n{err}' # generate json/bios @@ -826,6 +827,7 @@ def _reload_table_gkeys(self) -> None: * _load_table_gkeys method to load the table with gkeys, * reconnects the currentIndexChanged signal of the combo_planes widget """ + self.plane_aliases = [''] self.combo_planes.currentIndexChanged.disconnect() self._load_table_gkeys() self.combo_planes.currentIndexChanged.connect(self._load_table_gkeys) @@ -985,8 +987,7 @@ def _start_bios_update(self, silence: bool) -> None: 'error': self._error_during_bios_update, 'result': self._clone_bios_completed, } - for signal, handler in signal_handlers.items(): - getattr(clone_worker.signals, signal).connect(handler) + clone_worker.setup_signal_handlers(signal_handlers=signal_handlers) self.threadpool.start(clone_worker) else: self._check_bios_release(silence=silence) @@ -1041,7 +1042,6 @@ def _clone_bios_completed(self, result) -> None: install_result = f'{install_result}\n\nUsing Git/Live version.' self.statusbar.showMessage(sha) self._is_git_object_exists(text=self.le_bios_live.text()) - self._is_dir_dcs_bios(text=self.bios_path, widget_name='le_biosdir') self._reload_table_gkeys() if not silence: self._show_message_box(kind_of=MsgBoxTypes.INFO, title=f'Updated {self.l_bios}', message=install_result) @@ -1138,7 +1138,6 @@ def _update_release_bios(self, rel_info: ReleaseInfo) -> None: """ tmp_dir = Path(gettempdir()) local_zip = tmp_dir / rel_info.asset_file - self._remove_saved_games_dcs() download_file(url=rel_info.dl_url, save_path=local_zip) LOG.debug(f'Remove DCS-BIOS from: {tmp_dir} ') rmtree(path=tmp_dir / 'DCS-BIOS', ignore_errors=True) @@ -1204,14 +1203,17 @@ def _bios_repair_clicked(self) -> None: message=message, defaultButton=QMessageBox.StandardButton.No) if bool(reply == QMessageBox.StandardButton.Yes): self._clean_bios_files() + self._remove_dcs_bios_repo_dir() self._start_bios_update(silence=False) def _clean_bios_files(self) -> None: """Clean all DCS-BIOS directories and files.""" - self._remove_dcs_bios_repo_dir() - LOG.debug(f'Try remove regular DCS-BIOS directory: {self.bios_path}') + if self.bios_path.is_symlink(): + rm_symlink = f"(Get-Item '{self.bios_path}').Delete()" + ps_command = f'Start-Process powershell.exe -ArgumentList "-Command {rm_symlink}" -Verb RunAs' + LOG.debug(f'Execute: {ps_command}') + run_command(cmd=['powershell.exe', '-Command', ps_command]) rmtree(path=self.bios_path, ignore_errors=True) - self._remove_saved_games_dcs() def _remove_dcs_bios_repo_dir(self) -> None: """Remove DCS-BIOS repository directory.""" @@ -1223,16 +1225,6 @@ def _remove_dcs_bios_repo_dir(self) -> None: LOG.debug(f'Try remove DCS-BIOS old repo\n{err}', exc_info=True) LOG.debug(f'Clean up old DCS-BIOS git repository, RC: {return_code}') - def _remove_saved_games_dcs(self) -> None: - r"""Remove Saved Games\DCS.openbeta\Scripts\DCS-BIOS repository directory.""" - try: - cmd_symlink = f"Remove-Item -LiteralPath '{self.bios_path}' -Force -Recurse" - ps_command = f'Start-Process powershell.exe -ArgumentList "-Command {cmd_symlink}" -Verb RunAs' - LOG.debug(f'Execute: {ps_command} ') - run_command(cmd=['powershell.exe', '-Command', ps_command]) - except FileNotFoundError as err: - LOG.debug(f'Try remove DCS-BIOS dir\n{err}', exc_info=True) - # <=><=><=><=><=><=><=><=><=><=><=> start/stop <=><=><=><=><=><=><=><=><=><=><=> def _stop_clicked(self) -> None: """Set event to stop DCSpy.""" @@ -1418,8 +1410,7 @@ def run_in_background(self, job: Union[partial, Callable], signal_handlers: dict """ progress = True if 'progress' in signal_handlers.keys() else False worker = Worker(func=job, with_progress=progress) - for signal, handler in signal_handlers.items(): - getattr(worker.signals, signal).connect(handler) + worker.setup_signal_handlers(signal_handlers=signal_handlers) if isinstance(job, partial): job_name = job.func.__name__ args = job.args @@ -1780,7 +1771,24 @@ class WorkerSignals(QObject): stage = Signal(str) -class Worker(QRunnable): +class WorkerSignalsMixIn: + """Worker signals Mixin.""" + + def __init__(self): + """Signal handler for WorkerSignals.""" + self.signals = WorkerSignals() + + def setup_signal_handlers(self, signal_handlers: dict[str, Callable[[Any], None]]) -> None: + """ + Connect handlers to signals. + + :param signal_handlers: dict with signals and handlers as value. + """ + for signal, handler in signal_handlers.items(): + getattr(self.signals, signal).connect(handler) + + +class Worker(QRunnable, WorkerSignalsMixIn): """Runnable worker.""" def __init__(self, func: partial, with_progress: bool) -> None: @@ -1792,7 +1800,6 @@ def __init__(self, func: partial, with_progress: bool) -> None: """ super().__init__() self.func = func - self.signals = WorkerSignals() if with_progress: self.func.keywords['progress_callback'] = self.signals.progress @@ -1810,7 +1817,7 @@ def run(self) -> None: self.signals.finished.emit() -class GitCloneWorker(QRunnable): +class GitCloneWorker(QRunnable, WorkerSignalsMixIn): """Worker for git clone with reporting progress.""" def __init__(self, git_ref: str, bios_path: Path, to_path: Path, repo: str, silence: bool = False) -> None: @@ -1829,7 +1836,6 @@ def __init__(self, git_ref: str, bios_path: Path, to_path: Path, repo: str, sile self.to_path = to_path self.bios_path = bios_path self.silence = silence - self.signals = WorkerSignals() @Slot() def run(self): @@ -1837,11 +1843,14 @@ def run(self): try: sha = check_github_repo(git_ref=self.git_ref, update=True, repo=self.repo, repo_dir=self.to_path, progress=CloneProgress(self.signals.progress, self.signals.stage)) - target = self.to_path / 'Scripts' / 'DCS-BIOS' - cmd_symlink = f'"New-Item -ItemType SymbolicLink -Path \\"{self.bios_path}\\" -Target \\"{target}\\"' - ps_command = f"Start-Process powershell.exe -ArgumentList '-Command {cmd_symlink}' -Verb RunAs" - LOG.debug(f'Make symbolic link: {ps_command} ') - run_command(cmd=['powershell.exe', '-Command', ps_command]) + if not self.bios_path.is_symlink(): + target = self.to_path / 'Scripts' / 'DCS-BIOS' + cmd_symlink = f'"New-Item -ItemType SymbolicLink -Path \\"{self.bios_path}\\" -Target \\"{target}\\"' + ps_command = f"Start-Process powershell.exe -ArgumentList '-Command {cmd_symlink}' -Verb RunAs" + LOG.debug(f'Make symbolic link: {ps_command}') + run_command(cmd=['powershell.exe', '-Command', ps_command]) + sleep(0.8) + LOG.debug(f'Directory: {self.bios_path} is symbolic link: {self.bios_path.is_symlink()}') except Exception: exctype, value = sys.exc_info()[:2] self.signals.error.emit((exctype, value, traceback.format_exc()))