1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-06-07 02:43:22 +03:00

Merge pull request #2084 from HullSeals/enhancement/2051/config

[2051] Config Files Audit
LGTM
This commit is contained in:
Phoebe 2023-11-16 19:26:32 +01:00 committed by GitHub
commit fae7b32e43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 322 additions and 284 deletions

37
.gitignore vendored
View File

@ -1,8 +1,18 @@
# Ignore version file
.gitversion .gitversion
# Ignore macOS DS_Store files
.DS_Store .DS_Store
# Ignore build artifacts
build build
ChangeLog.html dist.win32/
dist.* dist.*
# Ignore generated ChangeLog.html file
ChangeLog.html
# Ignore files
dump dump
*.bak *.bak
*.pyc *.pyc
@ -11,20 +21,37 @@ dump
*.pdb *.pdb
*.msi *.msi
*.wixobj *.wixobj
*.zip
# Ignore Update Things
EDMarketConnector_Installer_*.exe EDMarketConnector_Installer_*.exe
appcast_win_*.xml appcast_win_*.xml
appcast_mac_*.xml appcast_mac_*.xml
EDMarketConnector.VisualElementsManifest.xml
*.zip
EDMC_Installer_Config.iss EDMC_Installer_Config.iss
EDMarketConnector.wxs
wix/components.wxs
# Ignore Visual Elements Manifest file for Windows
EDMarketConnector.VisualElementsManifest.xml
# Ignore IDE and editor configuration files
.idea .idea
.vscode .vscode
# Ignore virtual environments
.venv/ .venv/
venv/ venv/
venv2
# Ignore workspace file for Visual Studio Code
*.code-workspace *.code-workspace
# Ignore coverage reports
htmlcov/ htmlcov/
.ignored .ignored
.coverage .coverage
EDMarketConnector.wxs pylintrc
wix/components.wxs pylint.txt
# Ignore Submodule data directory
coriolis-data/

View File

@ -1,11 +1,15 @@
""" """
Code dealing with the configuration of the program. __init__.py - Code dealing with the configuration of the program.
Copyright (c) EDCD, All Rights Reserved
Licensed under the GNU General Public License.
See LICENSE file.
Windows uses the Registry to store values in a flat manner. Windows uses the Registry to store values in a flat manner.
Linux uses a file, but for commonality it's still a flat data structure. Linux uses a file, but for commonality it's still a flat data structure.
macOS uses a 'defaults' object. macOS uses a 'defaults' object.
""" """
from __future__ import annotations
__all__ = [ __all__ = [
# defined in the order they appear in the file # defined in the order they appear in the file
@ -40,10 +44,8 @@ import sys
import traceback import traceback
import warnings import warnings
from abc import abstractmethod from abc import abstractmethod
from typing import Any, Callable, Optional, Type, TypeVar from typing import Any, Callable, Type, TypeVar
import semantic_version import semantic_version
from constants import GITVERSION_FILE, applongname, appname from constants import GITVERSION_FILE, applongname, appname
# Any of these may be imported by plugins # Any of these may be imported by plugins
@ -52,9 +54,9 @@ appcmdname = 'EDMC'
# <https://semver.org/#semantic-versioning-specification-semver> # <https://semver.org/#semantic-versioning-specification-semver>
# Major.Minor.Patch(-prerelease)(+buildmetadata) # Major.Minor.Patch(-prerelease)(+buildmetadata)
# NB: Do *not* import this, use the functions appversion() and appversion_nobuild() # NB: Do *not* import this, use the functions appversion() and appversion_nobuild()
_static_appversion = '5.9.5' _static_appversion = '5.10.0-alpha0'
_cached_version: Optional[semantic_version.Version] = None _cached_version: semantic_version.Version | None = None
copyright = '© 2015-2019 Jonathan Harris, 2020-2023 EDCD' copyright = '© 2015-2019 Jonathan Harris, 2020-2023 EDCD'
update_feed = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml' update_feed = 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml'
@ -66,7 +68,7 @@ debug_senders: list[str] = []
trace_on: list[str] = [] trace_on: list[str] = []
capi_pretend_down: bool = False capi_pretend_down: bool = False
capi_debug_access_token: Optional[str] = None capi_debug_access_token: str | None = None
# This must be done here in order to avoid an import cycle with EDMCLogging. # This must be done here in order to avoid an import cycle with EDMCLogging.
# Other code should use EDMCLogging.get_main_logger # Other code should use EDMCLogging.get_main_logger
if os.getenv("EDMC_NO_UI"): if os.getenv("EDMC_NO_UI"):
@ -79,7 +81,6 @@ else:
_T = TypeVar('_T') _T = TypeVar('_T')
###########################################################################
def git_shorthash_from_head() -> str: def git_shorthash_from_head() -> str:
""" """
Determine short hash for current git HEAD. Determine short hash for current git HEAD.
@ -91,13 +92,14 @@ def git_shorthash_from_head() -> str:
shorthash: str = None # type: ignore shorthash: str = None # type: ignore
try: try:
git_cmd = subprocess.Popen('git rev-parse --short HEAD'.split(), git_cmd = subprocess.Popen(
"git rev-parse --short HEAD".split(),
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT stderr=subprocess.STDOUT,
) )
out, err = git_cmd.communicate() out, err = git_cmd.communicate()
except Exception as e: except subprocess.CalledProcessError as e:
logger.info(f"Couldn't run git command for short hash: {e!r}") logger.info(f"Couldn't run git command for short hash: {e!r}")
else: else:
@ -131,7 +133,7 @@ def appversion() -> semantic_version.Version:
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
# Running frozen, so we should have a .gitversion file # Running frozen, so we should have a .gitversion file
# Yes, .parent because if frozen we're inside library.zip # Yes, .parent because if frozen we're inside library.zip
with open(pathlib.Path(sys.path[0]).parent / GITVERSION_FILE, 'r', encoding='utf-8') as gitv: with open(pathlib.Path(sys.path[0]).parent / GITVERSION_FILE, encoding='utf-8') as gitv:
shorthash = gitv.read() shorthash = gitv.read()
else: else:
@ -157,11 +159,14 @@ def appversion_nobuild() -> semantic_version.Version:
:return: App version without any build meta data. :return: App version without any build meta data.
""" """
return appversion().truncate('prerelease') return appversion().truncate('prerelease')
###########################################################################
class AbstractConfig(abc.ABC): class AbstractConfig(abc.ABC):
"""Abstract root class of all platform specific Config implementations.""" """
Abstract root class of all platform specific Config implementations.
Commented lines are no longer supported or replaced.
"""
OUT_EDDN_SEND_STATION_DATA = 1 OUT_EDDN_SEND_STATION_DATA = 1
# OUT_MKT_BPC = 2 # No longer supported # OUT_MKT_BPC = 2 # No longer supported
@ -185,7 +190,6 @@ class AbstractConfig(abc.ABC):
respath_path: pathlib.Path respath_path: pathlib.Path
home_path: pathlib.Path home_path: pathlib.Path
default_journal_dir_path: pathlib.Path default_journal_dir_path: pathlib.Path
identifier: str identifier: str
__in_shutdown = False # Is the application currently shutting down ? __in_shutdown = False # Is the application currently shutting down ?
@ -241,7 +245,7 @@ class AbstractConfig(abc.ABC):
self.__eddn_url = eddn_url self.__eddn_url = eddn_url
@property @property
def eddn_url(self) -> Optional[str]: def eddn_url(self) -> str | None:
""" """
Provide the custom EDDN URL. Provide the custom EDDN URL.
@ -296,14 +300,14 @@ class AbstractConfig(abc.ABC):
def _suppress_call( def _suppress_call(
func: Callable[..., _T], exceptions: Type[BaseException] | list[Type[BaseException]] = Exception, func: Callable[..., _T], exceptions: Type[BaseException] | list[Type[BaseException]] = Exception,
*args: Any, **kwargs: Any *args: Any, **kwargs: Any
) -> Optional[_T]: ) -> _T | None:
if exceptions is None: if exceptions is None:
exceptions = [Exception] exceptions = [Exception]
if not isinstance(exceptions, list): if not isinstance(exceptions, list):
exceptions = [exceptions] exceptions = [exceptions]
with contextlib.suppress(*exceptions): # type: ignore # it works fine, mypy with contextlib.suppress(*exceptions):
return func(*args, **kwargs) return func(*args, **kwargs)
return None return None
@ -326,16 +330,16 @@ class AbstractConfig(abc.ABC):
if (a_list := self._suppress_call(self.get_list, ValueError, key, default=None)) is not None: if (a_list := self._suppress_call(self.get_list, ValueError, key, default=None)) is not None:
return a_list return a_list
elif (a_str := self._suppress_call(self.get_str, ValueError, key, default=None)) is not None: if (a_str := self._suppress_call(self.get_str, ValueError, key, default=None)) is not None:
return a_str return a_str
elif (a_bool := self._suppress_call(self.get_bool, ValueError, key, default=None)) is not None: if (a_bool := self._suppress_call(self.get_bool, ValueError, key, default=None)) is not None:
return a_bool return a_bool
elif (an_int := self._suppress_call(self.get_int, ValueError, key, default=None)) is not None: if (an_int := self._suppress_call(self.get_int, ValueError, key, default=None)) is not None:
return an_int return an_int
return default # type: ignore return default
@abstractmethod @abstractmethod
def get_list(self, key: str, *, default: list | None = None) -> list: def get_list(self, key: str, *, default: list | None = None) -> list:
@ -462,15 +466,14 @@ def get_config(*args, **kwargs) -> AbstractConfig:
from .darwin import MacConfig from .darwin import MacConfig
return MacConfig(*args, **kwargs) return MacConfig(*args, **kwargs)
elif sys.platform == "win32": # pragma: sys-platform-win32 if sys.platform == "win32": # pragma: sys-platform-win32
from .windows import WinConfig from .windows import WinConfig
return WinConfig(*args, **kwargs) return WinConfig(*args, **kwargs)
elif sys.platform == "linux": # pragma: sys-platform-linux if sys.platform == "linux": # pragma: sys-platform-linux
from .linux import LinuxConfig from .linux import LinuxConfig
return LinuxConfig(*args, **kwargs) return LinuxConfig(*args, **kwargs)
else: # pragma: sys-platform-not-known
raise ValueError(f'Unknown platform: {sys.platform=}') raise ValueError(f'Unknown platform: {sys.platform=}')

View File

@ -1,13 +1,19 @@
"""Darwin/macOS implementation of AbstractConfig.""" """
darwin.py - Darwin/macOS implementation of AbstractConfig.
Copyright (c) EDCD, All Rights Reserved
Licensed under the GNU General Public License.
See LICENSE file.
"""
from __future__ import annotations
import pathlib import pathlib
import sys import sys
from typing import Any, Dict, List, Union from typing import Any
from Foundation import ( # type: ignore from Foundation import ( # type: ignore
NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains, NSUserDefaults, NSApplicationSupportDirectory, NSBundle, NSDocumentDirectory, NSSearchPathForDirectoriesInDomains, NSUserDefaults,
NSUserDomainMask NSUserDomainMask
) )
from config import AbstractConfig, appname, logger from config import AbstractConfig, appname, logger
assert sys.platform == 'darwin' assert sys.platform == 'darwin'
@ -48,14 +54,14 @@ class MacConfig(AbstractConfig):
self.default_journal_dir_path = support_path / 'Frontier Developments' / 'Elite Dangerous' self.default_journal_dir_path = support_path / 'Frontier Developments' / 'Elite Dangerous'
self._defaults: Any = NSUserDefaults.standardUserDefaults() self._defaults: Any = NSUserDefaults.standardUserDefaults()
self._settings: Dict[str, Union[int, str, list]] = dict( self._settings: dict[str, int | str | list] = dict(
self._defaults.persistentDomainForName_(self.identifier) or {} self._defaults.persistentDomainForName_(self.identifier) or {}
) # make writeable ) # make writeable
if (out_dir := self.get_str('out_dir')) is None or not pathlib.Path(out_dir).exists(): if (out_dir := self.get_str('out_dir')) is None or not pathlib.Path(out_dir).exists():
self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0]) self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0])
def __raw_get(self, key: str) -> Union[None, list, str, int]: def __raw_get(self, key: str) -> None | list | str | int:
""" """
Retrieve the raw data for the given key. Retrieve the raw data for the given key.
@ -82,7 +88,7 @@ class MacConfig(AbstractConfig):
""" """
res = self.__raw_get(key) res = self.__raw_get(key)
if res is None: if res is None:
return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default return default # Yes it could be None, but we're _assuming_ that people gave us a default
if not isinstance(res, str): if not isinstance(res, str):
raise ValueError(f'unexpected data returned from __raw_get: {type(res)=} {res}') raise ValueError(f'unexpected data returned from __raw_get: {type(res)=} {res}')
@ -97,9 +103,9 @@ class MacConfig(AbstractConfig):
""" """
res = self.__raw_get(key) res = self.__raw_get(key)
if res is None: if res is None:
return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default return default # Yes it could be None, but we're _assuming_ that people gave us a default
elif not isinstance(res, list): if not isinstance(res, list):
raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}') raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
return res return res
@ -114,7 +120,7 @@ class MacConfig(AbstractConfig):
if res is None: if res is None:
return default return default
elif not isinstance(res, (str, int)): if not isinstance(res, (str, int)):
raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}') raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
try: try:
@ -122,7 +128,7 @@ class MacConfig(AbstractConfig):
except ValueError as e: except ValueError as e:
logger.error(f'__raw_get returned {res!r} which cannot be parsed to an int: {e}') logger.error(f'__raw_get returned {res!r} which cannot be parsed to an int: {e}')
return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default return default # Yes it could be None, but we're _assuming_ that people gave us a default
def get_bool(self, key: str, *, default: bool = None) -> bool: def get_bool(self, key: str, *, default: bool = None) -> bool:
""" """
@ -132,14 +138,14 @@ class MacConfig(AbstractConfig):
""" """
res = self.__raw_get(key) res = self.__raw_get(key)
if res is None: if res is None:
return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default return default # Yes it could be None, but we're _assuming_ that people gave us a default
elif not isinstance(res, bool): if not isinstance(res, bool):
raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}') raise ValueError(f'__raw_get returned unexpected type {type(res)=} {res!r}')
return res return res
def set(self, key: str, val: Union[int, str, List[str], bool]) -> None: def set(self, key: str, val: int | str | list[str] | bool) -> None:
""" """
Set the given key's data to the given value. Set the given key's data to the given value.

