From fe8818d187be86c85358c35d81062425b850dca1 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Mon, 10 Jun 2024 17:21:06 -0400 Subject: [PATCH] [2114] Apply PathLib Handover --- EDMC.py | 11 +++++--- EDMCLogging.py | 7 ++--- EDMCSystemProfiler.py | 17 ++++++------ EDMarketConnector.py | 16 ++++++------ build.py | 19 +++++++------- collate.py | 15 +++++------ commodity.py | 4 +-- companion.py | 5 ++-- dashboard.py | 9 ++++--- docs/examples/plugintest/load.py | 14 +++++----- installer.py | 12 ++++----- l10n.py | 44 ++++++++++++++++---------------- loadout.py | 6 ++--- monitor.py | 36 +++++++++++++------------- plug.py | 22 ++++++++++------ prefs.py | 18 ++++++------- theme.py | 5 ++-- ttkHyperlinkLabel.py | 3 +-- 18 files changed, 137 insertions(+), 126 deletions(-) diff --git a/EDMC.py b/EDMC.py index 6b72c7d1..55236b9c 100755 --- a/EDMC.py +++ b/EDMC.py @@ -14,6 +14,7 @@ import locale import os import queue import sys +from pathlib import Path from time import sleep, time from typing import TYPE_CHECKING, Any @@ -212,22 +213,24 @@ def main(): # noqa: C901, CCR001 # system, chances are its the current locale, and not utf-8. Otherwise if it was copied, its probably # utf8. Either way, try the system FIRST because reading something like cp1251 in UTF-8 results in garbage # but the reverse results in an exception. - json_file = os.path.abspath(args.j) + json_file = Path(args.j).resolve() try: with open(json_file) as file_handle: data = json.load(file_handle) except UnicodeDecodeError: with open(json_file, encoding='utf-8') as file_handle: data = json.load(file_handle) - config.set('querytime', int(os.path.getmtime(args.j))) + file_path = Path(args.j) + modification_time = file_path.stat().st_mtime + config.set('querytime', int(modification_time)) else: # Get state from latest Journal file logger.debug('Getting state from latest journal file') try: - monitor.currentdir = config.get_str('journaldir', default=config.default_journal_dir) + monitor.currentdir = Path(config.get_str('journaldir', default=config.default_journal_dir)) if not monitor.currentdir: - monitor.currentdir = config.default_journal_dir + monitor.currentdir = Path(config.default_journal_dir) logger.debug(f'logdir = "{monitor.currentdir}"') logfile = monitor.journal_newest_filename(monitor.currentdir) diff --git a/EDMCLogging.py b/EDMCLogging.py index b6d4b353..09a01860 100644 --- a/EDMCLogging.py +++ b/EDMCLogging.py @@ -26,12 +26,13 @@ To utilise logging in core code, or internal plugins, include this: To utilise logging in a 'found' (third-party) plugin, include this: - import os + from pathlib import Path import logging - plugin_name = os.path.basename(os.path.dirname(__file__)) + # Retrieve the name of the plugin folder + plugin_name = Path(__file__).resolve().parent.name + # Set up logger with hierarchical name including appname and plugin_name # plugin_name here *must* be the name of the folder the plugin resides in - # See, plug.py:load_plugins() logger = logging.getLogger(f'{appname}.{plugin_name}') """ from __future__ import annotations diff --git a/EDMCSystemProfiler.py b/EDMCSystemProfiler.py index 92502cc8..8b1dcbe5 100644 --- a/EDMCSystemProfiler.py +++ b/EDMCSystemProfiler.py @@ -11,7 +11,7 @@ import locale import webbrowser import platform import sys -from os import chdir, environ, path +from os import chdir, environ import pathlib import logging from journal_lock import JournalLock @@ -19,10 +19,10 @@ from journal_lock import JournalLock if getattr(sys, "frozen", False): # Under py2exe sys.path[0] is the executable name if sys.platform == "win32": - chdir(path.dirname(sys.path[0])) + chdir(pathlib.Path(sys.path[0]).parent) # Allow executable to be invoked from any cwd - environ["TCL_LIBRARY"] = path.join(path.dirname(sys.path[0]), "lib", "tcl") - environ["TK_LIBRARY"] = path.join(path.dirname(sys.path[0]), "lib", "tk") + environ['TCL_LIBRARY'] = str(pathlib.Path(sys.path[0]).parent / 'lib' / 'tcl') + environ['TK_LIBRARY'] = str(pathlib.Path(sys.path[0]).parent / 'lib' / 'tk') else: # We still want to *try* to have CWD be where the main script is, even if @@ -44,11 +44,12 @@ def get_sys_report(config: config.AbstractConfig) -> str: plt = platform.uname() locale.setlocale(locale.LC_ALL, "") lcl = locale.getlocale() - monitor.currentdir = config.get_str( + monitor.currentdir = pathlib.Path(config.get_str( "journaldir", default=config.default_journal_dir + ) ) if not monitor.currentdir: - monitor.currentdir = config.default_journal_dir + monitor.currentdir = pathlib.Path(config.default_journal_dir) try: logfile = monitor.journal_newest_filename(monitor.currentdir) if logfile is None: @@ -115,12 +116,12 @@ def main() -> None: root.withdraw() # Hide the window initially to calculate the dimensions try: icon_image = tk.PhotoImage( - file=path.join(cur_config.respath_path, "io.edcd.EDMarketConnector.png") + file=pathlib.Path(cur_config.respath_path) / "io.edcd.EDMarketConnector.png" ) root.iconphoto(True, icon_image) except tk.TclError: - root.iconbitmap(path.join(cur_config.respath_path, "EDMarketConnector.ico")) + root.iconbitmap(pathlib.Path(cur_config.respath_path) / "EDMarketConnector.ico") sys_report = get_sys_report(cur_config) diff --git a/EDMarketConnector.py b/EDMarketConnector.py index cd76ccc0..f67a4fe1 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -21,7 +21,7 @@ import sys import threading import webbrowser import tempfile -from os import chdir, environ, path +from os import chdir, environ from time import localtime, strftime, time from typing import TYPE_CHECKING, Any, Literal from constants import applongname, appname, protocolhandler_redirect @@ -32,10 +32,10 @@ from constants import applongname, appname, protocolhandler_redirect if getattr(sys, 'frozen', False): # Under py2exe sys.path[0] is the executable name if sys.platform == 'win32': - chdir(path.dirname(sys.path[0])) + os.chdir(pathlib.Path(sys.path[0]).parent) # Allow executable to be invoked from any cwd - environ['TCL_LIBRARY'] = path.join(path.dirname(sys.path[0]), 'lib', 'tcl') - environ['TK_LIBRARY'] = path.join(path.dirname(sys.path[0]), 'lib', 'tk') + environ['TCL_LIBRARY'] = str(pathlib.Path(sys.path[0]).parent / 'lib' / 'tcl') + environ['TK_LIBRARY'] = str(pathlib.Path(sys.path[0]).parent / 'lib' / 'tk') else: # We still want to *try* to have CWD be where the main script is, even if @@ -51,7 +51,7 @@ if __name__ == '__main__': if getattr(sys, 'frozen', False): # By default py2exe tries to write log to dirname(sys.executable) which fails when installed # unbuffered not allowed for text in python3, so use `1 for line buffering - log_file_path = path.join(tempfile.gettempdir(), f'{appname}.log') + log_file_path = pathlib.Path(tempfile.gettempdir()) / f'{appname}.log' sys.stdout = sys.stderr = open(log_file_path, mode='wt', buffering=1) # Do NOT use WITH here. # TODO: Test: Make *sure* this redirect is working, else py2exe is going to cause an exit popup @@ -468,8 +468,8 @@ class AppWindow: self.w.wm_iconbitmap(default='EDMarketConnector.ico') else: - self.w.tk.call('wm', 'iconphoto', self.w, '-default', - tk.PhotoImage(file=path.join(config.respath_path, 'io.edcd.EDMarketConnector.png'))) + image_path = pathlib.Path(config.respath_path) / 'io.edcd.EDMarketConnector.png' + self.w.tk.call('wm', 'iconphoto', self.w, '-default', image=tk.PhotoImage(file=image_path)) # TODO: Export to files and merge from them in future ? self.theme_icon = tk.PhotoImage( @@ -1639,7 +1639,7 @@ class AppWindow: # Avoid file length limits if possible provider = config.get_str('shipyard_provider', default='EDSY') target = plug.invoke(provider, 'EDSY', 'shipyard_url', loadout, monitor.is_beta) - file_name = path.join(config.app_dir_path, "last_shipyard.html") + file_name = pathlib.Path(config.app_dir_path) / "last_shipyard.html" with open(file_name, 'w') as f: f.write(SHIPYARD_HTML_TEMPLATE.format( diff --git a/build.py b/build.py index c5236c5a..224de56f 100644 --- a/build.py +++ b/build.py @@ -10,7 +10,6 @@ import shutil import sys import pathlib from string import Template -from os.path import join, isdir import py2exe from config import ( appcmdname, @@ -37,7 +36,7 @@ def iss_build(template_path: str, output_file: str) -> None: new_file.write(newfile) -def system_check(dist_dir: str) -> str: +def system_check(dist_dir: pathlib.Path) -> str: """Check if the system is able to build.""" if sys.version_info < (3, 11): sys.exit(f"Unexpected Python version {sys.version}") @@ -55,17 +54,17 @@ def system_check(dist_dir: str) -> str: print(f"Git short hash: {git_shorthash}") - if dist_dir and len(dist_dir) > 1 and isdir(dist_dir): + if dist_dir and pathlib.Path.is_dir(dist_dir): shutil.rmtree(dist_dir) return gitversion_file def generate_data_files( app_name: str, gitversion_file: str, plugins: list[str] -) -> list[tuple[str, list[str]]]: +) -> list[tuple[object, object]]: """Create the required datafiles to build.""" l10n_dir = "L10n" - fdevids_dir = "FDevIDs" + fdevids_dir = pathlib.Path("FDevIDs") data_files = [ ( "", @@ -88,13 +87,13 @@ def generate_data_files( ), ( l10n_dir, - [join(l10n_dir, x) for x in os.listdir(l10n_dir) if x.endswith(".strings")], + [pathlib.Path(l10n_dir) / x for x in os.listdir(l10n_dir) if x.endswith(".strings")] ), ( fdevids_dir, [ - join(fdevids_dir, "commodity.csv"), - join(fdevids_dir, "rare_commodity.csv"), + pathlib.Path(fdevids_dir / "commodity.csv"), + pathlib.Path(fdevids_dir / "rare_commodity.csv"), ], ), ("plugins", plugins), @@ -104,7 +103,7 @@ def generate_data_files( def build() -> None: """Build EDMarketConnector using Py2Exe.""" - dist_dir: str = "dist.win32" + dist_dir: pathlib.Path = pathlib.Path("dist.win32") gitversion_filename: str = system_check(dist_dir) # Constants @@ -142,7 +141,7 @@ def build() -> None: } # Function to generate DATA_FILES list - data_files: list[tuple[str, list[str]]] = generate_data_files( + data_files: list[tuple[object, object]] = generate_data_files( appname, gitversion_filename, plugins ) diff --git a/collate.py b/collate.py index 380cb8ba..0a413052 100755 --- a/collate.py +++ b/collate.py @@ -17,7 +17,6 @@ import json import os import pathlib import sys -from os.path import isfile from traceback import print_exc import companion @@ -35,7 +34,7 @@ def __make_backup(file_name: pathlib.Path, suffix: str = '.bak') -> None: """ backup_name = file_name.parent / (file_name.name + suffix) - if isfile(backup_name): + if pathlib.Path.is_file(backup_name): os.unlink(backup_name) os.rename(file_name, backup_name) @@ -58,7 +57,7 @@ def addcommodities(data) -> None: # noqa: CCR001 commodities = {} # slurp existing - if isfile(commodityfile): + if pathlib.Path.is_file(commodityfile): with open(commodityfile) as csvfile: reader = csv.DictReader(csvfile) for row in reader: @@ -86,7 +85,7 @@ def addcommodities(data) -> None: # noqa: CCR001 if len(commodities) <= size_pre: return - if isfile(commodityfile): + if pathlib.Path.is_file(commodityfile): __make_backup(commodityfile) with open(commodityfile, 'w', newline='\n') as csvfile: @@ -109,7 +108,7 @@ def addmodules(data): # noqa: C901, CCR001 fields = ('id', 'symbol', 'category', 'name', 'mount', 'guidance', 'ship', 'class', 'rating', 'entitlement') # slurp existing - if isfile(outfile): + if pathlib.Path.is_file(outfile): with open(outfile) as csvfile: reader = csv.DictReader(csvfile, restval='') for row in reader: @@ -147,7 +146,7 @@ def addmodules(data): # noqa: C901, CCR001 if not len(modules) > size_pre: return - if isfile(outfile): + if pathlib.Path.is_file(outfile): __make_backup(outfile) with open(outfile, 'w', newline='\n') as csvfile: @@ -170,7 +169,7 @@ def addships(data) -> None: # noqa: CCR001 fields = ('id', 'symbol', 'name') # slurp existing - if isfile(shipfile): + if pathlib.Path.is_file(shipfile): with open(shipfile) as csvfile: reader = csv.DictReader(csvfile, restval='') for row in reader: @@ -200,7 +199,7 @@ def addships(data) -> None: # noqa: CCR001 if not len(ships) > size_pre: return - if isfile(shipfile): + if pathlib.Path.is_file(shipfile): __make_backup(shipfile) with open(shipfile, 'w', newline='\n') as csvfile: diff --git a/commodity.py b/commodity.py index 6b167fa1..52c0f8b8 100644 --- a/commodity.py +++ b/commodity.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- import time -from os.path import join +from pathlib import Path from config import config from edmc_data import commodity_bracketmap as bracketmap @@ -29,7 +29,7 @@ def export(data, kind=COMMODITY_DEFAULT, filename=None) -> None: filename_time = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime)) filename_kind = 'csv' filename = f'{filename_system}.{filename_starport}.{filename_time}.{filename_kind}' - filename = join(config.get_str('outdir'), filename) + filename = Path(config.get_str('outdir')) / filename if kind == COMMODITY_CSV: sep = ';' # BUG: for fixing later after cleanup diff --git a/companion.py b/companion.py index 775da295..d91fed22 100644 --- a/companion.py +++ b/companion.py @@ -27,6 +27,7 @@ import tkinter as tk import urllib.parse import webbrowser from email.utils import parsedate +from pathlib import Path from queue import Queue from typing import TYPE_CHECKING, Any, Mapping, TypeVar import requests @@ -1135,7 +1136,7 @@ class Session: def dump_capi_data(self, data: CAPIData) -> None: """Dump CAPI data to file for examination.""" - if os.path.isdir('dump'): + if Path('dump').is_dir(): file_name: str = "" if data.source_endpoint == self.FRONTIER_CAPI_PATH_FLEETCARRIER: file_name += f"FleetCarrier.{data['name']['callsign']}" @@ -1203,7 +1204,7 @@ def fixup(data: CAPIData) -> CAPIData: # noqa: C901, CCR001 # Can't be usefully if not commodity_map: # Lazily populate for f in ('commodity.csv', 'rare_commodity.csv'): - if not os.path.isfile(config.app_dir_path / 'FDevIDs/' / f): + if not (Path(config.app_dir_path) / 'FDevIDs' / f).is_file(): logger.warning(f'FDevID file {f} not found! Generating output without these commodity name rewrites.') continue with open(config.app_dir_path / 'FDevIDs' / f, 'r') as csvfile: diff --git a/dashboard.py b/dashboard.py index 4776319c..a57264c0 100644 --- a/dashboard.py +++ b/dashboard.py @@ -12,7 +12,7 @@ import sys import time import tkinter as tk from calendar import timegm -from os.path import getsize, isdir, isfile, join +from pathlib import Path from typing import Any, cast from watchdog.observers.api import BaseObserver from config import config @@ -57,7 +57,7 @@ class Dashboard(FileSystemEventHandler): logdir = config.get_str('journaldir', default=config.default_journal_dir) logdir = logdir or config.default_journal_dir - if not isdir(logdir): + if not Path.is_dir(Path(logdir)): logger.info(f"No logdir, or it isn't a directory: {logdir=}") self.stop() return False @@ -164,7 +164,8 @@ class Dashboard(FileSystemEventHandler): :param event: Watchdog event. """ - if event.is_directory or (isfile(event.src_path) and getsize(event.src_path)): + modpath = Path(event.src_path) + if event.is_directory or (modpath.is_file() and modpath.stat().st_size): # Can get on_modified events when the file is emptied self.process(event.src_path if not event.is_directory else None) @@ -177,7 +178,7 @@ class Dashboard(FileSystemEventHandler): if config.shutting_down: return try: - status_json_path = join(self.currentdir, 'Status.json') + status_json_path = Path(self.currentdir) / 'Status.json' with open(status_json_path, 'rb') as h: data = h.read().strip() if data: # Can be empty if polling while the file is being re-written diff --git a/docs/examples/plugintest/load.py b/docs/examples/plugintest/load.py index 4aca1689..b494f436 100644 --- a/docs/examples/plugintest/load.py +++ b/docs/examples/plugintest/load.py @@ -3,10 +3,10 @@ """Plugin that tests that modules we bundle for plugins are present and working.""" import logging -import os import shutil import sqlite3 import zipfile +from pathlib import Path import semantic_version from SubA import SubA @@ -14,7 +14,7 @@ from SubA import SubA from config import appname, appversion, config # This could also be returned from plugin_start3() -plugin_name = os.path.basename(os.path.dirname(__file__)) +plugin_name = Path(__file__).resolve().parent.name # Logger per found plugin, so the folder name is included in # the logging format. @@ -49,17 +49,17 @@ class PluginTest: def __init__(self, directory: str): logger.debug(f'directory = "{directory}') - dbfile = os.path.join(directory, this.DBFILE) + dbfile = Path(directory) / this.DBFILE # Test 'import zipfile' - with zipfile.ZipFile(dbfile + '.zip', 'w') as zip: - if os.path.exists(dbfile): + with zipfile.ZipFile(str(dbfile) + '.zip', 'w') as zip: + if dbfile.exists(): zip.write(dbfile) zip.close() # Testing 'import shutil' - if os.path.exists(dbfile): - shutil.copyfile(dbfile, dbfile + '.bak') + if dbfile.exists(): + shutil.copyfile(dbfile, str(dbfile) + '.bak') # Testing 'import sqlite3' self.sqlconn = sqlite3.connect(dbfile) diff --git a/installer.py b/installer.py index 754b3373..1ee79ddb 100644 --- a/installer.py +++ b/installer.py @@ -5,23 +5,23 @@ Copyright (c) EDCD, All Rights Reserved Licensed under the GNU General Public License. See LICENSE file. """ -import os import subprocess +from pathlib import Path from build import build -def run_inno_setup_installer(iss_path: str) -> None: +def run_inno_setup_installer(iss_path: Path) -> None: """Run the Inno installer, building the installation exe.""" # Get the path to the Inno Setup compiler (iscc.exe) (Currently set to default path) - inno_setup_compiler_path: str = "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe" + inno_setup_compiler_path = Path("C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe") # Check if the Inno Setup compiler executable exists - if not os.path.isfile(inno_setup_compiler_path): + if not inno_setup_compiler_path.exists(): print(f"Error: Inno Setup compiler not found at '{inno_setup_compiler_path}'.") return # Check if the provided .iss file exists - if not os.path.isfile(iss_file_path): + if not iss_file_path.exists(): print(f"Error: The provided .iss file '{iss_path}' not found.") return @@ -40,6 +40,6 @@ def run_inno_setup_installer(iss_path: str) -> None: if __name__ == "__main__": build() # Add the ISS Template File - iss_file_path: str = "./EDMC_Installer_Config.iss" + iss_file_path = Path("./EDMC_Installer_Config.iss") # Build the ISS file run_inno_setup_installer(iss_file_path) diff --git a/l10n.py b/l10n.py index 8613244f..857b1596 100755 --- a/l10n.py +++ b/l10n.py @@ -17,10 +17,9 @@ import re import sys import warnings from contextlib import suppress -from os import listdir, sep, makedirs -from os.path import basename, dirname, isdir, join, abspath, exists +from os import listdir, sep from typing import TYPE_CHECKING, Iterable, TextIO, cast - +import pathlib from config import config from EDMCLogging import get_main_logger @@ -35,7 +34,7 @@ logger = get_main_logger() # Language name LANGUAGE_ID = '!Language' -LOCALISATION_DIR = 'L10n' +LOCALISATION_DIR: pathlib.Path = pathlib.Path('L10n') if sys.platform == 'win32': import ctypes @@ -119,10 +118,10 @@ class Translations: self.translations = {None: self.contents(cast(str, lang))} for plugin in listdir(config.plugin_dir_path): - plugin_path = join(config.plugin_dir_path, plugin, LOCALISATION_DIR) - if isdir(plugin_path): + plugin_path = config.plugin_dir_path / plugin / LOCALISATION_DIR + if pathlib.Path.is_dir(plugin_path): try: - self.translations[plugin] = self.contents(cast(str, lang), str(plugin_path)) + self.translations[plugin] = self.contents(cast(str, lang), plugin_path) except UnicodeDecodeError as e: logger.warning(f'Malformed file {lang}.strings in plugin {plugin}: {e}') @@ -133,7 +132,7 @@ class Translations: # DEPRECATED: Migrate to translations.translate or tr.tl. Will remove in 6.0 or later. builtins.__dict__['_'] = self.translate - def contents(self, lang: str, plugin_path: str | None = None) -> dict[str, str]: + def contents(self, lang: str, plugin_path: pathlib.Path | None = None) -> dict[str, str]: """Load all the translations from a translation file.""" assert lang in self.available() translations = {} @@ -173,12 +172,12 @@ class Translations: :return: The translated string """ plugin_name: str | None = None - plugin_path: str | None = None + plugin_path: pathlib.Path | None = None if context: # TODO: There is probably a better way to go about this now. plugin_name = context[len(config.plugin_dir)+1:].split(sep)[0] - plugin_path = join(config.plugin_dir_path, plugin_name, LOCALISATION_DIR) + plugin_path = pathlib.Path(config.plugin_dir_path / plugin_name / LOCALISATION_DIR) if lang: contents: dict[str, str] = self.contents(lang=lang, plugin_path=plugin_path) @@ -225,17 +224,17 @@ class Translations: return names - def respath(self) -> str: + def respath(self) -> pathlib.Path: """Path to localisation files.""" if getattr(sys, 'frozen', False): - return abspath(join(dirname(sys.executable), LOCALISATION_DIR)) + return pathlib.Path(sys.executable).parent.joinpath(LOCALISATION_DIR).resolve() if __file__: - return abspath(join(dirname(__file__), LOCALISATION_DIR)) + return pathlib.Path(__file__).parent.joinpath(LOCALISATION_DIR).resolve() - return abspath(LOCALISATION_DIR) + return pathlib.Path(LOCALISATION_DIR).resolve() - def file(self, lang: str, plugin_path: str | None = None) -> TextIO | None: + def file(self, lang: str, plugin_path: pathlib.Path | None = None) -> TextIO | None: """ Open the given lang file for reading. @@ -244,8 +243,8 @@ class Translations: :return: the opened file (Note: This should be closed when done) """ if plugin_path: - file_path = join(plugin_path, f'{lang}.strings') - if not exists(file_path): + file_path = plugin_path / f"{lang}.strings" + if not file_path.exists(): return None try: @@ -253,7 +252,7 @@ class Translations: except OSError: logger.exception(f'could not open {file_path}') - res_path = join(self.respath(), f'{lang}.strings') + res_path = self.respath() / f'{lang}.strings' return open(res_path, encoding='utf-8') @@ -382,9 +381,10 @@ Translations: Translations = translations # type: ignore if __name__ == "__main__": regexp = re.compile(r'''_\([ur]?(['"])(((? N regexp = re.compile(re.escape(ship) + r'\.\d\d\d\d-\d\d-\d\dT\d\d\.\d\d\.\d\d\.txt') oldfiles = sorted([x for x in listdir(config.get_str('outdir')) if regexp.match(x)]) if oldfiles: - with open(join(config.get_str('outdir'), oldfiles[-1]), 'rU') as h: + with open(Path(config.get_str('outdir')) / Path(oldfiles[-1]), 'rU') as h: if h.read() == string: return # same as last time - don't write @@ -55,7 +55,7 @@ def export(data: companion.CAPIData, requested_filename: str | None = None) -> N output_directory = config.get_str('outdir') ship_time = time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(query_time)) - file_path = join(output_directory, f"{ship}.{ship_time}.txt") + file_path = output_directory / Path(f"{ship}.{ship_time}.txt") with open(file_path, 'wt') as h: h.write(string) diff --git a/monitor.py b/monitor.py index 9d33f5eb..aca6af32 100644 --- a/monitor.py +++ b/monitor.py @@ -8,7 +8,7 @@ See LICENSE file. from __future__ import annotations import json -import pathlib +from pathlib import Path import queue import re import sys @@ -16,7 +16,6 @@ import threading from calendar import timegm from collections import defaultdict from os import SEEK_END, SEEK_SET, listdir -from os.path import basename, expanduser, getctime, isdir, join from time import gmtime, localtime, mktime, sleep, strftime, strptime, time from typing import TYPE_CHECKING, Any, BinaryIO, MutableMapping import semantic_version @@ -81,7 +80,7 @@ class EDLogs(FileSystemEventHandler): # TODO(A_D): A bunch of these should be switched to default values (eg '' for strings) and no longer be Optional FileSystemEventHandler.__init__(self) # futureproofing - not need for current version of watchdog self.root: 'tkinter.Tk' = None # type: ignore # Don't use Optional[] - mypy thinks no methods - self.currentdir: str | None = None # The actual logdir that we're monitoring + self.currentdir: Path | None = None # The actual logdir that we're monitoring self.logfile: str | None = None self.observer: BaseObserver | None = None self.observed = None # a watchdog ObservedWatch, or None if polling @@ -202,9 +201,9 @@ class EDLogs(FileSystemEventHandler): if journal_dir == '' or journal_dir is None: journal_dir = config.default_journal_dir - logdir = expanduser(journal_dir) + logdir = Path(journal_dir).expanduser() - if not logdir or not isdir(logdir): + if not logdir or not Path.is_dir(logdir): logger.error(f'Journal Directory is invalid: "{logdir}"') self.stop() return False @@ -277,9 +276,10 @@ class EDLogs(FileSystemEventHandler): # Odyssey Update 11 has, e.g. Journal.2022-03-15T152503.01.log # Horizons Update 11 equivalent: Journal.220315152335.01.log # So we can no longer use a naive sort. - journals_dir_path = pathlib.Path(journals_dir) - journal_files = (journals_dir_path / pathlib.Path(x) for x in journal_files) - return str(max(journal_files, key=getctime)) + journals_dir_path = Path(journals_dir) + journal_files = (journals_dir_path / Path(x) for x in journal_files) + latest_file = max(journal_files, key=lambda f: Path(f).stat().st_ctime) + return str(latest_file) return None @@ -348,7 +348,7 @@ class EDLogs(FileSystemEventHandler): def on_created(self, event: 'FileSystemEvent') -> None: """Watchdog callback when, e.g. client (re)started.""" - if not event.is_directory and self._RE_LOGFILE.search(basename(event.src_path)): + if not event.is_directory and self._RE_LOGFILE.search(Path(event.src_path).name): self.logfile = event.src_path @@ -1056,7 +1056,7 @@ class EDLogs(FileSystemEventHandler): self.state['Cargo'] = defaultdict(int) # From 3.3 full Cargo event (after the first one) is written to a separate file if 'Inventory' not in entry: - with open(join(self.currentdir, 'Cargo.json'), 'rb') as h: # type: ignore + with open(self.currentdir / 'Cargo.json', 'rb') as h: # type: ignore entry = json.load(h) self.state['CargoJSON'] = entry @@ -1083,7 +1083,7 @@ class EDLogs(FileSystemEventHandler): # Always attempt loading of this, but if it fails we'll hope this was # a startup/boarding version and thus `entry` contains # the data anyway. - currentdir_path = pathlib.Path(str(self.currentdir)) + currentdir_path = Path(str(self.currentdir)) shiplocker_filename = currentdir_path / 'ShipLocker.json' shiplocker_max_attempts = 5 shiplocker_fail_sleep = 0.01 @@ -1152,7 +1152,7 @@ class EDLogs(FileSystemEventHandler): # TODO: v31 doc says this is`backpack.json` ... but Howard Chalkley # said it's `Backpack.json` - backpack_file = pathlib.Path(str(self.currentdir)) / 'Backpack.json' + backpack_file = Path(str(self.currentdir)) / 'Backpack.json' backpack_data = None if not backpack_file.exists(): @@ -1528,7 +1528,7 @@ class EDLogs(FileSystemEventHandler): entry = fcmaterials elif event_type == 'moduleinfo': - with open(join(self.currentdir, 'ModulesInfo.json'), 'rb') as mf: # type: ignore + with open(self.currentdir / 'ModulesInfo.json', 'rb') as mf: # type: ignore try: entry = json.load(mf) @@ -2242,14 +2242,14 @@ class EDLogs(FileSystemEventHandler): oldfiles = sorted((x for x in listdir(config.get_str('outdir')) if regexp.match(x))) if oldfiles: try: - with open(join(config.get_str('outdir'), oldfiles[-1]), encoding='utf-8') as h: + with open(config.get_str('outdir') / Path(oldfiles[-1]), encoding='utf-8') as h: if h.read() == string: return # same as last time - don't write except UnicodeError: logger.exception("UnicodeError reading old ship loadout with utf-8 encoding, trying without...") try: - with open(join(config.get_str('outdir'), oldfiles[-1])) as h: + with open(config.get_str('outdir') / Path(oldfiles[-1])) as h: if h.read() == string: return # same as last time - don't write @@ -2268,7 +2268,7 @@ class EDLogs(FileSystemEventHandler): # Write ts = strftime('%Y-%m-%dT%H.%M.%S', localtime(time())) - filename = join(config.get_str('outdir'), f'{ship}.{ts}.txt') + filename = config.get_str('outdir') / Path(f'{ship}.{ts}.txt') try: with open(filename, 'wt', encoding='utf-8') as h: @@ -2355,7 +2355,7 @@ class EDLogs(FileSystemEventHandler): try: - with open(join(self.currentdir, 'NavRoute.json')) as f: + with open(self.currentdir / 'NavRoute.json') as f: raw = f.read() except Exception as e: @@ -2381,7 +2381,7 @@ class EDLogs(FileSystemEventHandler): try: - with open(join(self.currentdir, 'FCMaterials.json')) as f: + with open(self.currentdir / 'FCMaterials.json') as f: raw = f.read() except Exception as e: diff --git a/plug.py b/plug.py index 9c203f25..7c407242 100644 --- a/plug.py +++ b/plug.py @@ -14,6 +14,7 @@ import operator import os import sys import tkinter as tk +from pathlib import Path from tkinter import ttk from typing import Any, Mapping, MutableMapping @@ -47,7 +48,7 @@ last_error = LastError() class Plugin: """An EDMC plugin.""" - def __init__(self, name: str, loadfile: str | None, plugin_logger: logging.Logger | None): # noqa: CCR001 + def __init__(self, name: str, loadfile: Path | None, plugin_logger: logging.Logger | None): # noqa: CCR001 """ Load a single plugin. @@ -73,7 +74,7 @@ class Plugin: sys.modules[module.__name__] = module spec.loader.exec_module(module) if getattr(module, 'plugin_start3', None): - newname = module.plugin_start3(os.path.dirname(loadfile)) + newname = module.plugin_start3(Path(loadfile).resolve().parent) self.name = str(newname) if newname else self.name self.module = module elif getattr(module, 'plugin_start', None): @@ -171,7 +172,9 @@ def _load_internal_plugins(): for name in sorted(os.listdir(config.internal_plugin_dir_path)): if name.endswith('.py') and name[0] not in ('.', '_'): try: - plugin = Plugin(name[:-3], os.path.join(config.internal_plugin_dir_path, name), logger) + plugin_name = name[:-3] + plugin_path = config.internal_plugin_dir_path / name + plugin = Plugin(plugin_name, plugin_path, logger) plugin.folder = None internal.append(plugin) except Exception: @@ -186,9 +189,12 @@ def _load_found_plugins(): # The intent here is to e.g. have EDMC-Overlay load before any plugins # that depend on it. - for name in sorted(os.listdir(config.plugin_dir_path), key=lambda n: ( - not os.path.isfile(os.path.join(config.plugin_dir_path, n, '__init__.py')), n.lower())): - if not os.path.isdir(os.path.join(config.plugin_dir_path, name)) or name[0] in ('.', '_'): + plugin_files = sorted(Path(config.plugin_dir_path).iterdir(), key=lambda p: ( + not (p / '__init__.py').is_file(), p.name.lower())) + + for plugin_file in plugin_files: + name = plugin_file.name + if not (Path(config.plugin_dir_path) / name).is_dir() or name.startswith(('.', '_')): pass elif name.endswith('.disabled'): name, discard = name.rsplit('.', 1) @@ -196,12 +202,12 @@ def _load_found_plugins(): else: try: # Add plugin's folder to load path in case plugin has internal package dependencies - sys.path.append(os.path.join(config.plugin_dir_path, name)) + sys.path.append(str(Path(config.plugin_dir_path) / name)) import EDMCLogging # Create a logger for this 'found' plugin. Must be before the load.py is loaded. plugin_logger = EDMCLogging.get_plugin_logger(name) - found.append(Plugin(name, os.path.join(config.plugin_dir_path, name, 'load.py'), plugin_logger)) + found.append(Plugin(name, config.plugin_dir_path / name / 'load.py', plugin_logger)) except Exception: PLUGINS_broken.append(Plugin(name, None, logger)) logger.exception(f'Failure loading found Plugin "{name}"') diff --git a/prefs.py b/prefs.py index 9f2062f4..673d1e70 100644 --- a/prefs.py +++ b/prefs.py @@ -4,14 +4,14 @@ from __future__ import annotations import contextlib import logging -import pathlib +from os.path import expandvars +from pathlib import Path import subprocess import sys import tempfile import tkinter as tk import warnings from os import system -from os.path import expanduser, expandvars, join, normpath from tkinter import colorchooser as tkColorChooser # type: ignore # noqa: N812 from tkinter import ttk from types import TracebackType @@ -43,10 +43,10 @@ def help_open_log_folder() -> None: """Open the folder logs are stored in.""" warnings.warn('prefs.help_open_log_folder is deprecated, use open_log_folder instead. ' 'This function will be removed in 6.0 or later', DeprecationWarning, stacklevel=2) - open_folder(pathlib.Path(tempfile.gettempdir()) / appname) + open_folder(Path(tempfile.gettempdir()) / appname) -def open_folder(file: pathlib.Path) -> None: +def open_folder(file: Path) -> None: """Open the given file in the OS file explorer.""" if sys.platform.startswith('win'): # On Windows, use the "start" command to open the folder @@ -58,7 +58,7 @@ def open_folder(file: pathlib.Path) -> None: def help_open_system_profiler(parent) -> None: """Open the EDMC System Profiler.""" - profiler_path = pathlib.Path(config.respath_path) + profiler_path = Path(config.respath_path) try: if getattr(sys, 'frozen', False): profiler_path /= 'EDMCSystemProfiler.exe' @@ -323,7 +323,7 @@ class PreferencesDialog(tk.Toplevel): self.geometry(f"+{position.left}+{position.top}") # Set Log Directory - self.logfile_loc = pathlib.Path(tempfile.gettempdir()) / appname + self.logfile_loc = Path(tempfile.gettempdir()) / appname # Set minimum size to prevent content cut-off self.update_idletasks() # Update "requested size" from geometry manager @@ -1066,7 +1066,7 @@ class PreferencesDialog(tk.Toplevel): import tkinter.filedialog directory = tkinter.filedialog.askdirectory( parent=self, - initialdir=expanduser(pathvar.get()), + initialdir=Path(pathvar.get()).expanduser(), title=title, mustexist=tk.TRUE ) @@ -1088,7 +1088,7 @@ class PreferencesDialog(tk.Toplevel): if sys.platform == 'win32': start = len(config.home.split('\\')) if pathvar.get().lower().startswith(config.home.lower()) else 0 display = [] - components = normpath(pathvar.get()).split('\\') + components = Path(pathvar.get()).resolve().parts buf = ctypes.create_unicode_buffer(MAX_PATH) pidsRes = ctypes.c_int() # noqa: N806 # Windows convention for i in range(start, len(components)): @@ -1233,7 +1233,7 @@ class PreferencesDialog(tk.Toplevel): config.set( 'outdir', - join(config.home_path, self.outdir.get()[2:]) if self.outdir.get().startswith('~') else self.outdir.get() + str(config.home_path / self.outdir.get()[2:]) if self.outdir.get().startswith('~') else self.outdir.get() ) logdir = self.logdir.get() diff --git a/theme.py b/theme.py index 94e99f7a..41913f42 100644 --- a/theme.py +++ b/theme.py @@ -12,8 +12,8 @@ from __future__ import annotations import os import sys +from pathlib import Path import tkinter as tk -from os.path import join from tkinter import font as tk_font from tkinter import ttk from typing import Callable @@ -38,7 +38,8 @@ if sys.platform == 'win32': AddFontResourceEx.restypes = [LPCWSTR, DWORD, LPCVOID] # type: ignore FR_PRIVATE = 0x10 FR_NOT_ENUM = 0x20 - AddFontResourceEx(join(config.respath, 'EUROCAPS.TTF'), FR_PRIVATE, 0) + font_path = Path(config.respath) / 'EUROCAPS.TTF' + AddFontResourceEx(str(font_path), FR_PRIVATE, 0) elif sys.platform == 'linux': # pyright: reportUnboundVariable=false diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 6266d9fb..0852e89b 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -28,7 +28,6 @@ from tkinter import font as tk_font from tkinter import ttk from typing import Any import plug -from os import path from config import config, logger from l10n import translations as tr from monitor import monitor @@ -96,7 +95,7 @@ class HyperlinkLabel(tk.Label or ttk.Label): # type: ignore else: # Avoid file length limits if possible target = plug.invoke(url, 'EDSY', 'shipyard_url', loadout, monitor.is_beta) - file_name = path.join(config.app_dir_path, "last_shipyard.html") + file_name = config.app_dir_path / "last_shipyard.html" with open(file_name, 'w') as f: f.write(SHIPYARD_HTML_TEMPLATE.format(