diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index ea9a3093..32deb018 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -53,7 +53,7 @@ jobs: # Get Python set up #################################################################### - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version-file: '.python-version' - name: Install dependencies diff --git a/.github/workflows/push-checks.yml b/.github/workflows/push-checks.yml index 803bfe95..31ecfd6d 100644 --- a/.github/workflows/push-checks.yml +++ b/.github/workflows/push-checks.yml @@ -26,7 +26,7 @@ jobs: with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version-file: '.python-version' - name: Install dependencies diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index a3de381b..163e904f 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -1,5 +1,5 @@ # vim: tabstop=2 shiftwidth=2 -name: Build EDMC for Windows +name: Build EDMC on: push: @@ -11,7 +11,7 @@ jobs: variables: outputs: sem_ver: ${{ steps.var.outputs.sem_ver }} - archive_exclusions: ${{ steps.var.outputs.archive_exclusions }} + short_sha: ${{ steps.var.outputs.short_sha }} runs-on: "ubuntu-latest" steps: - name: Setting global variables @@ -20,6 +20,7 @@ jobs: with: script: | core.setOutput('sem_ver', '${{ github.ref_name }}'.replaceAll('Release\/', '')) + core.setOutput('short_sha', '${{ github.sha }}'.substring(0, 8)) linux_build: needs: [variables] @@ -29,6 +30,9 @@ jobs: - uses: actions/checkout@v4 with: submodules: true + - name: Create .gitversion + run: | + echo "${{ needs.variables.outputs.short_sha }}" > .gitversion - name: Make tar archive run: | @@ -40,7 +44,11 @@ jobs: --exclude=EDMarketConnector-release-*.* \ --exclude=.editorconfig \ --exclude=.flake8 \ - --exclude=.git* \ + --exclude=.gitattributes \ + --exclude=.gitignore \ + --exclude=.gitmodules \ + --exclude=.git \ + --exclude=.github \ --exclude=.mypy.ini \ --exclude=.pre-commit-config.yaml \ --exclude=build.py \ @@ -93,7 +101,7 @@ jobs: # presumably due to a Windows CL length limit. exclusions: 'EDMarketConnector/EDMarketConnector-release-*.* EDMarketConnector/.editorconfig EDMarketConnector/.flake8 EDMarketConnector/.git* EDMarketConnector/.mypy.ini EDMarketConnector/.pre-commit-config.yaml EDMarketConnector/build.py EDMarketConnector/*.manifest EDMarketConnector/coriolis-data/ EDMarketConnector/img/ EDMarketConnector/pyproject.toml EDMarketConnector/scripts/ EDMarketConnector/tests/' - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version-file: '.python-version' architecture: "x86" @@ -105,9 +113,9 @@ jobs: - name: Download winsparkle run: | - Invoke-Webrequest -UseBasicParsing https://github.com/vslavik/winsparkle/releases/download/v0.8.0/WinSparkle-0.8.0.zip -OutFile out.zip + Invoke-Webrequest -UseBasicParsing https://github.com/vslavik/winsparkle/releases/download/v0.8.1/WinSparkle-0.8.1.zip -OutFile out.zip Expand-Archive out.zip - Move-Item 'out\WinSparkle-0.8.0\Release\*' '.\' + Move-Item 'out\WinSparkle-0.8.1\Release\*' '.\' - name: Build EDMC run: | diff --git a/.gitignore b/.gitignore index 4283fcb0..a7da806e 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ htmlcov/ .coverage pylintrc pylint.txt +.pylintrc # Ignore Submodule data directory coriolis-data/ diff --git a/.python-version b/.python-version index 371cfe35..d4b278f0 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.11.1 +3.11.7 diff --git a/ChangeLog.md b/ChangeLog.md index ee4f7c56..0ff76f1a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -9,7 +9,7 @@ produce the Windows executables and installer. --- -* We now test against, and package with, Python 3.11.1, 32-bit. +* We now test against, and package with, Python 3.11.7, 32-bit. **As a consequence of this we no longer support Windows 7. This is due to @@ -33,6 +33,35 @@ produce the Windows executables and installer. currently used version in a given branch. --- +Release 5.10.1 +=== +This release contains a number of bugfixes, minor performance enhancements, +workflow and dependency updates, and a function deprecation. + +Note to plugin developers: modules.p and ships.p are deprecated, and slated +for removal in the next major release! Please look for that change coming soon. + +Note to plugin developers: The `openurl()` function in ttkHyperlinkLabel has been deprecated, +and slated for removal in the next major release! Please migrate to `webbrowser.open()`. + +**Changes and Enhancements** +* Deprecated `openurl()`. Please migrate to `webbrowser.open()` +* Updated a number of list comparisons to use more efficient tuple comparisons +* Updated a few type hints +* Updated a few binary comparitors to be more efficient +* Moved `resources.json` and `modules.json` back to the top level for all users +* Updated several dependencies +* Updated Python version to 3.11.7 + +**Bug Fixes** +* Fixed an issue where resources files could be in different locations for different users. + * These files are now in the same location (top level) for all users on all distributions. +* Fixed an issue where CMDRs without the Git application installed would crash on start if running from Source. + * Thanks to the Flatpak team for pointing this one out! +* Fixed a bug where CMDRs running from source would have their git hash version displayed as UNKNOWN. + * We're now more failure tolerant and use the bundled .gitversion if no true git hash is provided. +* Fixed a bug where starting two copies of EDMC with a valid install would not generate a duplicate warning. + Release 5.10.0 === This release contains a number of under-the-hood changes to EDMC designed to improve performance, code diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 87ba6814..690300e6 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -336,6 +336,8 @@ if __name__ == '__main__': # noqa: C901 def already_running_popup(): """Create the "already running" popup.""" + import tkinter as tk + from tkinter import ttk # Check for CL arg that suppresses this popup. if args.suppress_dupe_process_popup: sys.exit(0) @@ -1547,7 +1549,7 @@ class AppWindow: self.w.update_idletasks() # Companion login - if entry['event'] in [None, 'StartUp', 'NewCommander', 'LoadGame'] and monitor.cmdr: + if entry['event'] in (None, 'StartUp', 'NewCommander', 'LoadGame') and monitor.cmdr: if not config.get_list('cmdrs') or monitor.cmdr not in config.get_list('cmdrs'): config.set('cmdrs', config.get_list('cmdrs', default=[]) + [monitor.cmdr]) self.login() @@ -1565,7 +1567,7 @@ class AppWindow: logger.trace_if('journal.queue', 'Startup, returning') return # Startup - if entry['event'] in ['StartUp', 'LoadGame'] and monitor.started: + if entry['event'] in ('StartUp', 'LoadGame') and monitor.started: logger.info('StartUp or LoadGame event') # Disable WinSparkle automatic update checks, IFF configured to do so when in-game diff --git a/build.py b/build.py index e1d37f49..a86689a7 100644 --- a/build.py +++ b/build.py @@ -77,8 +77,8 @@ def generate_data_files( "snd_good.wav", "snd_bad.wav", "modules.p", # TODO: Remove in 6.0 - "resources/modules.json", - "resources/ships.json", + "modules.json", + "ships.json", "ships.p", # TODO: Remove in 6.0 f"{app_name}.VisualElementsManifest.xml", f"{app_name}.ico", diff --git a/config/__init__.py b/config/__init__.py index 77b6a0f7..7e5025a6 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -54,7 +54,7 @@ appcmdname = 'EDMC' # # Major.Minor.Patch(-prerelease)(+buildmetadata) # NB: Do *not* import this, use the functions appversion() and appversion_nobuild() -_static_appversion = '5.10.0' +_static_appversion = '5.10.1' _cached_version: semantic_version.Version | None = None copyright = '© 2015-2019 Jonathan Harris, 2020-2023 EDCD' @@ -81,15 +81,15 @@ else: _T = TypeVar('_T') -def git_shorthash_from_head() -> str: +def git_shorthash_from_head() -> str | None: """ Determine short hash for current git HEAD. Includes `.DIRTY` if any changes have been made from HEAD - :return: str - None if we couldn't determine the short hash. + :return: str | None: None if we couldn't determine the short hash. """ - shorthash: str = None # type: ignore + shorthash: str | None = None try: git_cmd = subprocess.Popen( @@ -99,14 +99,14 @@ def git_shorthash_from_head() -> str: ) out, err = git_cmd.communicate() - except subprocess.CalledProcessError as e: + except (subprocess.CalledProcessError, FileNotFoundError) as e: logger.info(f"Couldn't run git command for short hash: {e!r}") else: shorthash = out.decode().rstrip('\n') if re.match(r'^[0-9a-f]{7,}$', shorthash) is None: logger.error(f"'{shorthash}' doesn't look like a valid git short hash, forcing to None") - shorthash = None # type: ignore + shorthash = None if shorthash is not None: with contextlib.suppress(Exception): @@ -134,13 +134,19 @@ def appversion() -> semantic_version.Version: # Running frozen, so we should have a .gitversion file # Yes, .parent because if frozen we're inside library.zip with open(pathlib.Path(sys.path[0]).parent / GITVERSION_FILE, encoding='utf-8') as gitv: - shorthash = gitv.read() + shorthash: str | None = gitv.read() else: - # Running from source + # Running from source. Use git rev-parse --short HEAD + # or fall back to .gitversion file if it exists. + # This is also required for the Flatpak shorthash = git_shorthash_from_head() if shorthash is None: - shorthash = 'UNKNOWN' + if pathlib.Path(sys.path[0] + "/" + GITVERSION_FILE).exists(): + with open(pathlib.Path(sys.path[0] + "/" + GITVERSION_FILE), encoding='utf-8') as gitv: + shorthash = gitv.read() + else: + shorthash = 'UNKNOWN' _cached_version = semantic_version.Version(f'{_static_appversion}+{shorthash}') return _cached_version diff --git a/coriolis-update-files.py b/coriolis-update-files.py index b68d9647..9c1d7ecc 100755 --- a/coriolis-update-files.py +++ b/coriolis-update-files.py @@ -57,7 +57,7 @@ if __name__ == "__main__": modules['_'.join([reverse_ship_map[name], 'armour', bulkhead])] = {'mass': m['bulkheads'][i]['mass']} ships = OrderedDict([(k, ships[k]) for k in sorted(ships)]) # sort for easier diffing - with open("resources/ships.json", "w") as ships_file: + with open("ships.json", "w") as ships_file: json.dump(ships, ships_file, indent=4) # Module masses @@ -92,5 +92,5 @@ if __name__ == "__main__": add(modules, 'hpt_multicannon_fixed_medium_advanced', {'mass': 4}) modules = OrderedDict([(k, modules[k]) for k in sorted(modules)]) # sort for easier diffing - with open("resources/modules.json", "w") as modules_file: + with open("modules.json", "w") as modules_file: json.dump(modules, modules_file, indent=4) diff --git a/edshipyard.py b/edshipyard.py index 5d14009c..1660ad7e 100644 --- a/edshipyard.py +++ b/edshipyard.py @@ -24,7 +24,7 @@ __Module = dict[str, Union[str, list[str]]] # Have to keep old-style here for c ship_map = ship_name_map.copy() # Ship masses -ships_file = config.respath_path / "resources" / "ships.json" +ships_file = config.respath_path / "ships.json" with open(ships_file, encoding="utf-8") as ships_file_handle: ships = json.load(ships_file_handle) diff --git a/hotkey/darwin.py b/hotkey/darwin.py index 63f9d132..6afd0239 100644 --- a/hotkey/darwin.py +++ b/hotkey/darwin.py @@ -211,12 +211,12 @@ class MacHotkeyMgr(AbstractHotkeyMgr): return False # BkSp, Del, Clear = clear hotkey - if keycode in [0x7f, ord(NSDeleteFunctionKey), ord(NSClearLineFunctionKey)]: + if keycode in (0x7f, ord(NSDeleteFunctionKey), ord(NSClearLineFunctionKey)): self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE return None # don't allow keys needed for typing in System Map - if keycode in [0x13, 0x20, 0x2d] or 0x61 <= keycode <= 0x7a: + if keycode in (0x13, 0x20, 0x2d) or 0x61 <= keycode <= 0x7a: NSBeep() self.acquire_state = MacHotkeyMgr.ACQUIRE_INACTIVE return None diff --git a/hotkey/windows.py b/hotkey/windows.py index f21a24b2..fa4cb3a6 100644 --- a/hotkey/windows.py +++ b/hotkey/windows.py @@ -284,23 +284,23 @@ class WindowsHotkeyMgr(AbstractHotkeyMgr): | ((GetKeyState(VK_RWIN) & 0x8000) and MOD_WIN) keycode = event.keycode - if keycode in [VK_SHIFT, VK_CONTROL, VK_MENU, VK_LWIN, VK_RWIN]: + if keycode in (VK_SHIFT, VK_CONTROL, VK_MENU, VK_LWIN, VK_RWIN): return 0, modifiers if not modifiers: if keycode == VK_ESCAPE: # Esc = retain previous return False - if keycode in [VK_BACK, VK_DELETE, VK_CLEAR, VK_OEM_CLEAR]: # BkSp, Del, Clear = clear hotkey + if keycode in (VK_BACK, VK_DELETE, VK_CLEAR, VK_OEM_CLEAR): # BkSp, Del, Clear = clear hotkey return None if ( - keycode in [VK_RETURN, VK_SPACE, VK_OEM_MINUS] or ord('A') <= keycode <= ord('Z') + keycode in (VK_RETURN, VK_SPACE, VK_OEM_MINUS) or ord('A') <= keycode <= ord('Z') ): # don't allow keys needed for typing in System Map winsound.MessageBeep() return None - if (keycode in [VK_NUMLOCK, VK_SCROLL, VK_PROCESSKEY] + if (keycode in (VK_NUMLOCK, VK_SCROLL, VK_PROCESSKEY) or VK_CAPITAL <= keycode <= VK_MODECHANGE): # ignore unmodified mode switch keys return 0, modifiers diff --git a/l10n.py b/l10n.py index 26be12fc..a34ea0dc 100755 --- a/l10n.py +++ b/l10n.py @@ -25,7 +25,7 @@ from config import config from EDMCLogging import get_main_logger if TYPE_CHECKING: - def _(x: str) -> str: ... + def _(x: str) -> str: return x # Note that this is also done in EDMarketConnector.py, and thus removing this here may not have a desired effect try: diff --git a/resources/modules.json b/modules.json similarity index 100% rename from resources/modules.json rename to modules.json diff --git a/outfitting.py b/outfitting.py index 07e8ae76..2bb47c6d 100644 --- a/outfitting.py +++ b/outfitting.py @@ -57,7 +57,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0 """ # Lazily populate if not moduledata: - modules_path = config.respath_path / "resources" / "modules.json" + modules_path = config.respath_path / "modules.json" moduledata.update(json.loads(modules_path.read_text())) if not module.get('name'): @@ -79,7 +79,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0 new['rating'] = 'I' # Skip uninteresting stuff - some no longer present in ED 3.1 cAPI data - elif (name[0] in [ + elif (name[0] in ( 'bobble', 'decal', 'nameplate', @@ -87,7 +87,7 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0 'enginecustomisation', 'voicepack', 'weaponcustomisation' - ] + ) or name[1].startswith('shipkit')): return None @@ -205,10 +205,10 @@ def lookup(module, ship_map, entitled=False) -> dict | None: # noqa: C901, CCR0 elif len(name) < 4 and name[1] == 'resourcesiphon': # Hack! 128066402 has no size or class. (new['class'], new['rating']) = ('1', 'I') - elif len(name) < 4 and name[1] in ['guardianpowerdistributor', 'guardianpowerplant']: # Hack! No class. + elif len(name) < 4 and name[1] in ('guardianpowerdistributor', 'guardianpowerplant'): # Hack! No class. (new['class'], new['rating']) = (str(name[2][4:]), 'A') - elif len(name) < 4 and name[1] in ['guardianfsdbooster']: # Hack! No class. + elif len(name) < 4 and name[1] == 'guardianfsdbooster': # Hack! No class. (new['class'], new['rating']) = (str(name[2][4:]), 'H') else: diff --git a/plug.py b/plug.py index aa34ce77..15e7cfaa 100644 --- a/plug.py +++ b/plug.py @@ -165,7 +165,7 @@ def load_plugins(master: tk.Tk) -> None: def _load_internal_plugins(): internal = [] for name in sorted(os.listdir(config.internal_plugin_dir_path)): - if name.endswith('.py') and name[0] not in ['.', '_']: + if name.endswith('.py') and name[0] not in ('.', '_'): try: plugin = Plugin(name[:-3], os.path.join(config.internal_plugin_dir_path, name), logger) plugin.folder = None @@ -184,7 +184,7 @@ def _load_found_plugins(): for name in sorted(os.listdir(config.plugin_dir_path), key=lambda n: ( not os.path.isfile(os.path.join(config.plugin_dir_path, n, '__init__.py')), n.lower())): - if not os.path.isdir(os.path.join(config.plugin_dir_path, name)) or name[0] in ['.', '_']: + if not os.path.isdir(os.path.join(config.plugin_dir_path, name)) or name[0] in ('.', '_'): pass elif name.endswith('.disabled'): name, discard = name.rsplit('.', 1) diff --git a/plugins/inara.py b/plugins/inara.py index 199e6a1c..d523bb2e 100644 --- a/plugins/inara.py +++ b/plugins/inara.py @@ -918,7 +918,7 @@ def journal_entry( # noqa: C901, CCR001 ]) # optional mission-specific properties - for (iprop, prop) in [ + for (iprop, prop) in ( ('missionExpiry', 'Expiry'), # Listed as optional in the docs, but always seems to be present ('starsystemNameTarget', 'DestinationSystem'), ('stationNameTarget', 'DestinationStation'), @@ -932,7 +932,7 @@ def journal_entry( # noqa: C901, CCR001 ('passengerCount', 'PassengerCount'), ('passengerIsVIP', 'PassengerVIPs'), ('passengerIsWanted', 'PassengerWanted'), - ]: + ): if prop in entry: data[iprop] = entry[prop] @@ -1295,7 +1295,7 @@ def journal_entry( # noqa: C901, CCR001 # Friends if event_name == 'Friends': - if entry['Status'] in ['Added', 'Online']: + if entry['Status'] in ('Added', 'Online'): new_add_event( 'addCommanderFriend', entry['timestamp'], @@ -1305,7 +1305,7 @@ def journal_entry( # noqa: C901, CCR001 } ) - elif entry['Status'] in ['Declined', 'Lost']: + elif entry['Status'] in ('Declined', 'Lost'): new_add_event( 'delCommanderFriend', entry['timestamp'], @@ -1666,7 +1666,7 @@ def handle_special_events(data_event: dict[str, Any], reply_event: dict[str, Any this.lastlocation = reply_event.get('eventData', {}) if not config.shutting_down: this.system_link.event_generate('<>', when="tail") - elif data_event['eventName'] in ['addCommanderShip', 'setCommanderShip']: + elif data_event['eventName'] in ('addCommanderShip', 'setCommanderShip'): this.lastship = reply_event.get('eventData', {}) if not config.shutting_down: this.system_link.event_generate('<>', when="tail") diff --git a/requirements-dev.txt b/requirements-dev.txt index 7fb98e50..f8d701ff 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,7 +5,7 @@ wheel # We can't rely on just picking this up from either the base (not venv), # or venv-init-time version. Specify here so that dependabot will prod us # about new versions. -setuptools==69.0.2 +setuptools==69.0.3 # Static analysis tools flake8==6.1.0 @@ -18,17 +18,17 @@ flake8-noqa==1.3.2 flake8-polyfill==1.0.2 flake8-use-fstring==1.4 -mypy==1.7.1 +mypy==1.8.0 pep8-naming==0.13.3 safety==2.3.5 -types-requests==2.31.0.10 +types-requests==2.31.0.20231231 types-pkg-resources==0.1.3 # Code formatting tools autopep8==2.0.4 # Git pre-commit checking -pre-commit==3.5.0 +pre-commit==3.6.0 # HTML changelogs grip==4.6.2 @@ -40,7 +40,7 @@ py2exe==0.13.0.1; sys_platform == 'win32' # Testing pytest==7.4.3 pytest-cov==4.1.0 # Pytest code coverage support -coverage[toml]==7.3.2 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs +coverage[toml]==7.3.4 # pytest-cov dep. This is here to ensure that it includes TOML support for pyproject.toml configs coverage-conditional-plugin==0.9.0 # For manipulating folder permissions and the like. pywin32==306; sys_platform == 'win32' diff --git a/resources/ships.json b/ships.json similarity index 100% rename from resources/ships.json rename to ships.json diff --git a/stats.py b/stats.py index 041e47f3..4351e297 100644 --- a/stats.py +++ b/stats.py @@ -5,6 +5,8 @@ Copyright (c) EDCD, All Rights Reserved Licensed under the GNU General Public License. See LICENSE file. """ +from __future__ import annotations + import csv import json import sys @@ -22,7 +24,7 @@ from monitor import monitor logger = EDMCLogging.get_main_logger() if TYPE_CHECKING: - def _(x: str) -> str: ... + def _(x: str) -> str: return x if sys.platform == 'win32': import ctypes diff --git a/td.py b/td.py index 00dd1736..6a588f57 100644 --- a/td.py +++ b/td.py @@ -32,7 +32,7 @@ def export(data: CAPIData) -> None: with open(data_path / data_filename, 'wb') as h: # Format described here: https://github.com/eyeonus/Trade-Dangerous/wiki/Price-Data h.write('#! trade.py import -\n'.encode('utf-8')) - this_platform = sys.platform == 'darwin' and "Mac OS" or system() + this_platform = "Mac OS" if sys.platform == 'darwin' else system() cmdr_name = data['commander']['name'].strip() h.write( f'# Created by {applongname} {appversion()} on {this_platform} for Cmdr {cmdr_name}.\n'.encode('utf-8') diff --git a/tests/config/_old_config.py b/tests/config/_old_config.py index 22f0b18f..690c75eb 100644 --- a/tests/config/_old_config.py +++ b/tests/config/_old_config.py @@ -296,7 +296,7 @@ class OldConfig: None, ctypes.byref(key_size) ) - or key_type.value not in [REG_SZ, REG_MULTI_SZ] + or key_type.value not in (REG_SZ, REG_MULTI_SZ) ): return default diff --git a/ttkHyperlinkLabel.py b/ttkHyperlinkLabel.py index 24470a93..0cc19b4e 100644 --- a/ttkHyperlinkLabel.py +++ b/ttkHyperlinkLabel.py @@ -22,20 +22,21 @@ from __future__ import annotations import sys import tkinter as tk +import warnings import webbrowser from tkinter import font as tk_font from tkinter import ttk from typing import TYPE_CHECKING, Any if TYPE_CHECKING: - def _(x: str) -> str: ... + def _(x: str) -> str: return x # FIXME: Split this into multi-file module to separate the platforms class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # type: ignore """Clickable label for HTTP links.""" - def __init__(self, master: tk.Frame | None = None, **kw: Any) -> None: + def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> None: """ Initialize the HyperlinkLabel. @@ -80,10 +81,10 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # typ ) -> dict[str, tuple[str, str, str, Any, Any]] | None: """Change cursor and appearance depending on state and text.""" # This class' state - for thing in ['url', 'popup_copy', 'underline']: + for thing in ('url', 'popup_copy', 'underline'): if thing in kw: setattr(self, thing, kw.pop(thing)) - for thing in ['foreground', 'disabledforeground']: + for thing in ('foreground', 'disabledforeground'): if thing in kw: setattr(self, thing, kw[thing]) @@ -135,7 +136,7 @@ class HyperlinkLabel(sys.platform == 'darwin' and tk.Label or ttk.Label): # typ url = self.url(self['text']) if callable(self.url) else self.url if url: self._leave(event) # Remove underline before we change window to browser - openurl(url) + webbrowser.open(url) def _contextmenu(self, event: tk.Event) -> None: if self['text'] and (self.popup_copy(self['text']) if callable(self.popup_copy) else self.popup_copy): @@ -183,4 +184,6 @@ def openurl(url: str) -> None: ended up using `webbrowser.open()` *anyway*. :param url: URL to open. """ + warnings.warn("This function is deprecated. " + "Please use `webbrowser.open() instead.", DeprecationWarning, stacklevel=2) webbrowser.open(url)