View File

@ -1,9 +1,14 @@
"""Linux config implementation.""" """
linux.py - Linux config implementation.
Copyright (c) EDCD, All Rights Reserved
Licensed under the GNU General Public License.
See LICENSE file.
"""
import os import os
import pathlib import pathlib
import sys import sys
from configparser import ConfigParser from configparser import ConfigParser
from config import AbstractConfig, appname, logger from config import AbstractConfig, appname, logger
assert sys.platform == 'linux' assert sys.platform == 'linux'

View File

@ -1,6 +1,12 @@
"""Windows config implementation.""" """
windows.py - Windows config implementation.
Copyright (c) EDCD, All Rights Reserved
Licensed under the GNU General Public License.
See LICENSE file.
"""
from __future__ import annotations
# spell-checker: words folderid deps hkey edcd
import ctypes import ctypes
import functools import functools
import pathlib import pathlib
@ -8,8 +14,7 @@ import sys
import uuid import uuid
import winreg import winreg
from ctypes.wintypes import DWORD, HANDLE from ctypes.wintypes import DWORD, HANDLE
from typing import List, Literal, Optional, Union from typing import Literal
from config import AbstractConfig, applongname, appname, logger, update_interval from config import AbstractConfig, applongname, appname, logger, update_interval
assert sys.platform == 'win32' assert sys.platform == 'win32'
@ -29,7 +34,7 @@ CoTaskMemFree = ctypes.windll.ole32.CoTaskMemFree
CoTaskMemFree.argtypes = [ctypes.c_void_p] CoTaskMemFree.argtypes = [ctypes.c_void_p]
def known_folder_path(guid: uuid.UUID) -> Optional[str]: def known_folder_path(guid: uuid.UUID) -> str | None:
"""Look up a Windows GUID to actual folder path name.""" """Look up a Windows GUID to actual folder path name."""
buf = ctypes.c_wchar_p() buf = ctypes.c_wchar_p()
if SHGetKnownFolderPath(ctypes.create_string_buffer(guid.bytes_le), 0, 0, ctypes.byref(buf)): if SHGetKnownFolderPath(ctypes.create_string_buffer(guid.bytes_le), 0, 0, ctypes.byref(buf)):
@ -43,7 +48,8 @@ class WinConfig(AbstractConfig):
"""Implementation of AbstractConfig for Windows.""" """Implementation of AbstractConfig for Windows."""
def __init__(self, do_winsparkle=True) -> None: def __init__(self, do_winsparkle=True) -> None:
self.app_dir_path = pathlib.Path(str(known_folder_path(FOLDERID_LocalAppData))) / appname super().__init__()
self.app_dir_path = pathlib.Path(known_folder_path(FOLDERID_LocalAppData)) / appname # type: ignore
self.app_dir_path.mkdir(exist_ok=True) self.app_dir_path.mkdir(exist_ok=True)
self.plugin_dir_path = self.app_dir_path / 'plugins' self.plugin_dir_path = self.app_dir_path / 'plugins'
@ -52,19 +58,17 @@ class WinConfig(AbstractConfig):
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
self.respath_path = pathlib.Path(sys.executable).parent self.respath_path = pathlib.Path(sys.executable).parent
self.internal_plugin_dir_path = self.respath_path / 'plugins' self.internal_plugin_dir_path = self.respath_path / 'plugins'
else: else:
self.respath_path = pathlib.Path(__file__).parent.parent self.respath_path = pathlib.Path(__file__).parent.parent
self.internal_plugin_dir_path = self.respath_path / 'plugins' self.internal_plugin_dir_path = self.respath_path / 'plugins'
self.home_path = pathlib.Path.home() self.home_path = pathlib.Path.home()
journal_dir_str = known_folder_path(FOLDERID_SavedGames) journal_dir_path = pathlib.Path(
journaldir = pathlib.Path(journal_dir_str) if journal_dir_str is not None else None known_folder_path(FOLDERID_SavedGames)) / 'Frontier Developments' / 'Elite Dangerous' # type: ignore
self.default_journal_dir_path = None # type: ignore self.default_journal_dir_path = journal_dir_path if journal_dir_path.is_dir() else None # type: ignore
if journaldir is not None:
self.default_journal_dir_path = journaldir / 'Frontier Developments' / 'Elite Dangerous'
REGISTRY_SUBKEY = r'Software\Marginal\EDMarketConnector' # noqa: N806
create_key_defaults = functools.partial( create_key_defaults = functools.partial(
winreg.CreateKeyEx, winreg.CreateKeyEx,
key=winreg.HKEY_CURRENT_USER, key=winreg.HKEY_CURRENT_USER,
@ -72,20 +76,18 @@ class WinConfig(AbstractConfig):
) )
try: try:
self.__reg_handle: winreg.HKEYType = create_key_defaults( self.__reg_handle: winreg.HKEYType = create_key_defaults(sub_key=REGISTRY_SUBKEY)
sub_key=r'Software\Marginal\EDMarketConnector'
)
if do_winsparkle: if do_winsparkle:
self.__setup_winsparkle() self.__setup_winsparkle()
except OSError: except OSError:
logger.exception('could not create required registry keys') logger.exception('Could not create required registry keys')
raise raise
self.identifier = applongname self.identifier = applongname
if (outdir_str := self.get_str('outdir')) is None or not pathlib.Path(outdir_str).is_dir(): if (outdir_str := self.get_str('outdir')) is None or not pathlib.Path(outdir_str).is_dir():
docs = known_folder_path(FOLDERID_Documents) docs = known_folder_path(FOLDERID_Documents)
self.set('outdir', docs if docs is not None else self.home) self.set("outdir", docs if docs is not None else self.home)
def __setup_winsparkle(self): def __setup_winsparkle(self):
"""Ensure the necessary Registry keys for WinSparkle are present.""" """Ensure the necessary Registry keys for WinSparkle are present."""
@ -94,32 +96,30 @@ class WinConfig(AbstractConfig):
key=winreg.HKEY_CURRENT_USER, key=winreg.HKEY_CURRENT_USER,
access=winreg.KEY_ALL_ACCESS | winreg.KEY_WOW64_64KEY, access=winreg.KEY_ALL_ACCESS | winreg.KEY_WOW64_64KEY,
) )
try:
edcd_handle: winreg.HKEYType = create_key_defaults(sub_key=r'Software\EDCD\EDMarketConnector')
winsparkle_reg: winreg.HKEYType = winreg.CreateKeyEx(
edcd_handle, sub_key='WinSparkle', access=winreg.KEY_ALL_ACCESS | winreg.KEY_WOW64_64KEY
)
except OSError:
logger.exception('could not open WinSparkle handle')
raise
# set WinSparkle defaults - https://github.com/vslavik/winsparkle/wiki/Registry-Settings
winreg.SetValueEx(
winsparkle_reg, 'UpdateInterval', REG_RESERVED_ALWAYS_ZERO, winreg.REG_SZ, str(update_interval)
)
try: try:
winreg.QueryValueEx(winsparkle_reg, 'CheckForUpdates') with create_key_defaults(sub_key=r'Software\EDCD\EDMarketConnector') as edcd_handle:
with winreg.CreateKeyEx(edcd_handle, sub_key='WinSparkle',
access=winreg.KEY_ALL_ACCESS | winreg.KEY_WOW64_64KEY) as winsparkle_reg:
# Set WinSparkle defaults - https://github.com/vslavik/winsparkle/wiki/Registry-Settings
UPDATE_INTERVAL_NAME = 'UpdateInterval' # noqa: N806
CHECK_FOR_UPDATES_NAME = 'CheckForUpdates' # noqa: N806
REG_SZ = winreg.REG_SZ # noqa: N806
winreg.SetValueEx(winsparkle_reg, UPDATE_INTERVAL_NAME, REG_RESERVED_ALWAYS_ZERO, REG_SZ,
str(update_interval))
try:
winreg.QueryValueEx(winsparkle_reg, CHECK_FOR_UPDATES_NAME)
except FileNotFoundError: except FileNotFoundError:
# Key doesn't exist, set it to a default # Key doesn't exist, set it to a default
winreg.SetValueEx(winsparkle_reg, 'CheckForUpdates', REG_RESERVED_ALWAYS_ZERO, winreg.REG_SZ, '1') winreg.SetValueEx(winsparkle_reg, CHECK_FOR_UPDATES_NAME, REG_RESERVED_ALWAYS_ZERO, REG_SZ,
'1')
except OSError:
logger.exception('Could not open WinSparkle handle')
raise
winsparkle_reg.Close() def __get_regentry(self, key: str) -> None | list | str | int:
edcd_handle.Close()
def __get_regentry(self, key: str) -> Union[None, list, str, int]:
"""Access the Registry for the raw entry.""" """Access the Registry for the raw entry."""
try: try:
value, _type = winreg.QueryValueEx(self.__reg_handle, key) value, _type = winreg.QueryValueEx(self.__reg_handle, key)
@ -127,19 +127,17 @@ class WinConfig(AbstractConfig):
# Key doesn't exist # Key doesn't exist
return None return None
# The type returned is actually as we'd expect for each of these. The casts are here for type checkers and
# For programmers who want to actually know what is going on # For programmers who want to actually know what is going on
if _type == winreg.REG_SZ: if _type == winreg.REG_SZ:
return str(value) return str(value)
elif _type == winreg.REG_DWORD: if _type == winreg.REG_DWORD:
return int(value) return int(value)
elif _type == winreg.REG_MULTI_SZ: if _type == winreg.REG_MULTI_SZ:
return list(value) return list(value)
else: logger.warning(f'Registry key {key=} returned unknown type {_type=} {value=}')
logger.warning(f'registry key {key=} returned unknown type {_type=} {value=}')
return None return None
def get_str(self, key: str, *, default: str | None = None) -> str: def get_str(self, key: str, *, default: str | None = None) -> str:
@ -152,7 +150,7 @@ class WinConfig(AbstractConfig):
if res is None: if res is None:
return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default
elif not isinstance(res, str): if not isinstance(res, str):
raise ValueError(f'Data from registry is not a string: {type(res)=} {res=}') raise ValueError(f'Data from registry is not a string: {type(res)=} {res=}')
return res return res
@ -167,7 +165,7 @@ class WinConfig(AbstractConfig):
if res is None: if res is None:
return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default
elif not isinstance(res, list): if not isinstance(res, list):
raise ValueError(f'Data from registry is not a list: {type(res)=} {res}') raise ValueError(f'Data from registry is not a list: {type(res)=} {res}')
return res return res
@ -195,11 +193,11 @@ class WinConfig(AbstractConfig):
""" """
res = self.get_int(key, default=default) # type: ignore res = self.get_int(key, default=default) # type: ignore
if res is None: if res is None:
return default # type: ignore # Yes it could be None, but we're _assuming_ that people gave us a default return default # Yes it could be None, but we're _assuming_ that people gave us a default
return bool(res) return bool(res)
def set(self, key: str, val: Union[int, str, List[str], bool]) -> None: def set(self, key: str, val: int | str | list[str] | bool) -> None:
""" """
Set the given key's data to the given value. Set the given key's data to the given value.
@ -209,9 +207,8 @@ class WinConfig(AbstractConfig):
reg_type: Literal[1] | Literal[4] | Literal[7] reg_type: Literal[1] | Literal[4] | Literal[7]
if isinstance(val, str): if isinstance(val, str):
reg_type = winreg.REG_SZ reg_type = winreg.REG_SZ
winreg.SetValueEx(self.__reg_handle, key, REG_RESERVED_ALWAYS_ZERO, winreg.REG_SZ, val)
elif isinstance(val, int): # The original code checked for numbers.Integral, I dont think that is needed. elif isinstance(val, int):
reg_type = winreg.REG_DWORD reg_type = winreg.REG_DWORD
elif isinstance(val, list): elif isinstance(val, list):
@ -224,7 +221,6 @@ class WinConfig(AbstractConfig):
else: else:
raise ValueError(f'Unexpected type for value {type(val)=}') raise ValueError(f'Unexpected type for value {type(val)=}')
# Its complaining about the list, it works, tested on windows, ignored.
winreg.SetValueEx(self.__reg_handle, key, REG_RESERVED_ALWAYS_ZERO, reg_type, val) # type: ignore winreg.SetValueEx(self.__reg_handle, key, REG_RESERVED_ALWAYS_ZERO, reg_type, val) # type: ignore
def delete(self, key: str, *, suppress=False) -> None: def delete(self, key: str, *, suppress=False) -> None:

