Merge pull request #2520 from HullSeals/enhancement/2113/update-python-version

[#2113] Update Python Version to 3.13
This commit is contained in:
David Sangrey authored and GitHub committed 2025-11-09 14:13:18 -05:00
commit f5b1d088e5
14 files changed
+205 -179

No files matched your search

+1 -1
View File
@@ -77,7 +77,7 @@ repos:
always_run: true
default_language_version:
python: python3.11
python: python3.13
default_stages: [ pre-commit, pre-push ]
+1 -1
View File
@@ -1 +1 @@
3.11
3.13
+2 -2
View File
@@ -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
@@ -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
+1 -1
View File
@@ -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:
+9 -13
View File
@@ -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)
+5 -8
View File
@@ -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 = {}
@@ -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__()
+2 -2
View File
@@ -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.
+2 -4
View File
@@ -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)
+2 -1
View File
@@ -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
+1 -3
View File
@@ -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'))
+166 -130
View File
@@ -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("<<CompanionAuthEvent>>")')
self.master.event_generate('<<CompanionAuthEvent>>', when="tail")
self.master.event_generate("<<CompanionAuthEvent>>", 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 <https://docs.python.org/3/library/ctypes.html#fundamental-data-types>
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: <function WndProc
# at 0x000001F5B8738FE0>
# 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 (
'<html>'
'<head>'
'<title>Authentication successful - Elite: Dangerous</title>'
'<style>'
"<html>"
"<head>"
"<title>Authentication successful - Elite: Dangerous</title>"
"<style>"
'body { background-color: #000; color: #fff; font-family: "Helvetica Neue", Arial, sans-serif; }'
'h1 { text-align: center; margin-top: 100px; }'
'p { text-align: center; }'
'</style>'
'</head>'
'<body>'
'<h1>Authentication successful</h1>'
'<p>Thank you for authenticating.</p>'
'<p>Please close this browser tab now.</p>'
'</body>'
'</html>'
"h1 { text-align: center; margin-top: 100px; }"
"p { text-align: center; }"
"</style>"
"</head>"
"<body>"
"<h1>Authentication successful</h1>"
"<p>Thank you for authenticating.</p>"
"<p>Please close this browser tab now.</p>"
"</body>"
"</html>"
)
@@ -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
+9 -10
View File
@@ -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
+2 -2
View File
@@ -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
+2 -1
View File
@@ -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(