From f1c180caf19dcc3d566985444bd2957e68b540fb Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 9 Nov 2025 12:43:32 -0500 Subject: [PATCH 1/5] [#2113] Update Python Version to 3.13 --- .pre-commit-config.yaml | 2 +- .python-version | 2 +- ChangeLog.md | 2 +- requirements-dev.txt | 19 +++++++++---------- requirements.txt | 4 ++-- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 27544fce..8339f184 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,7 +77,7 @@ repos: always_run: true default_language_version: - python: python3.11 + python: python3.13 default_stages: [ pre-commit, pre-push ] diff --git a/.python-version b/.python-version index 2c073331..24ee5b1b 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.11 +3.13 diff --git a/ChangeLog.md b/ChangeLog.md index b30b82b7..8bab3370 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,6 @@ This is the master changelog for Elite Dangerous Market Connector. Entries are in reverse chronological order (latest first). --- -* We currently test against, and package with, Python 3.11, 32-bit. +* We currently test against, and package with, Python 3.13, 32-bit. * As a result, we do not support Windows 7, 8, or 8.1. * Developers can check the contents of the `.python-version` file in the source (not distributed with the Windows installer) for the diff --git a/requirements-dev.txt b/requirements-dev.txt index c64cac64..7ad17560 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,39 +4,38 @@ setuptools==80.9.0 # Static analysis tools -flake8==7.2.0 +flake8==7.3.0 flake8-annotations-coverage==0.0.6 flake8-cognitive-complexity==0.1.0 -flake8-comprehensions==3.16.0 +flake8-comprehensions==3.17.0 flake8-docstrings==1.7.0 flake8-json==24.4.0 flake8-noqa==1.4.0 -flake8-polyfill==1.0.2 flake8-use-fstring==1.4 mypy==1.18.2 pep8-naming==0.15.1 -safety==3.6.2 -types-requests==2.32.0.20250328 +safety==3.7.0 +types-requests==2.32.4.20250913 types-setuptools==80.9.0.20250822 # Code formatting tools autopep8==2.3.2 # Git pre-commit checking -pre-commit==4.3.0 +pre-commit==4.4.0 # HTML changelogs -mistune==3.1.3 +mistune==3.1.4 # Packaging # We only need py2exe on windows. -py2exe==0.13.0.2; sys_platform == 'win32' +py2exe==0.14.0.0; sys_platform == 'win32' # Testing -pytest==8.4.2 +pytest==9.0.0 pytest-cov==7.0.0 # Pytest code coverage support -coverage[toml]==7.11.0 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs +coverage[toml]==7.11.2 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs coverage-conditional-plugin==0.9.0 diff --git a/requirements.txt b/requirements.txt index 0f5e497a..10b4ac67 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,5 @@ watchdog==6.0.0 simplesystray==0.1.0; sys_platform == 'win32' semantic-version==2.10.0 # For manipulating folder permissions and the like. -pywin32==310; sys_platform == 'win32' -psutil==7.1.2 +pywin32==311; sys_platform == 'win32' +psutil==7.1.3 From 26db3ad0995882e57aaf0d46c82c5e0ed947d1c2 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 9 Nov 2025 13:37:14 -0500 Subject: [PATCH 2/5] [2113] Initiate ProtocolHandler, refactor WndProc for x64 --- protocol.py | 296 +++++++++++++++++++++++++++++----------------------- 1 file changed, 166 insertions(+), 130 deletions(-) diff --git a/protocol.py b/protocol.py index 58ee9fa3..cec4333a 100644 --- a/protocol.py +++ b/protocol.py @@ -5,6 +5,7 @@ Copyright (c) EDCD, All Rights Reserved Licensed under the GNU General Public License. See LICENSE file. """ + from __future__ import annotations import os @@ -23,8 +24,8 @@ logger = get_main_logger() is_wine = False -if sys.platform == 'win32': - is_wine = bool(os.getenv('WINEPREFIX')) +if sys.platform == "win32": + is_wine = bool(os.getenv("WINEPREFIX")) class GenericProtocolHandler: @@ -32,10 +33,10 @@ class GenericProtocolHandler: def __init__(self) -> None: self.redirect = protocolhandler_redirect # Base redirection URL - self.master: 'tkinter.Tk' = None # type: ignore + self.master: tkinter.Tk = None # type: ignore self.lastpayload: str | None = None - def start(self, master: 'tkinter.Tk') -> None: + def start(self, master: tkinter.Tk) -> None: """Start Protocol Handler.""" self.master = master @@ -46,29 +47,50 @@ class GenericProtocolHandler: """Generate an auth event.""" self.lastpayload = url - logger.trace_if('frontier-auth', f'Payload: {self.lastpayload}') + logger.trace_if("frontier-auth", f"Payload: {self.lastpayload}") if not config.shutting_down: logger.debug('event_generate("<>")') - self.master.event_generate('<>', when="tail") + self.master.event_generate("<>", when="tail") -if (config.auth_force_edmc_protocol # noqa: C901 - or ( - sys.platform == 'win32' - and getattr(sys, 'frozen', False) - and not is_wine - and not config.auth_force_localserver - )): +if config.auth_force_edmc_protocol or ( # noqa: C901 + sys.platform == "win32" + and getattr(sys, "frozen", False) + and not is_wine + and not config.auth_force_localserver +): # This could be false if you use auth_force_edmc_protocol, but then you get to keep the pieces - if sys.platform != 'win32': + if sys.platform != "win32": raise EnvironmentError("This code is for Windows only.") # spell-checker: words HBRUSH HICON WPARAM wstring WNDCLASS HMENU HGLOBAL from ctypes import ( # type: ignore - windll, POINTER, WINFUNCTYPE, Structure, byref, c_long, c_void_p, create_unicode_buffer, wstring_at + windll, + POINTER, + WINFUNCTYPE, + Structure, + byref, + c_long, + c_void_p, + create_unicode_buffer, + wstring_at, ) from ctypes.wintypes import ( - ATOM, BOOL, HBRUSH, HGLOBAL, HICON, HINSTANCE, HWND, INT, LPARAM, LPCWSTR, LPMSG, LPVOID, LPWSTR, - MSG, UINT, WPARAM + ATOM, + BOOL, + HBRUSH, + HGLOBAL, + HICON, + HINSTANCE, + HWND, + INT, + LPARAM, + LPCWSTR, + LPMSG, + LPVOID, + LPWSTR, + MSG, + UINT, + WPARAM, ) import win32gui import win32api @@ -83,16 +105,16 @@ if (config.auth_force_edmc_protocol # noqa: C901 """ _fields_ = [ - ('style', UINT), - ('lpfnWndProc', WINFUNCTYPE(c_long, HWND, UINT, WPARAM, LPARAM)), - ('cbClsExtra', INT), - ('cbWndExtra', INT), - ('hInstance', HINSTANCE), - ('hIcon', HICON), - ('hCursor', c_void_p), - ('hbrBackground', HBRUSH), - ('lpszMenuName', LPCWSTR), - ('lpszClassName', LPCWSTR) + ("style", UINT), + ("lpfnWndProc", WINFUNCTYPE(c_long, HWND, UINT, WPARAM, LPARAM)), + ("cbClsExtra", INT), + ("cbWndExtra", INT), + ("hInstance", HINSTANCE), + ("hIcon", HICON), + ("hCursor", c_void_p), + ("hbrBackground", HBRUSH), + ("lpszMenuName", LPCWSTR), + ("lpszClassName", LPCWSTR), ] CW_USEDEFAULT = 0x80000000 @@ -140,52 +162,58 @@ if (config.auth_force_edmc_protocol # noqa: C901 # Windows Message handler stuff (IPC) # https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms633573(v=vs.85) @WINFUNCTYPE(c_long, HWND, UINT, WPARAM, LPARAM) - def WndProc(hwnd: HWND, message: UINT, wParam: WPARAM, lParam: LPARAM) -> c_long: # noqa: N803 N802 + def WndProc(hwnd: HWND, message: UINT, wParam: WPARAM, lParam: LPARAM) -> int: # noqa: N803 N802 """ - Deal with DDE requests. + Windows message procedure for handling DDE requests. - :param hwnd: Window handle - :param message: The message being sent - :param wParam: Additional Message Information (depends on message type) - :param lParam: Also additional message information - :return: ??? + This routine responds to WM_DDE_INITIATE messages for the application's + DDE service (the app name) and topic ("System"). It sends an ACK if the + incoming atoms match the expected values. + + Returns int due to Python 3.11+ CTYPES Callback - decorator changes to c_long """ - if message != WM_DDE_INITIATE: - # Not a DDE init message, bail and tell windows to do the default - # https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-defwindowproca?redirectedfrom=MSDN - return win32gui.DefWindowProc(hwnd, message, wParam, lParam) + try: + if message != WM_DDE_INITIATE: + # Let Windows handle all other messages normally + return win32gui.DefWindowProc(hwnd, message, wParam, lParam) - service = create_unicode_buffer(256) - topic = create_unicode_buffer(256) - # Note that lParam is 32 bits, and broken into two 16 bit words. This will break on 64bit as the math is - # wrong - # if nonzero, the target application for which a conversation is requested - lparam_low = lParam & 0xFFFF # type: ignore - # if nonzero, the topic of said conversation - lparam_high = lParam >> 16 # type: ignore + # --- Decode the 64-bit lParam into LOWORD/HIWORD safely --- + # These helper functions handle pointer-sized values correctly on 64-bit builds + lparam_low = win32api.LOWORD(lParam) + lparam_high = win32api.HIWORD(lParam) - # if either of the words are nonzero, they contain - # atoms https://docs.microsoft.com/en-us/windows/win32/dataxchg/about-atom-tables - # which we can read out as shown below, and then compare. + service = create_unicode_buffer(256) + topic = create_unicode_buffer(256) - target_is_valid = lparam_low == 0 or ( - GlobalGetAtomNameW(lparam_low, service, 256) and service.value == appname - ) - - topic_is_valid = lparam_high == 0 or ( - GlobalGetAtomNameW(lparam_high, topic, 256) and topic.value.lower() == 'system' - ) - - if target_is_valid and topic_is_valid: - # if everything is happy, send an acknowledgement of the DDE request - SendMessageW( - wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, GlobalAddAtomW(appname), GlobalAddAtomW('System')) + # --- Retrieve atom names --- + target_is_valid = lparam_low == 0 or ( + GlobalGetAtomNameW(lparam_low, service, 256) + and service.value == appname ) - # It works as a constructor as per - return c_long(0) + topic_is_valid = lparam_high == 0 or ( + GlobalGetAtomNameW(lparam_high, topic, 256) + and topic.value.lower() == "system" + ) - return c_long(1) # This is an utter guess -Ath + if target_is_valid and topic_is_valid: + # Send acknowledgment of successful DDE handshake + SendMessageW( + wParam, + WM_DDE_ACK, + hwnd, + PackDDElParam( + WM_DDE_ACK, GlobalAddAtomW(appname), GlobalAddAtomW("System") + ), + ) + return 0 # Success + + return 1 # Not handled / invalid DDE initiation + + except Exception as e: + # Log unexpected exceptions but don’t crash the message loop + logger.error(f"WndProc error: {e}", exc_info=True) + return win32gui.DefWindowProc(hwnd, message, wParam, lParam) class WindowsProtocolHandler(GenericProtocolHandler): """ @@ -199,10 +227,10 @@ if (config.auth_force_edmc_protocol # noqa: C901 super().__init__() self.thread: threading.Thread | None = None - def start(self, master: 'tkinter.Tk') -> None: + def start(self, master: tkinter.Tk) -> None: """Start the DDE thread.""" super().start(master) - self.thread = threading.Thread(target=self.worker, name='DDE worker') + self.thread = threading.Thread(target=self.worker, name="DDE worker") self.thread.daemon = True self.thread.start() @@ -226,41 +254,33 @@ if (config.auth_force_edmc_protocol # noqa: C901 wndclass.hCursor = None wndclass.hbrBackground = None wndclass.lpszMenuName = None - wndclass.lpszClassName = 'DDEServer' + wndclass.lpszClassName = "DDEServer" if not RegisterClassW(byref(wndclass)): - print('Failed to register Dynamic Data Exchange for cAPI') + print("Failed to register Dynamic Data Exchange for cAPI") return # https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw hwnd = win32gui.CreateWindowEx( - 0, # dwExStyle - 'DDEServer', # lpClassName (use string directly as win32gui expects it) - "DDE Server", # lpWindowName - 0, # dwStyle - win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, - win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, # X, Y, nWidth, nHeight + 0, # dwExStyle + "DDEServer", # lpClassName (use string directly as win32gui expects it) + "DDE Server", # lpWindowName + 0, # dwStyle + win32con.CW_USEDEFAULT, + win32con.CW_USEDEFAULT, + win32con.CW_USEDEFAULT, + win32con.CW_USEDEFAULT, # X, Y, nWidth, nHeight self.master.winfo_id(), # hWndParent - 0, # hMenu (use 0 instead of None for win32gui) - wndclass.hInstance, # hInstance - None # lpParam + 0, # hMenu (use 0 instead of None for win32gui) + wndclass.hInstance, # hInstance + None, # lpParam ) msg = MSG() - # Calls GetMessageW: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessagew - # Something is off with the return from this, meaning you'll see: - # Exception ignored on converting result of ctypes callback function: - # Traceback (most recent call last): - # File "C:\Users\Athan\Documents\Devel\EDMarketConnector\protocol.py", line 323, in worker - # while int(GetMessageW(byref(msg), None, 0, 0)) != 0: - # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - # TypeError: 'c_long' object cannot be interpreted as an integer - # - # But it does actually work. Either getting a non-0 value and - # entering the loop, or getting 0 and exiting it. while GetMessageW(byref(msg), None, 0, 0) != 0: - logger.trace_if('frontier-auth.windows', f'DDE message of type: {msg.message}') + logger.trace_if( + "frontier-auth.windows", f"DDE message of type: {msg.message}" + ) if msg.message == WM_DDE_EXECUTE: # GlobalLock does some sort of "please dont move this?" # https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globallock @@ -268,28 +288,41 @@ if (config.auth_force_edmc_protocol # noqa: C901 GlobalUnlock(msg.lParam) # Unlocks the GlobalLock-ed object if args.lower().startswith('open("') and args.endswith('")'): - logger.trace_if('frontier-auth.windows', f'args are: {args}') + logger.trace_if("frontier-auth.windows", f"args are: {args}") url = parse.unquote(args[6:-2]).strip() if url.startswith(self.redirect): - logger.debug(f'Message starts with {self.redirect}') + logger.debug(f"Message starts with {self.redirect}") self.event(url) # Send back a WM_DDE_ACK. this is _required_ with WM_DDE_EXECUTE - win32gui.SetForegroundWindow(win32gui.GetParent(self.master.winfo_id())) - PostMessageW(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0x80, msg.lParam)) + win32gui.SetForegroundWindow( + win32gui.GetParent(self.master.winfo_id()) + ) + PostMessageW( + msg.wParam, + WM_DDE_ACK, + hwnd, + PackDDElParam(WM_DDE_ACK, 0x80, msg.lParam), + ) else: # Send back a WM_DDE_ACK. this is _required_ with WM_DDE_EXECUTE - PostMessageW(msg.wParam, WM_DDE_ACK, hwnd, PackDDElParam(WM_DDE_ACK, 0, msg.lParam)) + PostMessageW( + msg.wParam, + WM_DDE_ACK, + hwnd, + PackDDElParam(WM_DDE_ACK, 0, msg.lParam), + ) elif msg.message == WM_DDE_TERMINATE: win32gui.PostMessage(msg.wParam, WM_DDE_TERMINATE, hwnd, 0) else: - TranslateMessage(byref(msg)) # "Translates virtual key messages into character messages" ??? + TranslateMessage( + byref(msg) + ) # "Translates virtual key messages into character messages" ??? DispatchMessageW(byref(msg)) - else: # Linux / Run from source from http.server import BaseHTTPRequestHandler, HTTPServer @@ -303,17 +336,17 @@ else: # Linux / Run from source def __init__(self) -> None: super().__init__() - self.httpd = HTTPServer(('localhost', 0), HTTPRequestHandler) - self.redirect = f'http://localhost:{self.httpd.server_port}/auth' + self.httpd = HTTPServer(("localhost", 0), HTTPRequestHandler) + self.redirect = f"http://localhost:{self.httpd.server_port}/auth" if not os.getenv("EDMC_NO_UI"): - logger.info(f'Web server listening on {self.redirect}') + logger.info(f"Web server listening on {self.redirect}") self.thread: threading.Thread | None = None - def start(self, master: 'tkinter.Tk') -> None: + def start(self, master: tkinter.Tk) -> None: """Start the HTTP server thread.""" GenericProtocolHandler.start(self, master) - self.thread = threading.Thread(target=self.worker, name='OAuth worker') + self.thread = threading.Thread(target=self.worker, name="OAuth worker") self.thread.daemon = True self.thread.start() @@ -321,20 +354,20 @@ else: # Linux / Run from source """Shutdown the HTTP server thread.""" thread = self.thread if thread: - logger.debug('Thread') + logger.debug("Thread") self.thread = None if self.httpd: - logger.info('Shutting down httpd') + logger.info("Shutting down httpd") self.httpd.shutdown() - logger.info('Joining thread') + logger.info("Joining thread") thread.join() # Wait for it to quit else: - logger.debug('No thread') + logger.debug("No thread") - logger.debug('Done.') + logger.debug("Done.") def worker(self) -> None: """HTTP Worker.""" @@ -346,10 +379,12 @@ else: # Linux / Run from source def parse(self) -> bool: """Parse a request.""" - logger.trace_if('frontier-auth.http', f'Got message on path: {self.path}') + logger.trace_if("frontier-auth.http", f"Got message on path: {self.path}") url = parse.unquote(self.path) - if url.startswith('/auth'): - logger.debug('Request starts with /auth, sending to protocolhandler.event()') + if url.startswith("/auth"): + logger.debug( + "Request starts with /auth, sending to protocolhandler.event()" + ) protocolhandler.event(url) self.send_response(200) return True @@ -364,14 +399,14 @@ else: # Linux / Run from source def do_GET(self) -> None: # noqa: N802 # Required to override """Handle GET Request.""" if self.parse(): - self.send_header('Content-Type', 'text/html') + self.send_header("Content-Type", "text/html") self.end_headers() self.wfile.write(self._generate_auth_response().encode()) else: self.send_response(404) self.end_headers() - def log_request(self, code: int | str = '-', size: int | str = '-') -> None: + def log_request(self, code: int | str = "-", size: int | str = "-") -> None: """Override to prevent logging.""" def _generate_auth_response(self) -> str: @@ -381,21 +416,21 @@ else: # Linux / Run from source :return: The HTML content of the authentication response. """ return ( - '' - '' - 'Authentication successful - Elite: Dangerous' - '' - '' - '' - '

Authentication successful

' - '

Thank you for authenticating.

' - '

Please close this browser tab now.

' - '' - '' + "h1 { text-align: center; margin-top: 100px; }" + "p { text-align: center; }" + "" + "" + "" + "

Authentication successful

" + "

Thank you for authenticating.

" + "

Please close this browser tab now.

" + "" + "" ) @@ -405,9 +440,10 @@ def get_handler_impl() -> Type[GenericProtocolHandler]: :return: An instantiatable GenericProtocolHandler """ - if ( - (sys.platform == 'win32' and config.auth_force_edmc_protocol) - or (getattr(sys, 'frozen', False) and not is_wine and not config.auth_force_localserver) + if (sys.platform == "win32" and config.auth_force_edmc_protocol) or ( + getattr(sys, "frozen", False) + and not is_wine + and not config.auth_force_localserver ): return WindowsProtocolHandler @@ -415,4 +451,4 @@ def get_handler_impl() -> Type[GenericProtocolHandler]: # *late init* singleton -protocolhandler: GenericProtocolHandler +protocolhandler: GenericProtocolHandler = None # type: ignore From 6d474abd016e3e6e0181db8c420c6448a5189df2 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 9 Nov 2025 13:40:04 -0500 Subject: [PATCH 3/5] Update Dep Warnings using Python 3.13 Syntax --- config/__init__.py | 22 +++++++++------------- l10n.py | 11 ++++------- myNotebook.py | 6 ++---- plugins/eddn.py | 3 ++- prefs.py | 4 +--- 5 files changed, 18 insertions(+), 28 deletions(-) diff --git a/config/__init__.py b/config/__init__.py index 2c1d35fc..be4841a8 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -335,6 +335,7 @@ class AbstractConfig(abc.ABC): return None + @warnings.deprecated("get() is Deprecated. use the specific getter for your type. Will remove in 6.0") def get( self, key: str, default: list | str | bool | int | None = None @@ -347,9 +348,6 @@ class AbstractConfig(abc.ABC): :raises OSError: On Windows, if a Registry error occurs. :return: The data or the default. """ - # DEPRECATED: Migrate to specific type getters. Will remove in 6.0 or later. - warnings.warn('get is Deprecated. use the specific getter for your type', DeprecationWarning, stacklevel=2) - if (a_list := self._suppress_call(self.get_list, ValueError, key, default=None)) is not None: return a_list @@ -399,6 +397,7 @@ class AbstractConfig(abc.ABC): """ raise NotImplementedError + @warnings.deprecated("Migrate to get_int. Will remove in 6.0 or later.") def getint(self, key: str, *, default: int = 0) -> int: """ Getint is a Deprecated getter method. @@ -406,9 +405,6 @@ class AbstractConfig(abc.ABC): See get_int for its replacement. :raises OSError: On Windows, if a Registry error occurs. """ - # DEPRECATED: Migrate to get_int. Will remove in 6.0 or later. - warnings.warn('getint is Deprecated. Use get_int instead', DeprecationWarning, stacklevel=2) - return self.get_int(key, default=default) @abstractmethod @@ -464,18 +460,20 @@ class AbstractConfig(abc.ABC): """Close this config and release any associated resources.""" raise NotImplementedError -# DEPRECATED: Password system doesn't do anything. Will remove in 6.0 or later. + @warnings.deprecated("Password system doesn't do anything. Will remove in 6.0 or later.") def get_password(self, account: str) -> None: """Legacy password retrieval.""" - warnings.warn("password subsystem is no longer supported", DeprecationWarning, stacklevel=2) + pass + @warnings.deprecated("Password system doesn't do anything. Will remove in 6.0 or later.") def set_password(self, account: str, password: str) -> None: """Legacy password setting.""" - warnings.warn("password subsystem is no longer supported", DeprecationWarning, stacklevel=2) + pass + @warnings.deprecated("Password system doesn't do anything. Will remove in 6.0 or later.") def delete_password(self, account: str) -> None: """Legacy password deletion.""" - warnings.warn("password subsystem is no longer supported", DeprecationWarning, stacklevel=2) + pass # End Dep Zone @@ -509,10 +507,8 @@ def get_update_feed() -> str: return 'https://raw.githubusercontent.com/EDCD/EDMarketConnector/releases/edmarketconnector.xml' -# DEPRECATED: Migrate to get_update_feed(). Will remove in 6.0 or later. +@warnings.deprecated("Migrate to get_update_feed(). Will remove in 6.0 or later.") def __getattr__(name: str): if name == 'update_feed': - warnings.warn('update_feed is deprecated, and will be removed in 6.0 or later. ' - 'Please migrate to get_update_feed()', DeprecationWarning, stacklevel=2) return get_update_feed() raise AttributeError(name=name) diff --git a/l10n.py b/l10n.py index 81faed21..b9c8ab97 100755 --- a/l10n.py +++ b/l10n.py @@ -262,17 +262,17 @@ def _wszarray_to_list(array): class _Locale: """Locale holds a few utility methods to convert data to and from localized versions.""" - # DEPRECATED: Migrate to _Locale.string_from_number. Will remove in 6.0 or later. + @warnings.deprecated("Migrate to _Locale.string_from_number. Will remove in 6.0 or later.") def stringFromNumber(self, number: float | int, decimals: int | None = None) -> str: # noqa: N802 warnings.warn('use _Locale.string_from_number instead.', DeprecationWarning, stacklevel=2) return self.string_from_number(number, decimals) # type: ignore - # DEPRECATED: Migrate to _Locale.number_from_string. Will remove in 6.0 or later. + @warnings.deprecated("Migrate to _Locale.number_from_string. Will remove in 6.0 or later.") def numberFromString(self, string: str) -> int | float | None: # noqa: N802 warnings.warn('use _Locale.number_from_string instead.', DeprecationWarning, stacklevel=2) return self.number_from_string(string) - # DEPRECATED: Migrate to _Locale.preferred_languages. Will remove in 6.0 or later. + @warnings.deprecated("Migrate to _Locale.preferred_languages. Will remove in 6.0 or later.") def preferredLanguages(self) -> Iterable[str]: # noqa: N802 warnings.warn('use _Locale.preferred_languages instead.', DeprecationWarning, stacklevel=2) return self.preferred_languages() @@ -354,13 +354,10 @@ Locale = _Locale() translations = Translations() -# DEPRECATED: Migrate to `translations`. Will be removed in 6.0 or later. -# 'Translations' singleton is deprecated. +@warnings.deprecated("Migrate to `translations`. Will be removed in 6.0 or later.") # Begin Deprecation Zone class _Translations(Translations): def __init__(self): - warnings.warn('Translations and _Translations() are deprecated. ' - 'Please use translations and Translations() instead.', DeprecationWarning, stacklevel=2) super().__init__() diff --git a/myNotebook.py b/myNotebook.py index c57a6deb..02a7bef6 100644 --- a/myNotebook.py +++ b/myNotebook.py @@ -125,9 +125,8 @@ class EntryMenu(ttk.Entry): class Entry(EntryMenu): """Custom ttk.Entry class to fix some display issues.""" - # DEPRECATED: Migrate to EntryMenu. Will remove in 6.0 or later. + @warnings.deprecated("Migrate to EntryMenu. Will remove in 6.0 or later.") def __init__(self, master: ttk.Frame | None = None, **kw): - warnings.warn('Migrate to EntryMenu. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2) EntryMenu.__init__(self, master, **kw) @@ -144,9 +143,8 @@ class Button(ttk.Button): class ColoredButton(tk.Button): """Custom tk.Button class to fix some display issues.""" - # DEPRECATED: Migrate to tk.Button. Will remove in 6.0 or later. + @warnings.deprecated("Migrate to tk.Button. Will remove in 6.0 or later.") def __init__(self, master: ttk.Frame | None = None, **kw): - warnings.warn('Migrate to tk.Button. Will remove in 6.0 or later.', DeprecationWarning, stacklevel=2) tk.Button.__init__(self, master, **kw) diff --git a/plugins/eddn.py b/plugins/eddn.py index 39ecf147..29caa6b2 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -29,6 +29,7 @@ import pathlib import re import sqlite3 import tkinter as tk +import warnings from platform import system from textwrap import dedent from threading import Lock @@ -221,9 +222,9 @@ class EDDNSender: return db_conn + @warnings.deprecated("Legacy files no longer used as of 5.6.0. Will remove in 6.0 or later.") def convert_legacy_file(self): """Convert a legacy file's contents into the sqlite3 db.""" - # DEPRECATED: Legacy files no longer used as of 5.6.0. Will remove in 6.0 or later. filename = config.app_dir_path / 'replay.jsonl' if not filename.exists(): return diff --git a/prefs.py b/prefs.py index 762d9a27..e0660c9f 100644 --- a/prefs.py +++ b/prefs.py @@ -37,11 +37,9 @@ logger = get_main_logger() # May be imported by plugins -# DEPRECATED: Migrate to open_log_folder. Will remove in 6.0 or later. +@warnings.deprecated("Migrate to open_log_folder. Will remove in 6.0 or later.") 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(Path(config.app_dir_path / 'logs')) From 5f70a8d7a456e4383104cebf7d6dfe7cd5a28266 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 9 Nov 2025 13:41:07 -0500 Subject: [PATCH 4/5] [Minor] Replace Dep'd Functions, Minor Type Hinting --- ChangeLog.md | 2 +- companion.py | 2 +- monitor.py | 4 ++-- util_ships.py | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 8bab3370..ca780790 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1637,7 +1637,7 @@ updated to using Python 3.9.8. - When the new option is not active, the application will minimize to the taskbar as normal. -Bug Fixex +Bug Fixes --- * If a CAPI query failed in such a way that no `requests.Response` object diff --git a/companion.py b/companion.py index 384dcdd5..19f12a14 100644 --- a/companion.py +++ b/companion.py @@ -821,7 +821,7 @@ class Session: capi_data = CAPIData(capi_json, capi_host, capi_endpoint, monitor.cmdr) self.capi_raw_data.record_endpoint( capi_endpoint, r.content.decode(encoding='utf-8'), - datetime.datetime.utcnow() + datetime.datetime.now(datetime.timezone.utc) ) except requests.ConnectionError as e: diff --git a/monitor.py b/monitor.py index 91cf3375..3df0f85c 100644 --- a/monitor.py +++ b/monitor.py @@ -67,7 +67,7 @@ class EDLogs(FileSystemEventHandler): def __init__(self) -> None: # 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.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.logfile: str | None = None self.observer: BaseObserver | None = None @@ -188,7 +188,7 @@ class EDLogs(FileSystemEventHandler): }, } - def start(self, root: 'tkinter.Tk') -> bool: # noqa: CCR001 + def start(self, root: tkinter.Tk) -> bool: # noqa: CCR001 """ Start journal monitoring. diff --git a/util_ships.py b/util_ships.py index b2105106..8abbe20f 100644 --- a/util_ships.py +++ b/util_ships.py @@ -5,6 +5,7 @@ Copyright (c) EDCD, All Rights Reserved Licensed under the GNU General Public License. See LICENSE file. """ +import os from pathlib import Path from edmc_data import ship_name_map @@ -17,7 +18,7 @@ def ship_file_name(ship_name: str, ship_type: str) -> str: name = Path(name).with_suffix("").name # Check if the name is a reserved filename - if Path(name).is_reserved(): + if os.path.isreserved(name): name += "_" return name.translate( From 8c88ee7d75da87519baba2d3f0f94a52b5073845 Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Sun, 9 Nov 2025 14:02:35 -0500 Subject: [PATCH 5/5] [#2437] Add Catch for "en" as the set language --- l10n.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l10n.py b/l10n.py index b9c8ab97..236d65f4 100755 --- a/l10n.py +++ b/l10n.py @@ -126,7 +126,7 @@ class Translations: def contents(self, lang: str, plugin_path: pathlib.Path | None = None) -> dict[str, str]: """Load all the translations from a translation file.""" - if lang not in self.available(): + if lang not in self.available() and lang != self.FALLBACK: # Because "en" doesn't appear in self.available() raise KeyError(f'Language {lang} not available') translations = {}