View File

@ -19,6 +19,7 @@ referenced in this file (or only in any other core plugin), and if so...
`build.py` TO ENSURE THE FILES ARE ACTUALLY PRESENT `build.py` TO ENSURE THE FILES ARE ACTUALLY PRESENT
IN AN END-USER INSTALLATION ON WINDOWS. IN AN END-USER INSTALLATION ON WINDOWS.
""" """
from __future__ import annotations
import base64 import base64
import gzip import gzip
@ -26,7 +27,7 @@ import io
import json import json
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Union, Optional from typing import TYPE_CHECKING
import myNotebook as nb # noqa: N813 # its not my fault. import myNotebook as nb # noqa: N813 # its not my fault.
from EDMCLogging import get_main_logger from EDMCLogging import get_main_logger
from plug import show_error from plug import show_error
@ -80,7 +81,7 @@ def plugin_start3(path: str) -> str:
return 'Coriolis' return 'Coriolis'
def plugin_prefs(parent: ttk.Notebook, cmdr: Optional[str], is_beta: bool) -> tk.Frame: def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Frame:
"""Set up plugin preferences.""" """Set up plugin preferences."""
PADX = 10 # noqa: N806 PADX = 10 # noqa: N806
@ -130,7 +131,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: Optional[str], is_beta: bool) -> tk
return conf_frame return conf_frame
def prefs_changed(cmdr: Optional[str], is_beta: bool) -> None: def prefs_changed(cmdr: str | None, is_beta: bool) -> None:
""" """
Update URLs and override mode based on user preferences. Update URLs and override mode based on user preferences.
@ -175,7 +176,7 @@ def _get_target_url(is_beta: bool) -> str:
return coriolis_config.normal_url return coriolis_config.normal_url
def shipyard_url(loadout, is_beta) -> Union[str, bool]: def shipyard_url(loadout, is_beta) -> str | bool:
"""Return a URL for the current ship.""" """Return a URL for the current ship."""
# most compact representation # most compact representation
string = json.dumps(loadout, ensure_ascii=False, sort_keys=True, separators=(',', ':')).encode('utf-8') string = json.dumps(loadout, ensure_ascii=False, sort_keys=True, separators=(',', ':')).encode('utf-8')

View File

@ -18,6 +18,8 @@ referenced in this file (or only in any other core plugin), and if so...
`build.py` TO ENSURE THE FILES ARE ACTUALLY PRESENT `build.py` TO ENSURE THE FILES ARE ACTUALLY PRESENT
IN AN END-USER INSTALLATION ON WINDOWS. IN AN END-USER INSTALLATION ON WINDOWS.
""" """
from __future__ import annotations
import http import http
import itertools import itertools
import json import json
@ -37,9 +39,6 @@ from typing import (
Iterator, Iterator,
Mapping, Mapping,
MutableMapping, MutableMapping,
Optional,
Dict,
List,
) )
from typing import OrderedDict as OrderedDictT from typing import OrderedDict as OrderedDictT
from typing import Tuple, Union from typing import Tuple, Union
@ -86,27 +85,27 @@ class This:
self.odyssey = False self.odyssey = False
# Track location to add to Journal events # Track location to add to Journal events
self.system_address: Optional[str] = None self.system_address: str | None = None
self.system_name: Optional[str] = None self.system_name: str | None = None
self.coordinates: Optional[Tuple] = None self.coordinates: tuple | None = None
self.body_name: Optional[str] = None self.body_name: str | None = None
self.body_id: Optional[int] = None self.body_id: int | None = None
self.body_type: Optional[int] = None self.body_type: int | None = None
self.station_name: Optional[str] = None self.station_name: str | None = None
self.station_type: Optional[str] = None self.station_type: str | None = None
self.station_marketid: Optional[str] = None self.station_marketid: str | None = None
# Track Status.json data # Track Status.json data
self.status_body_name: Optional[str] = None self.status_body_name: str | None = None
# Avoid duplicates # Avoid duplicates
self.marketId: Optional[str] = None self.marketId: str | None = None
self.commodities: Optional[List[OrderedDictT[str, Any]]] = None self.commodities: list[OrderedDictT[str, Any]] | None = None
self.outfitting: Optional[Tuple[bool, List[str]]] = None self.outfitting: Tuple[bool, list[str]] | None = None
self.shipyard: Optional[Tuple[bool, List[Mapping[str, Any]]]] = None self.shipyard: Tuple[bool, list[Mapping[str, Any]]] | None = None
self.fcmaterials_marketid: int = 0 self.fcmaterials_marketid: int = 0
self.fcmaterials: Optional[List[OrderedDictT[str, Any]]] = None self.fcmaterials: list[OrderedDictT[str, Any]] | None = None
self.fcmaterials_capi_marketid: int = 0 self.fcmaterials_capi_marketid: int = 0
self.fcmaterials_capi: Optional[List[OrderedDictT[str, Any]]] = None self.fcmaterials_capi: list[OrderedDictT[str, Any]] | None = None
# For the tkinter parent window, so we can call update_idletasks() # For the tkinter parent window, so we can call update_idletasks()
self.parent: tk.Tk self.parent: tk.Tk
@ -395,7 +394,7 @@ class EDDNSender:
""" """
logger.trace_if("plugin.eddn.send", "Sending message") logger.trace_if("plugin.eddn.send", "Sending message")
should_return: bool should_return: bool
new_data: Dict[str, Any] new_data: dict[str, Any]
should_return, new_data = killswitch.check_killswitch('plugins.eddn.send', json.loads(msg)) should_return, new_data = killswitch.check_killswitch('plugins.eddn.send', json.loads(msg))
if should_return: if should_return:
@ -404,7 +403,7 @@ class EDDNSender:
# Even the smallest possible message compresses somewhat, so always compress # Even the smallest possible message compresses somewhat, so always compress
encoded, compressed = text.gzip(json.dumps(new_data, separators=(',', ':')), max_size=0) encoded, compressed = text.gzip(json.dumps(new_data, separators=(',', ':')), max_size=0)
headers: Optional[Dict[str, str]] = None headers: dict[str, str] | None = None
if compressed: if compressed:
headers = {'Content-Encoding': 'gzip'} headers = {'Content-Encoding': 'gzip'}
@ -612,7 +611,7 @@ class EDDN:
self.sender = EDDNSender(self, self.eddn_url) self.sender = EDDNSender(self, self.eddn_url)
self.fss_signals: List[Mapping[str, Any]] = [] self.fss_signals: list[Mapping[str, Any]] = []
def close(self): def close(self):
"""Close down the EDDN class instance.""" """Close down the EDDN class instance."""
@ -636,7 +635,7 @@ class EDDN:
:param is_beta: whether or not we're currently in beta mode :param is_beta: whether or not we're currently in beta mode
""" """
should_return: bool should_return: bool
new_data: Dict[str, Any] new_data: dict[str, Any]
should_return, new_data = killswitch.check_killswitch('capi.request./market', {}) should_return, new_data = killswitch.check_killswitch('capi.request./market', {})
if should_return: if should_return:
logger.warning("capi.request./market has been disabled by killswitch. Returning.") logger.warning("capi.request./market has been disabled by killswitch. Returning.")
@ -653,7 +652,7 @@ class EDDN:
modules, modules,
ships ships
) )
commodities: List[OrderedDictT[str, Any]] = [] commodities: list[OrderedDictT[str, Any]] = []
for commodity in data['lastStarport'].get('commodities') or []: for commodity in data['lastStarport'].get('commodities') or []:
# Check 'marketable' and 'not prohibited' # Check 'marketable' and 'not prohibited'
if (category_map.get(commodity['categoryname'], True) if (category_map.get(commodity['categoryname'], True)
@ -726,7 +725,7 @@ class EDDN:
:param data: The raw CAPI data. :param data: The raw CAPI data.
:return: Sanity-checked data. :return: Sanity-checked data.
""" """
modules: Dict[str, Any] = data['lastStarport'].get('modules') modules: dict[str, Any] = data['lastStarport'].get('modules')
if modules is None or not isinstance(modules, dict): if modules is None or not isinstance(modules, dict):
if modules is None: if modules is None:
logger.debug('modules was None. FC or Damaged Station?') logger.debug('modules was None. FC or Damaged Station?')
@ -743,13 +742,13 @@ class EDDN:
# Set a safe value # Set a safe value
modules = {} modules = {}
ships: Dict[str, Any] = data['lastStarport'].get('ships') ships: dict[str, Any] = data['lastStarport'].get('ships')
if ships is None or not isinstance(ships, dict): if ships is None or not isinstance(ships, dict):
if ships is None: if ships is None:
logger.debug('ships was None') logger.debug('ships was None')
else: else:
logger.error(f'ships was neither None nor a Dict! Type = {type(ships)}') logger.error(f'ships was neither None nor a dict! Type = {type(ships)}')
# Set a safe value # Set a safe value
ships = {'shipyard_list': {}, 'unavailable_list': []} ships = {'shipyard_list': {}, 'unavailable_list': []}
@ -769,7 +768,7 @@ class EDDN:
:param is_beta: whether or not we're currently in beta mode :param is_beta: whether or not we're currently in beta mode
""" """
should_return: bool should_return: bool
new_data: Dict[str, Any] new_data: dict[str, Any]
should_return, new_data = killswitch.check_killswitch('capi.request./shipyard', {}) should_return, new_data = killswitch.check_killswitch('capi.request./shipyard', {})
if should_return: if should_return:
logger.warning("capi.request./shipyard has been disabled by killswitch. Returning.") logger.warning("capi.request./shipyard has been disabled by killswitch. Returning.")
@ -796,7 +795,7 @@ class EDDN:
modules.values() modules.values()
) )
outfitting: List[str] = sorted( outfitting: list[str] = sorted(
self.MODULE_RE.sub(lambda match: match.group(0).capitalize(), mod['name'].lower()) for mod in to_search self.MODULE_RE.sub(lambda match: match.group(0).capitalize(), mod['name'].lower()) for mod in to_search
) )
@ -837,7 +836,7 @@ class EDDN:
:param is_beta: whether or not we are in beta mode :param is_beta: whether or not we are in beta mode
""" """
should_return: bool should_return: bool
new_data: Dict[str, Any] new_data: dict[str, Any]
should_return, new_data = killswitch.check_killswitch('capi.request./shipyard', {}) should_return, new_data = killswitch.check_killswitch('capi.request./shipyard', {})
if should_return: if should_return:
logger.warning("capi.request./shipyard has been disabled by killswitch. Returning.") logger.warning("capi.request./shipyard has been disabled by killswitch. Returning.")
@ -856,7 +855,7 @@ class EDDN:
ships ships
) )
shipyard: List[Mapping[str, Any]] = sorted( shipyard: list[Mapping[str, Any]] = sorted(
itertools.chain( itertools.chain(
(ship['name'].lower() for ship in (ships['shipyard_list'] or {}).values()), (ship['name'].lower() for ship in (ships['shipyard_list'] or {}).values()),
(ship['name'].lower() for ship in ships['unavailable_list'] or {}), (ship['name'].lower() for ship in ships['unavailable_list'] or {}),
@ -899,8 +898,8 @@ class EDDN:
:param is_beta: whether or not we're in beta mode :param is_beta: whether or not we're in beta mode
:param entry: the journal entry containing the commodities data :param entry: the journal entry containing the commodities data
""" """
items: List[Mapping[str, Any]] = entry.get('Items') or [] items: list[Mapping[str, Any]] = entry.get('Items') or []
commodities: List[OrderedDictT[str, Any]] = sorted((OrderedDict([ commodities: list[OrderedDictT[str, Any]] = sorted((OrderedDict([
('name', self.canonicalise(commodity['Name'])), ('name', self.canonicalise(commodity['Name'])),
('meanPrice', commodity['MeanPrice']), ('meanPrice', commodity['MeanPrice']),
('buyPrice', commodity['BuyPrice']), ('buyPrice', commodity['BuyPrice']),
@ -947,11 +946,11 @@ class EDDN:
:param is_beta: Whether or not we're in beta mode :param is_beta: Whether or not we're in beta mode
:param entry: The relevant journal entry :param entry: The relevant journal entry
""" """
modules: List[Mapping[str, Any]] = entry.get('Items', []) modules: list[Mapping[str, Any]] = entry.get('Items', [])
horizons: bool = entry.get('Horizons', False) horizons: bool = entry.get('Horizons', False)
# outfitting = sorted([self.MODULE_RE.sub(lambda m: m.group(0).capitalize(), module['Name']) # outfitting = sorted([self.MODULE_RE.sub(lambda m: m.group(0).capitalize(), module['Name'])
# for module in modules if module['Name'] != 'int_planetapproachsuite']) # for module in modules if module['Name'] != 'int_planetapproachsuite'])
outfitting: List[str] = sorted( outfitting: list[str] = sorted(
self.MODULE_RE.sub(lambda m: m.group(0).capitalize(), mod['Name']) for mod in self.MODULE_RE.sub(lambda m: m.group(0).capitalize(), mod['Name']) for mod in
filter(lambda m: m['Name'] != 'int_planetapproachsuite', modules) filter(lambda m: m['Name'] != 'int_planetapproachsuite', modules)
) )
@ -986,7 +985,7 @@ class EDDN:
:param is_beta: Whether or not we're in beta mode :param is_beta: Whether or not we're in beta mode
:param entry: the relevant journal entry :param entry: the relevant journal entry
""" """
ships: List[Mapping[str, Any]] = entry.get('PriceList') or [] ships: list[Mapping[str, Any]] = entry.get('Pricelist') or []
horizons: bool = entry.get('Horizons', False) horizons: bool = entry.get('Horizons', False)
shipyard = sorted(ship['ShipType'] for ship in ships) shipyard = sorted(ship['ShipType'] for ship in ships)
# Don't send empty ships list - shipyard data is only guaranteed present if user has visited the shipyard. # Don't send empty ships list - shipyard data is only guaranteed present if user has visited the shipyard.
@ -1042,7 +1041,7 @@ class EDDN:
self.sender.send_message_by_id(msg_id) self.sender.send_message_by_id(msg_id)
def standard_header( def standard_header(
self, game_version: Optional[str] = None, game_build: Optional[str] = None self, game_version: str | None = None, game_build: str | None = None
) -> MutableMapping[str, Any]: ) -> MutableMapping[str, Any]:
""" """
Return the standard header for an EDDN message, given tracked state. Return the standard header for an EDDN message, given tracked state.
@ -1134,7 +1133,7 @@ class EDDN:
def export_journal_fssdiscoveryscan( def export_journal_fssdiscoveryscan(
self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: Mapping[str, Any] self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: Mapping[str, Any]
) -> Optional[str]: ) -> str | None:
""" """
Send an FSSDiscoveryScan to EDDN on the correct schema. Send an FSSDiscoveryScan to EDDN on the correct schema.
@ -1176,7 +1175,7 @@ class EDDN:
def export_journal_navbeaconscan( def export_journal_navbeaconscan(
self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: Mapping[str, Any] self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: Mapping[str, Any]
) -> Optional[str]: ) -> str | None:
""" """
Send an NavBeaconScan to EDDN on the correct schema. Send an NavBeaconScan to EDDN on the correct schema.
@ -1218,7 +1217,7 @@ class EDDN:
def export_journal_codexentry( # noqa: CCR001 def export_journal_codexentry( # noqa: CCR001
self, cmdr: str, system_starpos: list, is_beta: bool, entry: MutableMapping[str, Any] self, cmdr: str, system_starpos: list, is_beta: bool, entry: MutableMapping[str, Any]
) -> Optional[str]: ) -> str | None:
""" """
Send a CodexEntry to EDDN on the correct schema. Send a CodexEntry to EDDN on the correct schema.
@ -1320,7 +1319,7 @@ class EDDN:
def export_journal_scanbarycentre( def export_journal_scanbarycentre(
self, cmdr: str, system_starpos: list, is_beta: bool, entry: Mapping[str, Any] self, cmdr: str, system_starpos: list, is_beta: bool, entry: Mapping[str, Any]
) -> Optional[str]: ) -> str | None:
""" """
Send a ScanBaryCentre to EDDN on the correct schema. Send a ScanBaryCentre to EDDN on the correct schema.
@ -1374,7 +1373,7 @@ class EDDN:
def export_journal_navroute( def export_journal_navroute(
self, cmdr: str, is_beta: bool, entry: MutableMapping[str, Any] self, cmdr: str, is_beta: bool, entry: MutableMapping[str, Any]
) -> Optional[str]: ) -> str | None:
""" """
Send a NavRoute to EDDN on the correct schema. Send a NavRoute to EDDN on the correct schema.
@ -1447,7 +1446,7 @@ class EDDN:
def export_journal_fcmaterials( def export_journal_fcmaterials(
self, cmdr: str, is_beta: bool, entry: MutableMapping[str, Any] self, cmdr: str, is_beta: bool, entry: MutableMapping[str, Any]
) -> Optional[str]: ) -> str | None:
""" """
Send an FCMaterials message to EDDN on the correct schema. Send an FCMaterials message to EDDN on the correct schema.
@ -1531,7 +1530,7 @@ class EDDN:
def export_capi_fcmaterials( def export_capi_fcmaterials(
self, data: CAPIData, is_beta: bool, horizons: bool self, data: CAPIData, is_beta: bool, horizons: bool
) -> Optional[str]: ) -> str | None:
""" """
Send CAPI-sourced 'onfootmicroresources' data on `fcmaterials/1` schema. Send CAPI-sourced 'onfootmicroresources' data on `fcmaterials/1` schema.
@ -1594,7 +1593,7 @@ class EDDN:
def export_journal_approachsettlement( def export_journal_approachsettlement(
self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: MutableMapping[str, Any] self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: MutableMapping[str, Any]
) -> Optional[str]: ) -> str | None:
""" """
Send an ApproachSettlement to EDDN on the correct schema. Send an ApproachSettlement to EDDN on the correct schema.
@ -1669,7 +1668,7 @@ class EDDN:
def export_journal_fssallbodiesfound( def export_journal_fssallbodiesfound(
self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: MutableMapping[str, Any] self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: MutableMapping[str, Any]
) -> Optional[str]: ) -> str | None:
""" """
Send an FSSAllBodiesFound message to EDDN on the correct schema. Send an FSSAllBodiesFound message to EDDN on the correct schema.
@ -1719,7 +1718,7 @@ class EDDN:
def export_journal_fssbodysignals( def export_journal_fssbodysignals(
self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: MutableMapping[str, Any] self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: MutableMapping[str, Any]
) -> Optional[str]: ) -> str | None:
""" """
Send an FSSBodySignals message to EDDN on the correct schema. Send an FSSBodySignals message to EDDN on the correct schema.
@ -1789,7 +1788,7 @@ class EDDN:
def export_journal_fsssignaldiscovered( def export_journal_fsssignaldiscovered(
self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: MutableMapping[str, Any] self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: MutableMapping[str, Any]
) -> Optional[str]: ) -> str | None:
""" """
Send an FSSSignalDiscovered message to EDDN on the correct schema. Send an FSSSignalDiscovered message to EDDN on the correct schema.
@ -1892,7 +1891,7 @@ class EDDN:
match = self.CANONICALISE_RE.match(item) match = self.CANONICALISE_RE.match(item)
return match and match.group(1) or item return match and match.group(1) or item
def capi_gameversion_from_host_endpoint(self, capi_host: Optional[str], capi_endpoint: str) -> str: def capi_gameversion_from_host_endpoint(self, capi_host: str | None, capi_endpoint: str) -> str:
""" """
Return the correct CAPI gameversion string for the given host/endpoint. Return the correct CAPI gameversion string for the given host/endpoint.
@ -1910,7 +1909,7 @@ class EDDN:
gv = 'CAPI-Legacy-' gv = 'CAPI-Legacy-'
else: else:
# Technically incorrect, but it will inform Listeners # Technically incorrect, but it will inform listeners
logger.error(f"{capi_host=} lead to bad gameversion") logger.error(f"{capi_host=} lead to bad gameversion")
gv = 'CAPI-UNKNOWN-' gv = 'CAPI-UNKNOWN-'
####################################################################### #######################################################################
@ -1924,7 +1923,7 @@ class EDDN:
gv += 'shipyard' gv += 'shipyard'
else: else:
# Technically incorrect, but it will inform Listeners # Technically incorrect, but it will inform listeners
logger.error(f"{capi_endpoint=} lead to bad gameversion") logger.error(f"{capi_endpoint=} lead to bad gameversion")
gv += 'UNKNOWN' gv += 'UNKNOWN'
####################################################################### #######################################################################
@ -1943,7 +1942,7 @@ def plugin_start3(plugin_dir: str) -> str:
return 'EDDN' return 'EDDN'
def plugin_app(parent: tk.Tk) -> Optional[tk.Frame]: def plugin_app(parent: tk.Tk) -> tk.Frame | None:
""" """
Set up any plugin-specific UI. Set up any plugin-specific UI.
@ -2183,7 +2182,7 @@ def filter_localised(d: Mapping[str, Any]) -> OrderedDictT[str, Any]:
""" """
Recursively remove any dict keys with names ending `_Localised` from a dict. Recursively remove any dict keys with names ending `_Localised` from a dict.
:param d: Dict to filter keys of. :param d: dict to filter keys of.
:return: The filtered dict. :return: The filtered dict.
""" """
filtered: OrderedDictT[str, Any] = OrderedDict() filtered: OrderedDictT[str, Any] = OrderedDict()
@ -2207,7 +2206,7 @@ def capi_filter_localised(d: Mapping[str, Any]) -> OrderedDictT[str, Any]:
""" """
Recursively remove any dict keys for known CAPI 'localised' names. Recursively remove any dict keys for known CAPI 'localised' names.
:param d: Dict to filter keys of. :param d: dict to filter keys of.
:return: The filtered dict. :return: The filtered dict.
""" """
filtered: OrderedDictT[str, Any] = OrderedDict() filtered: OrderedDictT[str, Any] = OrderedDict()
@ -2234,7 +2233,7 @@ def journal_entry( # noqa: C901, CCR001
station: str, station: str,
entry: MutableMapping[str, Any], entry: MutableMapping[str, Any],
state: Mapping[str, Any] state: Mapping[str, Any]
) -> Optional[str]: ) -> str | None:
""" """
Process a new Journal entry. Process a new Journal entry.
@ -2491,7 +2490,7 @@ def journal_entry( # noqa: C901, CCR001
return None return None
def cmdr_data_legacy(data: CAPIData, is_beta: bool) -> Optional[str]: def cmdr_data_legacy(data: CAPIData, is_beta: bool) -> str | None:
""" """
Process new CAPI data for Legacy galaxy. Process new CAPI data for Legacy galaxy.
@ -2510,7 +2509,7 @@ def cmdr_data_legacy(data: CAPIData, is_beta: bool) -> Optional[str]:
return cmdr_data(data, is_beta) return cmdr_data(data, is_beta)
def cmdr_data(data: CAPIData, is_beta: bool) -> Optional[str]: # noqa: CCR001 def cmdr_data(data: CAPIData, is_beta: bool) -> str | None: # noqa: CCR001
""" """
Process new CAPI data for not-Legacy galaxy (might be beta). Process new CAPI data for not-Legacy galaxy (might be beta).
@ -2611,7 +2610,7 @@ def capi_is_horizons(economies: MAP_STR_ANY, modules: MAP_STR_ANY, ships: MAP_ST
return economies_colony or modules_horizons or ship_horizons return economies_colony or modules_horizons or ship_horizons
def dashboard_entry(cmdr: str, is_beta: bool, entry: Dict[str, Any]) -> None: def dashboard_entry(cmdr: str, is_beta: bool, entry: dict[str, Any]) -> None:
""" """
Process Status.json data to track things like current Body. Process Status.json data to track things like current Body.

View File

@ -18,6 +18,8 @@ referenced in this file (or only in any other core plugin), and if so...
`build.py` TO ENSURE THE FILES ARE ACTUALLY PRESENT `build.py` TO ENSURE THE FILES ARE ACTUALLY PRESENT
IN AN END-USER INSTALLATION ON WINDOWS. IN AN END-USER INSTALLATION ON WINDOWS.
""" """
from __future__ import annotations
import json import json
import threading import threading
import tkinter as tk import tkinter as tk
@ -26,7 +28,7 @@ from queue import Queue
from threading import Thread from threading import Thread
from time import sleep from time import sleep
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Mapping, MutableMapping, Optional, Set, Tuple, Union, cast from typing import TYPE_CHECKING, Any, Literal, Mapping, MutableMapping, cast
import requests import requests
import killswitch import killswitch
import monitor import monitor
@ -72,27 +74,27 @@ class This:
self.game_build = "" self.game_build = ""
# Handle only sending Live galaxy data # Handle only sending Live galaxy data
self.legacy_galaxy_last_notified: Optional[datetime] = None self.legacy_galaxy_last_notified: datetime | None = None
self.session: requests.Session = requests.Session() self.session: requests.Session = requests.Session()
self.session.headers['User-Agent'] = user_agent self.session.headers['User-Agent'] = user_agent
self.queue: Queue = Queue() # Items to be sent to EDSM by worker thread self.queue: Queue = Queue() # Items to be sent to EDSM by worker thread
self.discarded_events: Set[str] = set() # List discarded events from EDSM self.discarded_events: set[str] = set() # List discarded events from EDSM
self.lastlookup: Dict[str, Any] # Result of last system lookup self.lastlookup: dict[str, Any] # Result of last system lookup
# Game state # Game state
self.multicrew: bool = False # don't send captain's ship info to EDSM while on a crew self.multicrew: bool = False # don't send captain's ship info to EDSM while on a crew
self.coordinates: Optional[Tuple[int, int, int]] = None self.coordinates: tuple[int, int, int] | None = None
self.newgame: bool = False # starting up - batch initial burst of events self.newgame: bool = False # starting up - batch initial burst of events
self.newgame_docked: bool = False # starting up while docked self.newgame_docked: bool = False # starting up while docked
self.navbeaconscan: int = 0 # batch up burst of Scan events after NavBeaconScan self.navbeaconscan: int = 0 # batch up burst of Scan events after NavBeaconScan
self.system_link: Optional[tk.Widget] = None self.system_link: tk.Widget | None = None
self.system_name: Optional[tk.Tk] = None self.system_name: tk.Tk | None = None
self.system_address: Optional[int] = None # Frontier SystemAddress self.system_address: int | None = None # Frontier SystemAddress
self.system_population: Optional[int] = None self.system_population: int | None = None
self.station_link: Optional[tk.Widget] = None self.station_link: tk.Widget | None = None
self.station_name: Optional[str] = None self.station_name: str | None = None
self.station_marketid: Optional[int] = None # Frontier MarketID self.station_marketid: int | None = None # Frontier MarketID
self.on_foot = False self.on_foot = False
self._IMG_KNOWN = None self._IMG_KNOWN = None
@ -100,21 +102,21 @@ class This:
self._IMG_NEW = None self._IMG_NEW = None
self._IMG_ERROR = None self._IMG_ERROR = None
self.thread: Optional[threading.Thread] = None self.thread: threading.Thread | None = None
self.log: Optional[tk.IntVar] = None self.log: tk.IntVar | None = None
self.log_button: Optional[ttk.Checkbutton] = None self.log_button: ttk.Checkbutton | None = None
self.label: Optional[tk.Widget] = None self.label: tk.Widget | None = None
self.cmdr_label: Optional[nb.Label] = None self.cmdr_label: nb.Label | None = None
self.cmdr_text: Optional[nb.Label] = None self.cmdr_text: nb.Label | None = None
self.user_label: Optional[nb.Label] = None self.user_label: nb.Label | None = None
self.user: Optional[nb.Entry] = None self.user: nb.Entry | None = None
self.apikey_label: Optional[nb.Label] = None self.apikey_label: nb.Label | None = None
self.apikey: Optional[nb.Entry] = None self.apikey: nb.Entry | None = None
this = This() this = This()
@ -277,7 +279,7 @@ def toggle_password_visibility():
this.apikey.config(show="*") # type: ignore this.apikey.config(show="*") # type: ignore
def plugin_prefs(parent: ttk.Notebook, cmdr: Optional[str], is_beta: bool) -> tk.Frame: def plugin_prefs(parent: ttk.Notebook, cmdr: str | None, is_beta: bool) -> tk.Frame:
""" """
Plugin preferences setup hook. Plugin preferences setup hook.
@ -361,7 +363,7 @@ def plugin_prefs(parent: ttk.Notebook, cmdr: Optional[str], is_beta: bool) -> tk
return frame return frame
def prefs_cmdr_changed(cmdr: Optional[str], is_beta: bool) -> None: # noqa: CCR001 def prefs_cmdr_changed(cmdr: str | None, is_beta: bool) -> None: # noqa: CCR001
""" """
Handle the Commander name changing whilst Settings was open. Handle the Commander name changing whilst Settings was open.
@ -390,7 +392,7 @@ def prefs_cmdr_changed(cmdr: Optional[str], is_beta: bool) -> None: # noqa: CCR
# LANG: We have no data on the current commander # LANG: We have no data on the current commander
this.cmdr_text['text'] = _('None') this.cmdr_text['text'] = _('None')
to_set: Union[Literal['normal'], Literal['disabled']] = tk.DISABLED to_set: Literal['normal'] | Literal['disabled'] = tk.DISABLED
if cmdr and not is_beta and this.log and this.log.get(): if cmdr and not is_beta and this.log and this.log.get():
to_set = tk.NORMAL to_set = tk.NORMAL
@ -440,9 +442,9 @@ def prefs_changed(cmdr: str, is_beta: bool) -> None:
config.set('edsm_out', this.log.get()) config.set('edsm_out', this.log.get())
if cmdr and not is_beta: if cmdr and not is_beta:
cmdrs: List[str] = config.get_list('edsm_cmdrs', default=[]) cmdrs: list[str] = config.get_list('edsm_cmdrs', default=[])
usernames: List[str] = config.get_list('edsm_usernames', default=[]) usernames: list[str] = config.get_list('edsm_usernames', default=[])
apikeys: List[str] = config.get_list('edsm_apikeys', default=[]) apikeys: list[str] = config.get_list('edsm_apikeys', default=[])
if this.user and this.apikey: if this.user and this.apikey:
if cmdr in cmdrs: if cmdr in cmdrs:
@ -460,7 +462,7 @@ def prefs_changed(cmdr: str, is_beta: bool) -> None:
config.set('edsm_apikeys', apikeys) config.set('edsm_apikeys', apikeys)
def credentials(cmdr: str) -> Optional[Tuple[str, str]]: def credentials(cmdr: str) -> tuple[str, str] | None:
""" """
Get credentials for the given commander, if they exist. Get credentials for the given commander, if they exist.
@ -635,7 +637,7 @@ Queueing: {entry!r}'''
# Update system data # Update system data
def cmdr_data(data: CAPIData, is_beta: bool) -> Optional[str]: # noqa: CCR001 def cmdr_data(data: CAPIData, is_beta: bool) -> str | None: # noqa: CCR001
""" """
Process new CAPI data. Process new CAPI data.
@ -722,7 +724,7 @@ def worker() -> None: # noqa: CCR001 C901
:return: None :return: None
""" """
logger.debug('Starting...') logger.debug('Starting...')
pending: List[Mapping[str, Any]] = [] # Unsent events pending: list[Mapping[str, Any]] = [] # Unsent events
closing = False closing = False
cmdr: str = "" cmdr: str = ""
last_game_version = "" last_game_version = ""
@ -744,7 +746,7 @@ def worker() -> None: # noqa: CCR001 C901
logger.debug(f'{this.shutting_down=}, so setting closing = True') logger.debug(f'{this.shutting_down=}, so setting closing = True')
closing = True closing = True
item: Optional[Tuple[str, str, str, Mapping[str, Any]]] = this.queue.get() item: tuple[str, str, str, Mapping[str, Any]] | None = this.queue.get()
if item: if item:
(cmdr, game_version, game_build, entry) = item (cmdr, game_version, game_build, entry) = item
logger.trace_if(CMDR_EVENTS, f'De-queued ({cmdr=}, {game_version=}, {game_build=}, {entry["event"]=})') logger.trace_if(CMDR_EVENTS, f'De-queued ({cmdr=}, {game_version=}, {game_build=}, {entry["event"]=})')
@ -756,7 +758,7 @@ def worker() -> None: # noqa: CCR001 C901
retrying = 0 retrying = 0
while retrying < 3: while retrying < 3:
if item is None: if item is None:
item = cast(Tuple[str, str, str, Mapping[str, Any]], ("", {})) item = cast(tuple[str, str, str, Mapping[str, Any]], ("", {}))
should_skip, new_item = killswitch.check_killswitch( should_skip, new_item = killswitch.check_killswitch(
'plugins.edsm.worker', 'plugins.edsm.worker',
item, item,
@ -909,7 +911,7 @@ def worker() -> None: # noqa: CCR001 C901
last_game_build = game_build last_game_build = game_build
def should_send(entries: List[Mapping[str, Any]], event: str) -> bool: # noqa: CCR001 def should_send(entries: list[Mapping[str, Any]], event: str) -> bool: # noqa: CCR001
""" """
Whether or not any of the given entries should be sent to EDSM. Whether or not any of the given entries should be sent to EDSM.

View File

@ -18,11 +18,13 @@ referenced in this file (or only in any other core plugin), and if so...
`build.py` TO ENSURE THE FILES ARE ACTUALLY PRESENT `build.py` TO ENSURE THE FILES ARE ACTUALLY PRESENT
IN AN END-USER INSTALLATION ON WINDOWS. IN AN END-USER INSTALLATION ON WINDOWS.
""" """
from __future__ import annotations
import base64 import base64
import gzip import gzip
import io import io
import json import json
from typing import Any, Mapping, Union from typing import Any, Mapping
def plugin_start3(plugin_dir: str) -> str: def plugin_start3(plugin_dir: str) -> str:
@ -36,7 +38,7 @@ def plugin_start3(plugin_dir: str) -> str:
# Return a URL for the current ship # Return a URL for the current ship
def shipyard_url(loadout: Mapping[str, Any], is_beta: bool) -> Union[bool, str]: def shipyard_url(loadout: Mapping[str, Any], is_beta: bool) -> bool | str:
""" """
Construct a URL for ship loadout. Construct a URL for ship loadout.

View File

@ -18,6 +18,7 @@ referenced in this file (or only in any other core plugin), and if so...
`build.py` TO ENSURE THE FILES ARE ACTUALLY PRESENT `build.py` TO ENSURE THE FILES ARE ACTUALLY PRESENT
IN AN END-USER INSTALLATION ON WINDOWS. IN AN END-USER INSTALLATION ON WINDOWS.
""" """
from __future__ import annotations
import json import json
import threading import threading
@ -29,9 +30,8 @@ from datetime import datetime, timedelta, timezone
from operator import itemgetter from operator import itemgetter
from threading import Lock, Thread from threading import Lock, Thread
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Any, Callable, Deque, Dict, List, Mapping, NamedTuple, Optional from typing import TYPE_CHECKING, Any, Callable, Deque, Mapping, NamedTuple, Sequence, cast, Union
from typing import OrderedDict as OrderedDictT from typing import OrderedDict as OrderedDictT
from typing import Sequence, Union, cast
import requests import requests
import edmc_data import edmc_data
import killswitch import killswitch
@ -63,8 +63,8 @@ CREDITS_DELTA_MIN_ABSOLUTE = 10_000_000 # Absolute difference threshold
class Credentials(NamedTuple): class Credentials(NamedTuple):
"""Credentials holds the set of credentials required to identify an inara API payload to inara.""" """Credentials holds the set of credentials required to identify an inara API payload to inara."""
cmdr: Optional[str] cmdr: str | None
fid: Optional[str] fid: str | None
api_key: str api_key: str
@ -89,25 +89,25 @@ class This:
self.parent: tk.Tk self.parent: tk.Tk
# Handle only sending Live galaxy data # Handle only sending Live galaxy data
self.legacy_galaxy_last_notified: Optional[datetime] = None self.legacy_galaxy_last_notified: datetime | None = None
self.lastlocation = None # eventData from the last Commander's Flight Log event self.lastlocation = None # eventData from the last Commander's Flight Log event
self.lastship = None # eventData from the last addCommanderShip or setCommanderShip event self.lastship = None # eventData from the last addCommanderShip or setCommanderShip event
# Cached Cmdr state # Cached Cmdr state
self.cmdr: Optional[str] = None self.cmdr: str | None = None
self.FID: Optional[str] = None # Frontier ID self.FID: str | None = None # Frontier ID
self.multicrew: bool = False # don't send captain's ship info to Inara while on a crew self.multicrew: bool = False # don't send captain's ship info to Inara while on a crew
self.newuser: bool = False # just entered API Key - send state immediately self.newuser: bool = False # just entered API Key - send state immediately
self.newsession: bool = True # starting a new session - wait for Cargo event self.newsession: bool = True # starting a new session - wait for Cargo event
self.undocked: bool = False # just undocked self.undocked: bool = False # just undocked
self.suppress_docked = False # Skip initial Docked event if started docked self.suppress_docked = False # Skip initial Docked event if started docked
self.cargo: Optional[List[OrderedDictT[str, Any]]] = None self.cargo: list[OrderedDictT[str, Any]] | None = None
self.materials: Optional[List[OrderedDictT[str, Any]]] = None self.materials: list[OrderedDictT[str, Any]] | None = None
self.last_credits: int = 0 # Send credit update soon after Startup / new game self.last_credits: int = 0 # Send credit update soon after Startup / new game
self.storedmodules: Optional[List[OrderedDictT[str, Any]]] = None self.storedmodules: list[OrderedDictT[str, Any]] | None = None
self.loadout: Optional[OrderedDictT[str, Any]] = None self.loadout: OrderedDictT[str, Any] | None = None
self.fleet: Optional[List[OrderedDictT[str, Any]]] = None self.fleet: list[OrderedDictT[str, Any]] | None = None
self.shipswap: bool = False # just swapped ship self.shipswap: bool = False # just swapped ship
self.on_foot = False self.on_foot = False
@ -115,9 +115,9 @@ class This:
# Main window clicks # Main window clicks
self.system_link: tk.Widget = None # type: ignore self.system_link: tk.Widget = None # type: ignore
self.system_name: Optional[str] = None # type: ignore self.system_name: str | None = None # type: ignore
self.system_address: Optional[str] = None # type: ignore self.system_address: str | None = None # type: ignore
self.system_population: Optional[int] = None self.system_population: int | None = None
self.station_link: tk.Widget = None # type: ignore self.station_link: tk.Widget = None # type: ignore
self.station = None self.station = None
self.station_marketid = None self.station_marketid = None
@ -129,7 +129,7 @@ class This:
self.apikey: nb.Entry self.apikey: nb.Entry
self.apikey_label: tk.Label self.apikey_label: tk.Label
self.events: Dict[Credentials, Deque[Event]] = defaultdict(deque) self.events: dict[Credentials, Deque[Event]] = defaultdict(deque)
self.event_lock: Lock = threading.Lock() # protects events, for use when rewriting events self.event_lock: Lock = threading.Lock() # protects events, for use when rewriting events
def filter_events(self, key: Credentials, predicate: Callable[[Event], bool]) -> None: def filter_events(self, key: Credentials, predicate: Callable[[Event], bool]) -> None:
@ -361,7 +361,7 @@ def prefs_changed(cmdr: str, is_beta: bool) -> None:
) )
def credentials(cmdr: Optional[str]) -> Optional[str]: def credentials(cmdr: str | None) -> str | None:
""" """
Get the credentials for the current commander. Get the credentials for the current commander.
@ -383,7 +383,7 @@ def credentials(cmdr: Optional[str]) -> Optional[str]:
def journal_entry( # noqa: C901, CCR001 def journal_entry( # noqa: C901, CCR001
cmdr: str, is_beta: bool, system: str, station: str, entry: Dict[str, Any], state: Dict[str, Any] cmdr: str, is_beta: bool, system: str, station: str, entry: dict[str, Any], state: dict[str, Any]
) -> str: ) -> str:
""" """
Journal entry hook. Journal entry hook.
@ -394,7 +394,7 @@ def journal_entry( # noqa: C901, CCR001
# causing users to spam Inara with 'URL provider' queries, and we want to # causing users to spam Inara with 'URL provider' queries, and we want to
# stop that. # stop that.
should_return: bool should_return: bool
new_entry: Dict[str, Any] = {} new_entry: dict[str, Any] = {}
should_return, new_entry = killswitch.check_killswitch('plugins.inara.journal', entry, logger) should_return, new_entry = killswitch.check_killswitch('plugins.inara.journal', entry, logger)
if should_return: if should_return:
@ -813,7 +813,7 @@ def journal_entry( # noqa: C901, CCR001
# Fleet # Fleet
if event_name == 'StoredShips': if event_name == 'StoredShips':
fleet: List[OrderedDictT[str, Any]] = sorted( fleet: list[OrderedDictT[str, Any]] = sorted(
[OrderedDict({ [OrderedDict({
'shipType': x['ShipType'], 'shipType': x['ShipType'],
'shipGameID': x['ShipID'], 'shipGameID': x['ShipID'],
@ -860,7 +860,7 @@ def journal_entry( # noqa: C901, CCR001
# Stored modules # Stored modules
if event_name == 'StoredModules': if event_name == 'StoredModules':
items = {mod['StorageSlot']: mod for mod in entry['Items']} # Impose an order items = {mod['StorageSlot']: mod for mod in entry['Items']} # Impose an order
modules: List[OrderedDictT[str, Any]] = [] modules: list[OrderedDictT[str, Any]] = []
for slot in sorted(items): for slot in sorted(items):
item = items[slot] item = items[slot]
module: OrderedDictT[str, Any] = OrderedDict([ module: OrderedDictT[str, Any] = OrderedDict([
@ -1088,7 +1088,7 @@ def journal_entry( # noqa: C901, CCR001
# #
# So we're going to do a lot of checking here and bail out if we dont like the look of ANYTHING here # So we're going to do a lot of checking here and bail out if we dont like the look of ANYTHING here
to_send_data: Optional[Dict[str, Any]] = {} # This is a glorified sentinel until lower down. to_send_data: dict[str, Any] | None = {} # This is a glorified sentinel until lower down.
# On Horizons, neither of these exist on TouchDown # On Horizons, neither of these exist on TouchDown
star_system_name = entry.get('StarSystem', this.system_name) star_system_name = entry.get('StarSystem', this.system_name)
body_name = entry.get('Body', state['Body'] if state['BodyType'] == 'Planet' else None) body_name = entry.get('Body', state['Body'] if state['BodyType'] == 'Planet' else None)
@ -1370,7 +1370,7 @@ def cmdr_data(data: CAPIData, is_beta): # noqa: CCR001, reanalyze me later
pass pass
def make_loadout(state: Dict[str, Any]) -> OrderedDictT[str, Any]: # noqa: CCR001 def make_loadout(state: dict[str, Any]) -> OrderedDictT[str, Any]: # noqa: CCR001
""" """
Construct an inara loadout from an event. Construct an inara loadout from an event.
@ -1440,8 +1440,8 @@ def new_add_event(
name: str, name: str,
timestamp: str, timestamp: str,
data: EVENT_DATA, data: EVENT_DATA,
cmdr: Optional[str] = None, cmdr: str | None = None,
fid: Optional[str] = None fid: str | None = None
): ):
""" """
Add a journal event to the queue, to be sent to inara at the next opportunity. Add a journal event to the queue, to be sent to inara at the next opportunity.
@ -1470,11 +1470,11 @@ def new_add_event(
this.events[key].append(Event(name, timestamp, data)) this.events[key].append(Event(name, timestamp, data))
def clean_event_list(event_list: List[Event]) -> List[Event]: def clean_event_list(event_list: list[Event]) -> list[Event]:
""" """
Check for killswitched events and remove or modify them as requested. Check for killswitched events and remove or modify them as requested.
:param event_list: List of events to clean :param event_list: list of events to clean
:return: Cleaned list of events :return: Cleaned list of events
""" """
cleaned_events = [] cleaned_events = []
@ -1533,14 +1533,14 @@ def new_worker():
logger.debug('Done.') logger.debug('Done.')
def get_events(clear: bool = True) -> Dict[Credentials, List[Event]]: def get_events(clear: bool = True) -> dict[Credentials, list[Event]]:
""" """
Fetch a copy of all events from the current queue. Fetch a copy of all events from the current queue.
:param clear: whether to clear the queues as we go, defaults to True :param clear: whether to clear the queues as we go, defaults to True
:return: a copy of the event dictionary :return: a copy of the event dictionary
""" """
events_copy: Dict[Credentials, List[Event]] = {} events_copy: dict[Credentials, list[Event]] = {}
with this.event_lock: with this.event_lock:
for key, events in this.events.items(): for key, events in this.events.items():
@ -1590,7 +1590,7 @@ def send_data(url: str, data: Mapping[str, Any]) -> bool:
return True # Regardless of errors above, we DID manage to send it, therefore inform our caller as such return True # Regardless of errors above, we DID manage to send it, therefore inform our caller as such
def handle_api_error(data: Mapping[str, Any], status: int, reply: Dict[str, Any]) -> None: def handle_api_error(data: Mapping[str, Any], status: int, reply: dict[str, Any]) -> None:
""" """
Handle API error response. Handle API error response.
@ -1604,7 +1604,7 @@ def handle_api_error(data: Mapping[str, Any], status: int, reply: Dict[str, Any]
plug.show_error(_('Error: Inara {MSG}').format(MSG=error_message)) plug.show_error(_('Error: Inara {MSG}').format(MSG=error_message))
def handle_success_reply(data: Mapping[str, Any], reply: Dict[str, Any]) -> None: def handle_success_reply(data: Mapping[str, Any], reply: dict[str, Any]) -> None:
""" """
Handle successful API response. Handle successful API response.
@ -1619,7 +1619,7 @@ def handle_success_reply(data: Mapping[str, Any], reply: Dict[str, Any]) -> None
handle_special_events(data_event, reply_event) handle_special_events(data_event, reply_event)
def handle_individual_error(data_event: Dict[str, Any], reply_status: int, reply_text: str) -> None: def handle_individual_error(data_event: dict[str, Any], reply_status: int, reply_text: str) -> None:
""" """
Handle individual API error. Handle individual API error.
@ -1638,7 +1638,7 @@ def handle_individual_error(data_event: Dict[str, Any], reply_status: int, reply
)) ))
def handle_special_events(data_event: Dict[str, Any], reply_event: Dict[str, Any]) -> None: def handle_special_events(data_event: dict[str, Any], reply_event: dict[str, Any]) -> None:
""" """
Handle special events in the API response. Handle special events in the API response.

View File

@ -37,7 +37,7 @@ def test_class_logger(caplog: 'LogCaptureFixture') -> None:
ClassVarLogger.set_logger(logger) ClassVarLogger.set_logger(logger)
ClassVarLogger.logger.debug('test') # type: ignore # its there ClassVarLogger.logger.debug('test') # type: ignore # its there
ClassVarLogger.logger.info('test2') # type: ignore # its there ClassVarLogger.logger.info('test2') # type: ignore # its there
log_stuff('test3') # type: ignore # its there log_stuff('test3')
# Dont move these, it relies on the line numbres. # Dont move these, it relies on the line numbres.
assert 'EDMarketConnector.EDMCLogging.py:test_logging_classvar.py:38 test' in caplog.text assert 'EDMarketConnector.EDMCLogging.py:test_logging_classvar.py:38 test' in caplog.text

View File

@ -1,12 +1,13 @@
# type: ignore """Old Configuration Test File."""
from __future__ import annotations
import numbers import numbers
import sys import sys
import warnings import warnings
from configparser import NoOptionError from configparser import NoOptionError
from os import getenv, makedirs, mkdir, pardir from os import getenv, makedirs, mkdir, pardir
from os.path import dirname, expanduser, isdir, join, normpath from os.path import dirname, expanduser, isdir, join, normpath
from typing import TYPE_CHECKING, Optional, Union from typing import TYPE_CHECKING
from config import applongname, appname, update_interval from config import applongname, appname, update_interval
from EDMCLogging import get_main_logger from EDMCLogging import get_main_logger
@ -81,7 +82,7 @@ elif sys.platform == 'win32':
RegDeleteValue.restype = LONG RegDeleteValue.restype = LONG
RegDeleteValue.argtypes = [HKEY, LPCWSTR] RegDeleteValue.argtypes = [HKEY, LPCWSTR]
def known_folder_path(guid: uuid.UUID) -> Optional[str]: def known_folder_path(guid: uuid.UUID) -> str | None:
"""Look up a Windows GUID to actual folder path name.""" """Look up a Windows GUID to actual folder path name."""
buf = ctypes.c_wchar_p() buf = ctypes.c_wchar_p()
if SHGetKnownFolderPath(ctypes.create_string_buffer(guid.bytes_le), 0, 0, ctypes.byref(buf)): if SHGetKnownFolderPath(ctypes.create_string_buffer(guid.bytes_le), 0, 0, ctypes.byref(buf)):
@ -95,7 +96,7 @@ elif sys.platform == 'linux':
from configparser import RawConfigParser from configparser import RawConfigParser
class OldConfig(): class OldConfig:
"""Object that holds all configuration data.""" """Object that holds all configuration data."""
OUT_EDDN_SEND_STATION_DATA = 1 OUT_EDDN_SEND_STATION_DATA = 1
@ -153,19 +154,18 @@ class OldConfig():
if not self.get('outdir') or not isdir(str(self.get('outdir'))): if not self.get('outdir') or not isdir(str(self.get('outdir'))):
self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0]) self.set('outdir', NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, True)[0])
def get(self, key: str, default: Union[None, list, str] = None) -> Union[None, list, str]: def get(self, key: str, default: None | list | str = None) -> None | list | str:
"""Look up a string configuration value.""" """Look up a string configuration value."""
val = self.settings.get(key) val = self.settings.get(key)
if val is None: if val is None:
return default return default
elif isinstance(val, str): if isinstance(val, str):
return str(val) return str(val)
elif isinstance(val, list): if isinstance(val, list):
return list(val) # make writeable return list(val) # make writeable
else:
return default return default
def getint(self, key: str, default: int = 0) -> int: def getint(self, key: str, default: int = 0) -> int:
@ -181,7 +181,7 @@ class OldConfig():
logger.debug('The exception type is ...', exc_info=e) logger.debug('The exception type is ...', exc_info=e)
return default return default
def set(self, key: str, val: Union[int, str, list]) -> None: def set(self, key: str, val: int | str | list) -> None:
"""Set value on the specified configuration key.""" """Set value on the specified configuration key."""
self.settings[key] = val self.settings[key] = val
@ -202,7 +202,7 @@ class OldConfig():
elif sys.platform == 'win32': elif sys.platform == 'win32':
def __init__(self): def __init__(self):
self.app_dir = join(known_folder_path(FOLDERID_LocalAppData), appname) # type: ignore # Not going to change self.app_dir = join(known_folder_path(FOLDERID_LocalAppData), appname) # type: ignore
if not isdir(self.app_dir): if not isdir(self.app_dir):
mkdir(self.app_dir) mkdir(self.app_dir)
@ -279,10 +279,10 @@ class OldConfig():
RegSetValueEx(sparklekey, 'UpdateInterval', 0, 1, buf, len(buf) * 2) RegSetValueEx(sparklekey, 'UpdateInterval', 0, 1, buf, len(buf) * 2)
RegCloseKey(sparklekey) RegCloseKey(sparklekey)
if not self.get('outdir') or not isdir(self.get('outdir')): # type: ignore # Not going to change if not self.get('outdir') or not isdir(self.get('outdir')): # type: ignore
self.set('outdir', known_folder_path(FOLDERID_Documents) or self.home) self.set('outdir', known_folder_path(FOLDERID_Documents) or self.home)
def get(self, key: str, default: Union[None, list, str] = None) -> Union[None, list, str]: def get(self, key: str, default: None | list | str = None) -> None | list | str:
"""Look up a string configuration value.""" """Look up a string configuration value."""
key_type = DWORD() key_type = DWORD()
key_size = DWORD() key_size = DWORD()
@ -304,10 +304,9 @@ class OldConfig():
if RegQueryValueEx(self.hkey, key, 0, ctypes.byref(key_type), buf, ctypes.byref(key_size)): if RegQueryValueEx(self.hkey, key, 0, ctypes.byref(key_type), buf, ctypes.byref(key_size)):
return default return default
elif key_type.value == REG_MULTI_SZ: if key_type.value == REG_MULTI_SZ:
return list(ctypes.wstring_at(buf, len(buf)-2).split('\x00')) return list(ctypes.wstring_at(buf, len(buf)-2).split('\x00'))
else:
return str(buf.value) return str(buf.value)
def getint(self, key: str, default: int = 0) -> int: def getint(self, key: str, default: int = 0) -> int:
@ -328,10 +327,9 @@ class OldConfig():
): ):
return default return default
else:
return key_val.value return key_val.value
def set(self, key: str, val: Union[int, str, list]) -> None: def set(self, key: str, val: int | str | list) -> None:
"""Set value on the specified configuration key.""" """Set value on the specified configuration key."""
if isinstance(val, str): if isinstance(val, str):
buf = ctypes.create_unicode_buffer(val) buf = ctypes.create_unicode_buffer(val)
@ -388,7 +386,7 @@ class OldConfig():
self.config = RawConfigParser(comment_prefixes=('#',)) self.config = RawConfigParser(comment_prefixes=('#',))
try: try:
with codecs.open(self.filename, 'r') as h: with codecs.open(self.filename) as h:
self.config.read_file(h) self.config.read_file(h)
except Exception as e: except Exception as e:
@ -398,7 +396,7 @@ class OldConfig():
if not self.get('outdir') or not isdir(self.get('outdir')): # type: ignore # Not going to change if not self.get('outdir') or not isdir(self.get('outdir')): # type: ignore # Not going to change
self.set('outdir', expanduser('~')) self.set('outdir', expanduser('~'))
def get(self, key: str, default: Union[None, list, str] = None) -> Union[None, list, str]: def get(self, key: str, default: None | list | str = None) -> None | list | str:
"""Look up a string configuration value.""" """Look up a string configuration value."""
try: try:
val = self.config.get(self.SECTION, key) val = self.config.get(self.SECTION, key)
@ -407,7 +405,6 @@ class OldConfig():
# so we add a spurious ';' entry in set() and remove it here # so we add a spurious ';' entry in set() and remove it here
assert val.split('\n')[-1] == ';', val.split('\n') assert val.split('\n')[-1] == ';', val.split('\n')
return [self._unescape(x) for x in val.split('\n')[:-1]] return [self._unescape(x) for x in val.split('\n')[:-1]]
else:
return self._unescape(val) return self._unescape(val)
except NoOptionError: except NoOptionError:
@ -434,13 +431,13 @@ class OldConfig():
return default return default
def set(self, key: str, val: Union[int, str, list]) -> None: def set(self, key: str, val: int | str | list) -> None:
"""Set value on the specified configuration key.""" """Set value on the specified configuration key."""
if isinstance(val, bool): if isinstance(val, bool):
self.config.set(self.SECTION, key, val and '1' or '0') # type: ignore # Not going to change self.config.set(self.SECTION, key, val and '1' or '0')
elif isinstance(val, str) or isinstance(val, numbers.Integral): elif isinstance(val, (numbers.Integral, str)):
self.config.set(self.SECTION, key, self._escape(val)) # type: ignore # Not going to change self.config.set(self.SECTION, key, self._escape(val))
elif isinstance(val, list): elif isinstance(val, list):
self.config.set(self.SECTION, key, '\n'.join([self._escape(x) for x in val] + [';'])) self.config.set(self.SECTION, key, '\n'.join([self._escape(x) for x in val] + [';']))
@ -460,7 +457,7 @@ class OldConfig():
def close(self) -> None: def close(self) -> None:
"""Close the configuration.""" """Close the configuration."""
self.save() self.save()
self.config = None self.config = None # type: ignore
def _escape(self, val: str) -> str: def _escape(self, val: str) -> str:
"""Escape a string for storage.""" """Escape a string for storage."""

View File

@ -7,15 +7,13 @@ key deletions. Said modifications are to keys that are generated internally.
Most of these tests are parity tests with the "old" config, and likely one day can be Most of these tests are parity tests with the "old" config, and likely one day can be
entirely removed. entirely removed.
""" """
from __future__ import annotations
import contextlib import contextlib
import itertools import itertools
import pathlib import pathlib
import random import random
import string import string
import sys import sys
from typing import Any, Iterable, List, cast from typing import Any, Iterable, cast
import pytest import pytest
from pytest import mark from pytest import mark
@ -30,12 +28,12 @@ from _old_config import old_config # noqa: E402
from config import config # noqa: E402 from config import config # noqa: E402
def _fuzz_list(length: int) -> List[str]: def _fuzz_list(length: int) -> list[str]:
out = [] out = []
for _ in range(length): for _ in range(length):
out.append(_fuzz_generators[str](random.randint(0, 1337))) out.append(_fuzz_generators[str](random.randint(0, 1337)))
return cast(List[str], out) return cast(list[str], out)
_fuzz_generators = { # Type annotating this would be a nightmare. _fuzz_generators = { # Type annotating this would be a nightmare.
@ -72,7 +70,7 @@ bool_tests = [True, False]
big_int = int(0xFFFFFFFF) # 32 bit int big_int = int(0xFFFFFFFF) # 32 bit int
def _make_params(args: List[Any], id_name: str = 'random_test_{i}') -> list: def _make_params(args: list[Any], id_name: str = 'random_test_{i}') -> list:
return [pytest.param(x, id=id_name.format(i=i)) for i, x in enumerate(args)] return [pytest.param(x, id=id_name.format(i=i)) for i, x in enumerate(args)]
@ -81,7 +79,7 @@ def _build_test_list(static_data, random_data, random_id_name='random_test_{i}')
class TestNewConfig: class TestNewConfig:
"""Test the new config with an array of hand picked and random data.""" """Test the new config with an array of hand-picked and random data."""
def __update_linuxconfig(self) -> None: def __update_linuxconfig(self) -> None:
"""On linux config uses ConfigParser, which doesn't update from disk changes. Force the update here.""" """On linux config uses ConfigParser, which doesn't update from disk changes. Force the update here."""
@ -117,7 +115,7 @@ class TestNewConfig:
config.delete(name) config.delete(name)
@mark.parametrize("lst", _build_test_list(list_tests, _get_fuzz(list))) @mark.parametrize("lst", _build_test_list(list_tests, _get_fuzz(list)))
def test_list(self, lst: List[str]) -> None: def test_list(self, lst: list[str]) -> None:
"""Save a list and then ask for it back.""" """Save a list and then ask for it back."""
name = f'list_test_{ hash("".join(lst)) }' name = f'list_test_{ hash("".join(lst)) }'
config.set(name, lst) config.set(name, lst)
@ -216,7 +214,7 @@ class TestOldNewConfig:
assert res == string assert res == string
@mark.parametrize("lst", _build_test_list(list_tests, _get_fuzz(list))) @mark.parametrize("lst", _build_test_list(list_tests, _get_fuzz(list)))
def test_list(self, lst: List[str]) -> None: def test_list(self, lst: list[str]) -> None:
"""Save a list though the old config, recall it using the new config.""" """Save a list though the old config, recall it using the new config."""
lst = [x.replace("\r", "") for x in lst] # OldConfig on linux fails to store these correctly lst = [x.replace("\r", "") for x in lst] # OldConfig on linux fails to store these correctly
if sys.platform == 'win32': if sys.platform == 'win32':

View File

@ -1,13 +1,13 @@
"""Tests for journal_lock.py code.""" """Tests for journal_lock.py code."""
from __future__ import annotations
import multiprocessing as mp import multiprocessing as mp
import os import os
import pathlib import pathlib
import sys import sys
from typing import Generator from typing import Generator
import pytest import pytest
from pytest import MonkeyPatch, TempdirFactory, TempPathFactory from pytest import MonkeyPatch, TempdirFactory, TempPathFactory
from config import config from config import config
from journal_lock import JournalLock, JournalLockResult from journal_lock import JournalLock, JournalLockResult
@ -142,7 +142,7 @@ class TestJournalLock:
def get_str(key: str, *, default: str | None = None) -> str: def get_str(key: str, *, default: str | None = None) -> str:
"""Mock config.*Config get_str to provide fake journaldir.""" """Mock config.*Config get_str to provide fake journaldir."""
if key == 'journaldir': if key == 'journaldir':
return tmp_path_factory.mktemp("changing") return tmp_path_factory.mktemp("changing") # type: ignore
print('Other key, calling up ...') print('Other key, calling up ...')
return config.get_str(key) # Call the non-mocked return config.get_str(key) # Call the non-mocked
@ -301,7 +301,7 @@ class TestJournalLock:
# Need to release any handles on the lockfile else the sub-process # Need to release any handles on the lockfile else the sub-process
# might not be able to clean up properly, and that will impact # might not be able to clean up properly, and that will impact
# on later tests. # on later tests.
jlock.journal_dir_lockfile.close() jlock.journal_dir_lockfile.close() # type: ignore
print('Telling sub-process to quit...') print('Telling sub-process to quit...')
exit_q.put('quit') exit_q.put('quit')

View File

@ -1,6 +1,8 @@
"""Test the apply functions used by killswitch to modify data.""" """Test the apply functions used by killswitch to modify data."""
from __future__ import annotations
import copy import copy
from typing import Any, Optional from typing import Any
import pytest import pytest
@ -33,11 +35,11 @@ def test_apply(source: UPDATABLE_DATA, key: str, action: str, to_set: Any, resul
def test_apply_errors() -> None: def test_apply_errors() -> None:
"""_apply should fail when passed something that isn't a Sequence or MutableMapping.""" """_apply should fail when passed something that isn't a Sequence or MutableMapping."""
with pytest.raises(ValueError, match=r'Dont know how to'): with pytest.raises(ValueError, match=r'Dont know how to'):
killswitch._apply(set(), '0', None, False) # type: ignore # Its intentional that its broken killswitch._apply(set(), '0') # type: ignore # Its intentional that its broken
killswitch._apply(None, '', None) # type: ignore # Its intentional that its broken killswitch._apply(None, '') # type: ignore # Its intentional that its broken
with pytest.raises(ValueError, match=r'Cannot use string'): with pytest.raises(ValueError, match=r'Cannot use string'):
killswitch._apply([], 'test', None, False) killswitch._apply([], 'test')
def test_apply_no_error() -> None: def test_apply_no_error() -> None:
@ -61,7 +63,7 @@ def test_apply_no_error() -> None:
(False, 0), (str((1 << 63)-1), (1 << 63)-1), (True, 1), (str(1 << 1337), 1 << 1337) (False, 0), (str((1 << 63)-1), (1 << 63)-1), (True, 1), (str(1 << 1337), 1 << 1337)
] ]
) )
def test_get_int(input: str, expected: Optional[int]) -> None: def test_get_int(input: str, expected: int | None) -> None:
"""Check that _get_int doesn't throw when handed bad data.""" """Check that _get_int doesn't throw when handed bad data."""
assert expected == killswitch._get_int(input) assert expected == killswitch._get_int(input)

View File

@ -1,7 +1,7 @@
"""Tests of killswitch behaviour.""" """Tests of killswitch behaviour."""
import copy from __future__ import annotations
from typing import Optional
import copy
import pytest import pytest
import semantic_version import semantic_version
@ -34,7 +34,7 @@ TEST_SET = killswitch.KillSwitchSet([
], ],
) )
def test_killswitch( def test_killswitch(
input: killswitch.UPDATABLE_DATA, kill: str, should_pass: bool, result: Optional[killswitch.UPDATABLE_DATA], input: killswitch.UPDATABLE_DATA, kill: str, should_pass: bool, result: killswitch.UPDATABLE_DATA | None,
version: str version: str
) -> None: ) -> None:
"""Simple killswitch tests.""" """Simple killswitch tests."""