From 2ec702ec8c61a2a7f05677c7b0feea6653ff5f14 Mon Sep 17 00:00:00 2001 From: Daniel Bast <2790401+dbast@users.noreply.github.com> Date: Mon, 24 Apr 2023 10:55:29 +0200 Subject: [PATCH] Run flake8 via pre-commit and resolve findings with the help of black (#125) * Add black+flake8 to pre-commit * Apply black * Handle flake8 findings * news --- .pre-commit-config.yaml | 9 + menuinst/_legacy/__init__.py | 17 +- menuinst/_legacy/main.py | 14 +- menuinst/_legacy/utils.py | 2 +- menuinst/_legacy/win32.py | 59 ++-- menuinst/_schema.py | 13 +- menuinst/platforms/base.py | 13 +- menuinst/platforms/linux.py | 10 +- menuinst/platforms/osx.py | 2 +- menuinst/platforms/win.py | 6 +- menuinst/platforms/win_utils/knownfolders.py | 304 ++++++++++--------- menuinst/platforms/win_utils/registry.py | 20 +- menuinst/platforms/win_utils/win_elevate.py | 34 +-- menuinst/utils.py | 16 +- news/116-pre-commit | 2 +- tests/_legacy/test_menu_creation.py | 19 +- tests/_legacy/test_win32.py | 6 +- tests/conftest.py | 2 + tests/test_api.py | 36 ++- tests/test_conda.py | 4 +- tests/test_schema.py | 3 +- tests/test_windows.py | 10 +- 22 files changed, 344 insertions(+), 257 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 39b0d8c8..f578fbcc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,3 +23,12 @@ repos: rev: 5.12.0 hooks: - id: isort + - repo: https://github.com/psf/black + rev: 23.1.0 + hooks: + - id: black + args: [--skip-string-normalization] + - repo: https://github.com/pycqa/flake8 + rev: 6.0.0 + hooks: + - id: flake8 diff --git a/menuinst/_legacy/__init__.py b/menuinst/_legacy/__init__.py index 7dba6214..c8037933 100644 --- a/menuinst/_legacy/__init__.py +++ b/menuinst/_legacy/__init__.py @@ -68,15 +68,22 @@ def install(path, remove=False, prefix=None, recursing=False, root_prefix=None): retcode = 1 try: if not recursing: - retcode = runAsAdmin([join(root_prefix, 'python'), '-c', - "import menuinst; menuinst.install(%r, %r, %r, %r, %r)" % ( - path, bool(remove), prefix, True, root_prefix)]) + retcode = runAsAdmin( + [ + join(root_prefix, 'python'), + '-c', + "import menuinst; menuinst.install(%r, %r, %r, %r, %r)" + % (path, bool(remove), prefix, True, root_prefix), + ] + ) except OSError: pass if retcode != 0: - logging.warn("Insufficient permissions to write menu folder. " - "Falling back to user location") + logging.warn( + "Insufficient permissions to write menu folder. " + "Falling back to user location" + ) _install(path, remove, prefix, mode='user', root_prefix=root_prefix) else: _install(path, remove, prefix, mode='user', root_prefix=root_prefix) diff --git a/menuinst/_legacy/main.py b/menuinst/_legacy/main.py index f5321f8f..78e8f38e 100644 --- a/menuinst/_legacy/main.py +++ b/menuinst/_legacy/main.py @@ -9,19 +9,13 @@ def main(): from optparse import OptionParser - p = OptionParser( - usage="usage: %prog [options] MENU_FILE", - description="install a menu item") + p = OptionParser(usage="usage: %prog [options] MENU_FILE", description="install a menu item") - p.add_option('-p', '--prefix', - action="store", - default=DEFAULT_PREFIX) + p.add_option('-p', '--prefix', action="store", default=DEFAULT_PREFIX) - p.add_option('--remove', - action="store_true") + p.add_option('--remove', action="store_true") - p.add_option('--version', - action="store_true") + p.add_option('--version', action="store_true") opts, args = p.parse_args() diff --git a/menuinst/_legacy/utils.py b/menuinst/_legacy/utils.py index eed4209a..c5911a44 100644 --- a/menuinst/_legacy/utils.py +++ b/menuinst/_legacy/utils.py @@ -6,7 +6,7 @@ def rm_empty_dir(path): try: os.rmdir(path) - except OSError: # directory might not exist or not be empty + except OSError: # directory might not exist or not be empty pass diff --git a/menuinst/_legacy/win32.py b/menuinst/_legacy/win32.py index 152fb16c..4fda343f 100644 --- a/menuinst/_legacy/win32.py +++ b/menuinst/_legacy/win32.py @@ -66,8 +66,6 @@ def ensure_pad(name, pad="_"): def to_unicode(var, codec=locale.getpreferredencoding()): - if sys.version_info[0] < 3 and isinstance(var, unicode): - return var if not codec: codec = "utf-8" if hasattr(var, "decode"): @@ -79,7 +77,7 @@ def to_bytes(var, codec=locale.getpreferredencoding()): if isinstance(var, bytes): return var if not codec: - codec="utf-8" + codec = "utf-8" if hasattr(var, "encode"): var = var.encode(codec) return var @@ -106,22 +104,31 @@ def substitute_env_variables(text, dir): (u'${PREFIX}', env_prefix), (u'${ROOT_PREFIX}', root_prefix), (u'${DISTRIBUTION_NAME}', os.path.split(root_prefix)[-1].capitalize()), - (u'${PYTHON_SCRIPTS}', - os.path.normpath(join(env_prefix, u'Scripts')).replace(u"\\", u"/")), + ( + u'${PYTHON_SCRIPTS}', + os.path.normpath(join(env_prefix, u'Scripts')).replace(u"\\", u"/"), + ), (u'${MENU_DIR}', join(env_prefix, u'Menu')), (u'${PERSONALDIR}', dir['documents']), (u'${USERPROFILE}', dir['profile']), (u'${ENV_NAME}', env_name), (u'${PY_VER}', u'%d' % (py_major_ver)), (u'${PLATFORM}', u"(%s-bit)" % py_bitness), - ): + ): if b: text = text.replace(a, b) return text class Menu(object): - def __init__(self, name, prefix=unicode_root_prefix, env_name=u"", mode=None, root_prefix=unicode_root_prefix): + def __init__( + self, + name, + prefix=unicode_root_prefix, + env_name=u"", + mode=None, + root_prefix=unicode_root_prefix, + ): """ Prefix is the system prefix to be used -- this is needed since there is the possibility of a different Python's packages being managed. @@ -130,9 +137,13 @@ def __init__(self, name, prefix=unicode_root_prefix, env_name=u"", mode=None, ro # bytestrings passed in need to become unicode self.prefix = to_unicode(prefix) self.root_prefix = to_unicode(root_prefix) - used_mode = mode if mode else ('user' if exists(join(self.prefix, u'.nonadmin')) else 'system') - logger.debug("Menu: name: '%s', prefix: '%s', env_name: '%s', mode: '%s', used_mode: '%s', root_prefix: '%s'" - % (name, self.prefix, env_name, mode, used_mode, root_prefix)) + used_mode = ( + mode if mode else ('user' if exists(join(self.prefix, u'.nonadmin')) else 'system') + ) + logger.debug( + "Menu: name: '%s', prefix: '%s', env_name: '%s', mode: '%s', used_mode: '%s', root_prefix: '%s'" # noqa + % (name, self.prefix, env_name, mode, used_mode, root_prefix) + ) try: self.set_dir(name, self.prefix, env_name, used_mode, root_prefix) except WindowsError: @@ -141,11 +152,13 @@ def __init__(self, name, prefix=unicode_root_prefix, env_name=u"", mode=None, ro # required. If the process isn't elevated, we get the # WindowsError if 'user' in dirs_src and used_mode == 'system': - logger.warn("Insufficient permissions to write menu folder. " - "Falling back to user location") + logger.warn( + "Insufficient permissions to write menu folder. " + "Falling back to user location" + ) try: self.set_dir(name, self.prefix, env_name, 'user') - except: + except: # noqa pass else: logger.fatal("Unable to create AllUsers menu folder") @@ -194,9 +207,11 @@ def quote_args(args): # cmd.exe /K or /C expects a single string argument and requires # doubled-up quotes when any sub-arguments have spaces: # https://stackoverflow.com/a/6378038/3257826 - if (len(args) > 2 and ("CMD.EXE" in args[0].upper() or "%COMSPEC%" in args[0].upper()) - and (args[1].upper() == '/K' or args[1].upper() == '/C') - and any(' ' in arg for arg in args[2:]) + if ( + len(args) > 2 + and ("CMD.EXE" in args[0].upper() or "%COMSPEC%" in args[0].upper()) + and (args[1].upper() == '/K' or args[1].upper() == '/C') + and any(' ' in arg for arg in args[2:]) ): args = [ ensure_pad(args[0], '"'), # cmd.exe @@ -222,11 +237,11 @@ def create(self, remove=False): fix_win_slashes = [0] prefix = self.menu.prefix.replace('/', '\\') unicode_root_prefix = self.menu.root_prefix.replace('/', '\\') - root_py = join(unicode_root_prefix, u"python.exe") + root_py = join(unicode_root_prefix, u"python.exe") root_pyw = join(unicode_root_prefix, u"pythonw.exe") - env_py = join(prefix, u"python.exe") + env_py = join(prefix, u"python.exe") env_pyw = join(prefix, u"pythonw.exe") - cwp_py = [root_py, join(unicode_root_prefix, u'cwp.py'), prefix, env_py] + cwp_py = [root_py, join(unicode_root_prefix, u'cwp.py'), prefix, env_py] cwp_pyw = [root_pyw, join(unicode_root_prefix, u'cwp.py'), prefix, env_pyw] if "pywscript" in self.shortcut: args = cwp_pyw @@ -241,7 +256,7 @@ def create(self, remove=False): elif "script" in self.shortcut: # It is unclear whether running through cwp.py is what we want here. In # the long term I would rather this was made an explicit choice. - args = [root_py, join(unicode_root_prefix, u'cwp.py'), prefix] + args = [root_py, join(unicode_root_prefix, u'cwp.py'), prefix] fix_win_slashes = [len(args)] args += self.shortcut["script"].split() extend_script_args(args, self.shortcut) @@ -287,7 +302,9 @@ def create(self, remove=False): if self.shortcut.get('quicklaunch') and 'quicklaunch' in self.menu.dir: dst_dirs.append(self.menu.dir['quicklaunch']) - name_suffix = " ({})".format(self.menu.dir['env_name']) if self.menu.dir['env_name'] else "" + name_suffix = ( + " ({})".format(self.menu.dir['env_name']) if self.menu.dir['env_name'] else "" + ) for dst_dir in dst_dirs: name = substitute_env_variables(self.shortcut['name'], self.menu.dir) dst = join(dst_dir, name + name_suffix + '.lnk') diff --git a/menuinst/_schema.py b/menuinst/_schema.py index 74f596b3..befa9468 100644 --- a/menuinst/_schema.py +++ b/menuinst/_schema.py @@ -2,6 +2,8 @@ Generate JSON schemas from pydantic models """ +# flake8: noqa + import json from logging import getLogger from pathlib import Path @@ -252,7 +254,9 @@ class CFBundleDocumentTypesModel(BaseModel): .. entitlements: https://developer.apple.com/documentation/bundleresources/entitlements """ - link_in_bundle: Optional[Dict[constr(min_length=1), constr(regex=r"^(?!\/)(?!\.\./).*")]] = None + link_in_bundle: Optional[ + Dict[constr(min_length=1), constr(regex=r"^(?!\/)(?!\.\./).*")] + ] = None """ Paths that should be symlinked into the shortcut app bundle. It takes a mapping of source to destination paths. Destination paths must be @@ -346,10 +350,7 @@ def dump_schema_to_json(write=True): def dump_default_to_json(write=True): here = Path(__file__).parent default_item = MenuItem( - name="REQUIRED", - description="REQUIRED", - command=["REQUIRED"], - platforms={} + name="REQUIRED", description="REQUIRED", command=["REQUIRED"], platforms={} ).dict() default_item["platforms"] = { "win": Windows().dict(), @@ -362,7 +363,7 @@ def dump_default_to_json(write=True): **{ "$id": "https://schemas.conda.io/menuinst-1.schema.json", "$schema": "https://json-schema.org/draft-07/schema", - } + }, ).dict() for platform_value in default["menu_items"][0]["platforms"].values(): for key in list(platform_value.keys()): diff --git a/menuinst/platforms/base.py b/menuinst/platforms/base.py index f67cfbbf..4538b3f4 100644 --- a/menuinst/platforms/base.py +++ b/menuinst/platforms/base.py @@ -147,11 +147,15 @@ def placeholders(self) -> Dict[str, str]: "MENU_ITEM_LOCATION": str(self.location), } - def render_key(self, key: str, slug: bool = False, extra: Optional[Dict[str, str]] = None) -> Any: + def render_key( + self, key: str, slug: bool = False, extra: Optional[Dict[str, str]] = None + ) -> Any: value = self.metadata.get(key) return self.render(value, slug=slug, extra=extra) - def render(self, value: Any, slug: bool = False, extra: Optional[Dict[str, str]] = None) -> Any: + def render( + self, value: Any, slug: bool = False, extra: Optional[Dict[str, str]] = None + ) -> Any: if value in (None, True, False): return value kwargs = { @@ -161,10 +165,7 @@ def render(self, value: Any, slug: bool = False, extra: Optional[Dict[str, str]] if isinstance(value, str): return self.menu.render(value, **kwargs) if hasattr(value, "items"): - return { - key: self.menu.render(value, **kwargs) - for key, value in value.items() - } + return {key: self.menu.render(value, **kwargs) for key, value in value.items()} return [self.menu.render(item, **kwargs) for item in value] def _precreate(self): diff --git a/menuinst/platforms/linux.py b/menuinst/platforms/linux.py index 66f337e4..067ca3fc 100644 --- a/menuinst/platforms/linux.py +++ b/menuinst/platforms/linux.py @@ -21,13 +21,15 @@ class LinuxMenu(Menu): menuinst will populate the relevant XML config and create a .directory entry """ + _system_config_directory = Path("/etc/xdg/") - _system_data_directory = Path("/usr/share") + _system_data_directory = Path("/usr/share") + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if self.mode == "system": self.config_directory = self._system_config_directory - self.data_directory =self._system_data_directory + self.data_directory = self._system_data_directory else: self.config_directory = Path( os.environ.get("XDG_CONFIG_HOME", "~/.config") @@ -37,7 +39,9 @@ def __init__(self, *args, **kwargs): ).expanduser() # XML Config paths - self.system_menu_config_location = self._system_config_directory / "menus" / "applications.menu" + self.system_menu_config_location = ( + self._system_config_directory / "menus" / "applications.menu" + ) self.menu_config_location = self.config_directory / "menus" / "applications.menu" # .desktop / .directory paths self.directory_entry_location = ( diff --git a/menuinst/platforms/osx.py b/menuinst/platforms/osx.py index 89d8e3c5..44ac2992 100644 --- a/menuinst/platforms/osx.py +++ b/menuinst/platforms/osx.py @@ -226,6 +226,6 @@ def _sign_with_entitlements(self): "--deep", "--entitlements", entitlements_path, - self.location + self.location, ] ) diff --git a/menuinst/platforms/win.py b/menuinst/platforms/win.py index 963970c6..0ba1ef36 100644 --- a/menuinst/platforms/win.py +++ b/menuinst/platforms/win.py @@ -91,7 +91,9 @@ def _conda_exe_path_candidates(self): self.base_prefix / "bin" / "micromamba.exe", ) - def render(self, value: Any, slug: bool = False, extra: Optional[Dict[str, str]] = None) -> Any: + def render( + self, value: Any, slug: bool = False, extra: Optional[Dict[str, str]] = None + ) -> Any: """ We extend the render method here to replace forward slashes with backslashes. We ONLY do it if the string does not start with /, because it might @@ -367,7 +369,7 @@ def _unregister_file_extensions(self): unregister_file_extension(ext, identifier, mode=self.parent.mode) def _register_url_protocols(self): - "See https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85)" + "See https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85)" # noqa protocols = self.metadata["url_protocols"] if not protocols: return diff --git a/menuinst/platforms/win_utils/knownfolders.py b/menuinst/platforms/win_utils/knownfolders.py index 2ece2028..3b1782a2 100644 --- a/menuinst/platforms/win_utils/knownfolders.py +++ b/menuinst/platforms/win_utils/knownfolders.py @@ -34,127 +34,133 @@ logger = getLogger(__name__) -class GUID(ctypes.Structure): # [1] +class GUID(ctypes.Structure): # [1] _fields_ = [ ("Data1", wintypes.DWORD), ("Data2", wintypes.WORD), ("Data3", wintypes.WORD), - ("Data4", wintypes.BYTE * 8) + ("Data4", wintypes.BYTE * 8), ] def __init__(self, uuid_): ctypes.Structure.__init__(self) self.Data1, self.Data2, self.Data3, self.Data4[0], self.Data4[1], rest = uuid_.fields for i in range(2, 8): - self.Data4[i] = rest>>(8 - i - 1)*8 & 0xff - -class FOLDERID: # [2] - AccountPictures = UUID('{008ca0b1-55b4-4c56-b8a8-4de4b299d3be}') - AdminTools = UUID('{724EF170-A42D-4FEF-9F26-B60E846FBA4F}') - ApplicationShortcuts = UUID('{A3918781-E5F2-4890-B3D9-A7E54332328C}') - CameraRoll = UUID('{AB5FB87B-7CE2-4F83-915D-550846C9537B}') - CDBurning = UUID('{9E52AB10-F80D-49DF-ACB8-4330F5687855}') - CommonAdminTools = UUID('{D0384E7D-BAC3-4797-8F14-CBA229B392B5}') - CommonOEMLinks = UUID('{C1BAE2D0-10DF-4334-BEDD-7AA20B227A9D}') - CommonPrograms = UUID('{0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8}') - CommonStartMenu = UUID('{A4115719-D62E-491D-AA7C-E74B8BE3B067}') - CommonStartup = UUID('{82A5EA35-D9CD-47C5-9629-E15D2F714E6E}') - CommonTemplates = UUID('{B94237E7-57AC-4347-9151-B08C6C32D1F7}') - Contacts = UUID('{56784854-C6CB-462b-8169-88E350ACB882}') - Cookies = UUID('{2B0F765D-C0E9-4171-908E-08A611B84FF6}') - Desktop = UUID('{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}') - DeviceMetadataStore = UUID('{5CE4A5E9-E4EB-479D-B89F-130C02886155}') - Documents = UUID('{FDD39AD0-238F-46AF-ADB4-6C85480369C7}') - DocumentsLibrary = UUID('{7B0DB17D-9CD2-4A93-9733-46CC89022E7C}') - Downloads = UUID('{374DE290-123F-4565-9164-39C4925E467B}') - Favorites = UUID('{1777F761-68AD-4D8A-87BD-30B759FA33DD}') - Fonts = UUID('{FD228CB7-AE11-4AE3-864C-16F3910AB8FE}') - GameTasks = UUID('{054FAE61-4DD8-4787-80B6-090220C4B700}') - History = UUID('{D9DC8A3B-B784-432E-A781-5A1130A75963}') - ImplicitAppShortcuts = UUID('{BCB5256F-79F6-4CEE-B725-DC34E402FD46}') - InternetCache = UUID('{352481E8-33BE-4251-BA85-6007CAEDCF9D}') - Libraries = UUID('{1B3EA5DC-B587-4786-B4EF-BD1DC332AEAE}') - Links = UUID('{bfb9d5e0-c6a9-404c-b2b2-ae6db6af4968}') - LocalAppData = UUID('{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}') - LocalAppDataLow = UUID('{A520A1A4-1780-4FF6-BD18-167343C5AF16}') - LocalizedResourcesDir = UUID('{2A00375E-224C-49DE-B8D1-440DF7EF3DDC}') - Music = UUID('{4BD8D571-6D19-48D3-BE97-422220080E43}') - MusicLibrary = UUID('{2112AB0A-C86A-4FFE-A368-0DE96E47012E}') - NetHood = UUID('{C5ABBF53-E17F-4121-8900-86626FC2C973}') - OriginalImages = UUID('{2C36C0AA-5812-4b87-BFD0-4CD0DFB19B39}') - PhotoAlbums = UUID('{69D2CF90-FC33-4FB7-9A0C-EBB0F0FCB43C}') - PicturesLibrary = UUID('{A990AE9F-A03B-4E80-94BC-9912D7504104}') - Pictures = UUID('{33E28130-4E1E-4676-835A-98395C3BC3BB}') - Playlists = UUID('{DE92C1C7-837F-4F69-A3BB-86E631204A23}') - PrintHood = UUID('{9274BD8D-CFD1-41C3-B35E-B13F55A758F4}') - Profile = UUID('{5E6C858F-0E22-4760-9AFE-EA3317B67173}') - ProgramData = UUID('{62AB5D82-FDC1-4DC3-A9DD-070D1D495D97}') - ProgramFiles = UUID('{905e63b6-c1bf-494e-b29c-65b732d3d21a}') - ProgramFilesX64 = UUID('{6D809377-6AF0-444b-8957-A3773F02200E}') - ProgramFilesX86 = UUID('{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}') - ProgramFilesCommon = UUID('{F7F1ED05-9F6D-47A2-AAAE-29D317C6F066}') - ProgramFilesCommonX64 = UUID('{6365D5A7-0F0D-45E5-87F6-0DA56B6A4F7D}') - ProgramFilesCommonX86 = UUID('{DE974D24-D9C6-4D3E-BF91-F4455120B917}') - Programs = UUID('{A77F5D77-2E2B-44C3-A6A2-ABA601054A51}') - Public = UUID('{DFDF76A2-C82A-4D63-906A-5644AC457385}') - PublicDesktop = UUID('{C4AA340D-F20F-4863-AFEF-F87EF2E6BA25}') - PublicDocuments = UUID('{ED4824AF-DCE4-45A8-81E2-FC7965083634}') - PublicDownloads = UUID('{3D644C9B-1FB8-4f30-9B45-F670235F79C0}') - PublicGameTasks = UUID('{DEBF2536-E1A8-4c59-B6A2-414586476AEA}') - PublicLibraries = UUID('{48DAF80B-E6CF-4F4E-B800-0E69D84EE384}') - PublicMusic = UUID('{3214FAB5-9757-4298-BB61-92A9DEAA44FF}') - PublicPictures = UUID('{B6EBFB86-6907-413C-9AF7-4FC2ABF07CC5}') - PublicRingtones = UUID('{E555AB60-153B-4D17-9F04-A5FE99FC15EC}') - PublicUserTiles = UUID('{0482af6c-08f1-4c34-8c90-e17ec98b1e17}') - PublicVideos = UUID('{2400183A-6185-49FB-A2D8-4A392A602BA3}') - QuickLaunch = UUID('{52a4f021-7b75-48a9-9f6b-4b87a210bc8f}') - Recent = UUID('{AE50C081-EBD2-438A-8655-8A092E34987A}') - RecordedTVLibrary = UUID('{1A6FDBA2-F42D-4358-A798-B74D745926C5}') - ResourceDir = UUID('{8AD10C31-2ADB-4296-A8F7-E4701232C972}') - Ringtones = UUID('{C870044B-F49E-4126-A9C3-B52A1FF411E8}') - RoamingAppData = UUID('{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}') - RoamedTileImages = UUID('{AAA8D5A5-F1D6-4259-BAA8-78E7EF60835E}') - RoamingTiles = UUID('{00BCFC5A-ED94-4e48-96A1-3F6217F21990}') - SampleMusic = UUID('{B250C668-F57D-4EE1-A63C-290EE7D1AA1F}') - SamplePictures = UUID('{C4900540-2379-4C75-844B-64E6FAF8716B}') - SamplePlaylists = UUID('{15CA69B3-30EE-49C1-ACE1-6B5EC372AFB5}') - SampleVideos = UUID('{859EAD94-2E85-48AD-A71A-0969CB56A6CD}') - SavedGames = UUID('{4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4}') - SavedSearches = UUID('{7d1d3a04-debb-4115-95cf-2f29da2920da}') - Screenshots = UUID('{b7bede81-df94-4682-a7d8-57a52620b86f}') - SearchHistory = UUID('{0D4C3DB6-03A3-462F-A0E6-08924C41B5D4}') - SearchTemplates = UUID('{7E636BFE-DFA9-4D5E-B456-D7B39851D8A9}') - SendTo = UUID('{8983036C-27C0-404B-8F08-102D10DCFD74}') - SidebarDefaultParts = UUID('{7B396E54-9EC5-4300-BE0A-2482EBAE1A26}') - SidebarParts = UUID('{A75D362E-50FC-4fb7-AC2C-A8BEAA314493}') - SkyDrive = UUID('{A52BBA46-E9E1-435f-B3D9-28DAA648C0F6}') - SkyDriveCameraRoll = UUID('{767E6811-49CB-4273-87C2-20F355E1085B}') - SkyDriveDocuments = UUID('{24D89E24-2F19-4534-9DDE-6A6671FBB8FE}') - SkyDrivePictures = UUID('{339719B5-8C47-4894-94C2-D8F77ADD44A6}') - StartMenu = UUID('{625B53C3-AB48-4EC1-BA1F-A1EF4146FC19}') - Startup = UUID('{B97D20BB-F46A-4C97-BA10-5E3608430854}') - System = UUID('{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}') - SystemX86 = UUID('{D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27}') - Templates = UUID('{A63293E8-664E-48DB-A079-DF759E0509F7}') - UserPinned = UUID('{9E3995AB-1F9C-4F13-B827-48B24B6C7174}') - UserProfiles = UUID('{0762D272-C50A-4BB0-A382-697DCD729B80}') - UserProgramFiles = UUID('{5CD7AEE2-2219-4A67-B85D-6C9CE15660CB}') - UserProgramFilesCommon = UUID('{BCBD3057-CA5C-4622-B42D-BC56DB0AE516}') - Videos = UUID('{18989B1D-99B5-455B-841C-AB7C74E4DDFC}') - VideosLibrary = UUID('{491E922F-5643-4AF4-A7EB-4E7A138D8174}') - Windows = UUID('{F38BF404-1D43-42F2-9305-67DE0B28FC23}') - -class UserHandle: # [3] + self.Data4[i] = rest >> (8 - i - 1) * 8 & 0xFF + + +class FOLDERID: # [2] + AccountPictures = UUID('{008ca0b1-55b4-4c56-b8a8-4de4b299d3be}') + AdminTools = UUID('{724EF170-A42D-4FEF-9F26-B60E846FBA4F}') + ApplicationShortcuts = UUID('{A3918781-E5F2-4890-B3D9-A7E54332328C}') + CameraRoll = UUID('{AB5FB87B-7CE2-4F83-915D-550846C9537B}') + CDBurning = UUID('{9E52AB10-F80D-49DF-ACB8-4330F5687855}') + CommonAdminTools = UUID('{D0384E7D-BAC3-4797-8F14-CBA229B392B5}') + CommonOEMLinks = UUID('{C1BAE2D0-10DF-4334-BEDD-7AA20B227A9D}') + CommonPrograms = UUID('{0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8}') + CommonStartMenu = UUID('{A4115719-D62E-491D-AA7C-E74B8BE3B067}') + CommonStartup = UUID('{82A5EA35-D9CD-47C5-9629-E15D2F714E6E}') + CommonTemplates = UUID('{B94237E7-57AC-4347-9151-B08C6C32D1F7}') + Contacts = UUID('{56784854-C6CB-462b-8169-88E350ACB882}') + Cookies = UUID('{2B0F765D-C0E9-4171-908E-08A611B84FF6}') + Desktop = UUID('{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}') + DeviceMetadataStore = UUID('{5CE4A5E9-E4EB-479D-B89F-130C02886155}') + Documents = UUID('{FDD39AD0-238F-46AF-ADB4-6C85480369C7}') + DocumentsLibrary = UUID('{7B0DB17D-9CD2-4A93-9733-46CC89022E7C}') + Downloads = UUID('{374DE290-123F-4565-9164-39C4925E467B}') + Favorites = UUID('{1777F761-68AD-4D8A-87BD-30B759FA33DD}') + Fonts = UUID('{FD228CB7-AE11-4AE3-864C-16F3910AB8FE}') + GameTasks = UUID('{054FAE61-4DD8-4787-80B6-090220C4B700}') + History = UUID('{D9DC8A3B-B784-432E-A781-5A1130A75963}') + ImplicitAppShortcuts = UUID('{BCB5256F-79F6-4CEE-B725-DC34E402FD46}') + InternetCache = UUID('{352481E8-33BE-4251-BA85-6007CAEDCF9D}') + Libraries = UUID('{1B3EA5DC-B587-4786-B4EF-BD1DC332AEAE}') + Links = UUID('{bfb9d5e0-c6a9-404c-b2b2-ae6db6af4968}') + LocalAppData = UUID('{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}') + LocalAppDataLow = UUID('{A520A1A4-1780-4FF6-BD18-167343C5AF16}') + LocalizedResourcesDir = UUID('{2A00375E-224C-49DE-B8D1-440DF7EF3DDC}') + Music = UUID('{4BD8D571-6D19-48D3-BE97-422220080E43}') + MusicLibrary = UUID('{2112AB0A-C86A-4FFE-A368-0DE96E47012E}') + NetHood = UUID('{C5ABBF53-E17F-4121-8900-86626FC2C973}') + OriginalImages = UUID('{2C36C0AA-5812-4b87-BFD0-4CD0DFB19B39}') + PhotoAlbums = UUID('{69D2CF90-FC33-4FB7-9A0C-EBB0F0FCB43C}') + PicturesLibrary = UUID('{A990AE9F-A03B-4E80-94BC-9912D7504104}') + Pictures = UUID('{33E28130-4E1E-4676-835A-98395C3BC3BB}') + Playlists = UUID('{DE92C1C7-837F-4F69-A3BB-86E631204A23}') + PrintHood = UUID('{9274BD8D-CFD1-41C3-B35E-B13F55A758F4}') + Profile = UUID('{5E6C858F-0E22-4760-9AFE-EA3317B67173}') + ProgramData = UUID('{62AB5D82-FDC1-4DC3-A9DD-070D1D495D97}') + ProgramFiles = UUID('{905e63b6-c1bf-494e-b29c-65b732d3d21a}') + ProgramFilesX64 = UUID('{6D809377-6AF0-444b-8957-A3773F02200E}') + ProgramFilesX86 = UUID('{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}') + ProgramFilesCommon = UUID('{F7F1ED05-9F6D-47A2-AAAE-29D317C6F066}') + ProgramFilesCommonX64 = UUID('{6365D5A7-0F0D-45E5-87F6-0DA56B6A4F7D}') + ProgramFilesCommonX86 = UUID('{DE974D24-D9C6-4D3E-BF91-F4455120B917}') + Programs = UUID('{A77F5D77-2E2B-44C3-A6A2-ABA601054A51}') + Public = UUID('{DFDF76A2-C82A-4D63-906A-5644AC457385}') + PublicDesktop = UUID('{C4AA340D-F20F-4863-AFEF-F87EF2E6BA25}') + PublicDocuments = UUID('{ED4824AF-DCE4-45A8-81E2-FC7965083634}') + PublicDownloads = UUID('{3D644C9B-1FB8-4f30-9B45-F670235F79C0}') + PublicGameTasks = UUID('{DEBF2536-E1A8-4c59-B6A2-414586476AEA}') + PublicLibraries = UUID('{48DAF80B-E6CF-4F4E-B800-0E69D84EE384}') + PublicMusic = UUID('{3214FAB5-9757-4298-BB61-92A9DEAA44FF}') + PublicPictures = UUID('{B6EBFB86-6907-413C-9AF7-4FC2ABF07CC5}') + PublicRingtones = UUID('{E555AB60-153B-4D17-9F04-A5FE99FC15EC}') + PublicUserTiles = UUID('{0482af6c-08f1-4c34-8c90-e17ec98b1e17}') + PublicVideos = UUID('{2400183A-6185-49FB-A2D8-4A392A602BA3}') + QuickLaunch = UUID('{52a4f021-7b75-48a9-9f6b-4b87a210bc8f}') + Recent = UUID('{AE50C081-EBD2-438A-8655-8A092E34987A}') + RecordedTVLibrary = UUID('{1A6FDBA2-F42D-4358-A798-B74D745926C5}') + ResourceDir = UUID('{8AD10C31-2ADB-4296-A8F7-E4701232C972}') + Ringtones = UUID('{C870044B-F49E-4126-A9C3-B52A1FF411E8}') + RoamingAppData = UUID('{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}') + RoamedTileImages = UUID('{AAA8D5A5-F1D6-4259-BAA8-78E7EF60835E}') + RoamingTiles = UUID('{00BCFC5A-ED94-4e48-96A1-3F6217F21990}') + SampleMusic = UUID('{B250C668-F57D-4EE1-A63C-290EE7D1AA1F}') + SamplePictures = UUID('{C4900540-2379-4C75-844B-64E6FAF8716B}') + SamplePlaylists = UUID('{15CA69B3-30EE-49C1-ACE1-6B5EC372AFB5}') + SampleVideos = UUID('{859EAD94-2E85-48AD-A71A-0969CB56A6CD}') + SavedGames = UUID('{4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4}') + SavedSearches = UUID('{7d1d3a04-debb-4115-95cf-2f29da2920da}') + Screenshots = UUID('{b7bede81-df94-4682-a7d8-57a52620b86f}') + SearchHistory = UUID('{0D4C3DB6-03A3-462F-A0E6-08924C41B5D4}') + SearchTemplates = UUID('{7E636BFE-DFA9-4D5E-B456-D7B39851D8A9}') + SendTo = UUID('{8983036C-27C0-404B-8F08-102D10DCFD74}') + SidebarDefaultParts = UUID('{7B396E54-9EC5-4300-BE0A-2482EBAE1A26}') + SidebarParts = UUID('{A75D362E-50FC-4fb7-AC2C-A8BEAA314493}') + SkyDrive = UUID('{A52BBA46-E9E1-435f-B3D9-28DAA648C0F6}') + SkyDriveCameraRoll = UUID('{767E6811-49CB-4273-87C2-20F355E1085B}') + SkyDriveDocuments = UUID('{24D89E24-2F19-4534-9DDE-6A6671FBB8FE}') + SkyDrivePictures = UUID('{339719B5-8C47-4894-94C2-D8F77ADD44A6}') + StartMenu = UUID('{625B53C3-AB48-4EC1-BA1F-A1EF4146FC19}') + Startup = UUID('{B97D20BB-F46A-4C97-BA10-5E3608430854}') + System = UUID('{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}') + SystemX86 = UUID('{D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27}') + Templates = UUID('{A63293E8-664E-48DB-A079-DF759E0509F7}') + UserPinned = UUID('{9E3995AB-1F9C-4F13-B827-48B24B6C7174}') + UserProfiles = UUID('{0762D272-C50A-4BB0-A382-697DCD729B80}') + UserProgramFiles = UUID('{5CD7AEE2-2219-4A67-B85D-6C9CE15660CB}') + UserProgramFilesCommon = UUID('{BCBD3057-CA5C-4622-B42D-BC56DB0AE516}') + Videos = UUID('{18989B1D-99B5-455B-841C-AB7C74E4DDFC}') + VideosLibrary = UUID('{491E922F-5643-4AF4-A7EB-4E7A138D8174}') + Windows = UUID('{F38BF404-1D43-42F2-9305-67DE0B28FC23}') + + +class UserHandle: # [3] current = wintypes.HANDLE(0) - common = wintypes.HANDLE(-1) + common = wintypes.HANDLE(-1) -_CoTaskMemFree = windll.ole32.CoTaskMemFree # [4] -_CoTaskMemFree.restype= None + +_CoTaskMemFree = windll.ole32.CoTaskMemFree # [4] +_CoTaskMemFree.restype = None _CoTaskMemFree.argtypes = [ctypes.c_void_p] -_SHGetKnownFolderPath = windll.shell32.SHGetKnownFolderPath # [5] [3] +_SHGetKnownFolderPath = windll.shell32.SHGetKnownFolderPath # [5] [3] _SHGetKnownFolderPath.argtypes = [ - ctypes.POINTER(GUID), wintypes.DWORD, wintypes.HANDLE, ctypes.POINTER(ctypes.c_wchar_p) + ctypes.POINTER(GUID), + wintypes.DWORD, + wintypes.HANDLE, + ctypes.POINTER(ctypes.c_wchar_p), ] ''' @@ -178,8 +184,14 @@ class UserHandle: # [3] pass ''' -class PathNotFoundException(Exception): pass -class PathNotVerifiableException(Exception): pass + +class PathNotFoundException(Exception): + pass + + +class PathNotVerifiableException(Exception): + pass + def get_path(folderid, user_handle=UserHandle.common): fid = GUID(folderid) @@ -187,7 +199,9 @@ def get_path(folderid, user_handle=UserHandle.common): pPathUnverified = ctypes.c_wchar_p() S_OK = 0 KF_FLAG_DONT_VERIFY = 0x00004000 - result = _SHGetKnownFolderPath(ctypes.byref(fid), KF_FLAG_DONT_VERIFY, user_handle, ctypes.byref(pPathUnverified)) + result = _SHGetKnownFolderPath( + ctypes.byref(fid), KF_FLAG_DONT_VERIFY, user_handle, ctypes.byref(pPathUnverified) + ) exception = None if result != S_OK: exception = PathNotFoundException() @@ -211,6 +225,7 @@ def get_folder_path(folder_id, user=None): # New users created on the machine have their folders created by copying those of 'Default'. return get_path(folder_id, user) + # [1] http://msdn.microsoft.com/en-us/library/windows/desktop/aa373931.aspx # [2] http://msdn.microsoft.com/en-us/library/windows/desktop/dd378457.aspx # [3] http://msdn.microsoft.com/en-us/library/windows/desktop/bb762188.aspx @@ -218,7 +233,8 @@ def get_folder_path(folder_id, user=None): # [5] http://www.themacaque.com/?p=954 # jaimergp: The code below was copied from menuinst.win32, 1.4.19 -# module: menuinst/win32.py - https://github.com/conda/menuinst/blob/e17afafd/menuinst/win32.py#L40-L102 +# module: menuinst/win32.py - +# https://github.com/conda/menuinst/blob/e17afafd/menuinst/win32.py#L40-L102 # ---- # When running as 'nt authority/system' as sometimes people do via SCCM, # various folders do not exist, such as QuickLaunch. This doesn't matter @@ -229,32 +245,39 @@ def get_folder_path(folder_id, user=None): # exist, in which case, the 2nd entry of the value tuple is a sub-class of # Exception. -dirs_src = {"system": { "desktop": get_folder_path(FOLDERID.PublicDesktop), - "start": get_folder_path(FOLDERID.CommonPrograms), - "documents": get_folder_path(FOLDERID.PublicDocuments), - "profile": get_folder_path(FOLDERID.Profile)}, - - "user": { "desktop": get_folder_path(FOLDERID.Desktop), - "start": get_folder_path(FOLDERID.Programs), - "quicklaunch": get_folder_path(FOLDERID.QuickLaunch), - "documents": get_folder_path(FOLDERID.Documents), - "profile": get_folder_path(FOLDERID.Profile)}} +dirs_src = { + "system": { + "desktop": get_folder_path(FOLDERID.PublicDesktop), + "start": get_folder_path(FOLDERID.CommonPrograms), + "documents": get_folder_path(FOLDERID.PublicDocuments), + "profile": get_folder_path(FOLDERID.Profile), + }, + "user": { + "desktop": get_folder_path(FOLDERID.Desktop), + "start": get_folder_path(FOLDERID.Programs), + "quicklaunch": get_folder_path(FOLDERID.QuickLaunch), + "documents": get_folder_path(FOLDERID.Documents), + "profile": get_folder_path(FOLDERID.Profile), + }, +} def folder_path(preferred_mode, check_other_mode, key): - ''' This function implements all heuristics and workarounds for messed up - KNOWNFOLDERID registry values. It's also verbose (OutputDebugStringW) - about whether fallbacks worked or whether they would have worked if - check_other_mode had been allowed. + '''This function implements all heuristics and workarounds for messed up + KNOWNFOLDERID registry values. It's also verbose (OutputDebugStringW) + about whether fallbacks worked or whether they would have worked if + check_other_mode had been allowed. ''' other_mode = 'system' if preferred_mode == 'user' else 'user' path, exception = dirs_src[preferred_mode][key] if not exception: return path - logger.info("WARNING: menuinst key: '%s'\n" - " path: '%s'\n" - " .. excepted with: '%s' in knownfolders.py, implementing workarounds .." - % (key, path, type(exception).__name__)) + logger.info( + "WARNING: menuinst key: '%s'\n" + " path: '%s'\n" + " .. excepted with: '%s' in knownfolders.py, implementing workarounds .." + % (key, path, type(exception).__name__) + ) # Since I have seen 'user', 'documents' set as '\\vmware-host\Shared Folders\Documents' # when there's no such server, we check 'user', 'profile' + '\Documents' before maybe # trying the other_mode (though I have chickened out on that idea). @@ -269,17 +292,24 @@ def folder_path(preferred_mode, check_other_mode, key): # Do not fall back to something we cannot write to. if exception: if check_other_mode: - logger.info(" .. despite 'check_other_mode'\n" - " and 'other_mode' 'path' of '%s'\n" - " it excepted with: '%s' in knownfolders.py" % (path, - type(exception).__name__)) + logger.info( + " .. despite 'check_other_mode'\n" + " and 'other_mode' 'path' of '%s'\n" + " it excepted with: '%s' in knownfolders.py" + % (path, type(exception).__name__) + ) else: - logger.info(" .. 'check_other_mode' is False,\n" - " and 'other_mode' 'path' is '%s'\n" - " but it excepted anyway with: '%s' in knownfolders.py" % (path, type(exception).__name__)) + logger.info( + " .. 'check_other_mode' is False,\n" + " and 'other_mode' 'path' is '%s'\n" + " but it excepted anyway with: '%s' in knownfolders.py" + % (path, type(exception).__name__) + ) return None if not check_other_mode: - logger.info(" .. due to lack of 'check_other_mode' not picking\n" - " non-excepting path of '%s'\n in knownfolders.py" % (path)) + logger.info( + " .. due to lack of 'check_other_mode' not picking\n" + " non-excepting path of '%s'\n in knownfolders.py" % (path) + ) return None return path diff --git a/menuinst/platforms/win_utils/registry.py b/menuinst/platforms/win_utils/registry.py index ad1fff24..2fd93daf 100644 --- a/menuinst/platforms/win_utils/registry.py +++ b/menuinst/platforms/win_utils/registry.py @@ -53,7 +53,7 @@ def register_file_extension(extension, identifier, command, icon=None, mode="use winreg.HKEY_LOCAL_MACHINE # HKLM if mode == "system" else winreg.HKEY_CURRENT_USER, # HKCU - r"Software\Classes" + r"Software\Classes", ) as key: # First we associate an extension with a handler winreg.SetValueEx( @@ -61,7 +61,7 @@ def register_file_extension(extension, identifier, command, icon=None, mode="use identifier, 0, winreg.REG_SZ, - "", # presence of the key is enough + "", # presence of the key is enough ) log.debug("Created registry entry for extension '%s'", extension) @@ -86,15 +86,23 @@ def register_file_extension(extension, identifier, command, icon=None, mode="use def unregister_file_extension(extension, identifier, mode="user"): - root, root_str = (winreg.HKEY_LOCAL_MACHINE, "HKLM") if mode == "system" else (winreg.HKEY_CURRENT_USER, "HKCU") + root, root_str = ( + (winreg.HKEY_LOCAL_MACHINE, "HKLM") + if mode == "system" + else (winreg.HKEY_CURRENT_USER, "HKCU") + ) _reg_exe("delete", fr"{root_str}\Software\Classes\{identifier}") try: - with winreg.OpenKey(root, fr"Software\Classes\{extension}\OpenWithProgids", 0, winreg.KEY_ALL_ACCESS) as key: + with winreg.OpenKey( + root, fr"Software\Classes\{extension}\OpenWithProgids", 0, winreg.KEY_ALL_ACCESS + ) as key: try: winreg.QueryValueEx(key, identifier) except FileNotFoundError: - log.debug("Handler '%s' is not associated with extension '%s'", identifier, extension) + log.debug( + "Handler '%s' is not associated with extension '%s'", identifier, extension + ) else: winreg.DeleteValue(key, identifier) except Exception as exc: @@ -129,7 +137,7 @@ def unregister_url_protocol(protocol, identifier=None, mode="user"): key_str = fr"HKCU\Software\Classes\{protocol}" try: with winreg.OpenKey(*key_tuple) as key: - value, _ = winreg.QueryValueEx(key, "_menuinst") + value, _ = winreg.QueryValueEx(key, "_menuinst") delete = identifier is None or value == identifier except OSError as exc: log.exception("Could not check key %s for deletion", protocol, exc_info=exc) diff --git a/menuinst/platforms/win_utils/win_elevate.py b/menuinst/platforms/win_utils/win_elevate.py index 2aea75cc..2cf03abe 100755 --- a/menuinst/platforms/win_utils/win_elevate.py +++ b/menuinst/platforms/win_utils/win_elevate.py @@ -16,11 +16,6 @@ from enum import IntEnum from subprocess import list2cmdline -if sys.version_info < (3,): - text_type = basestring -else: - text_type = str - def isUserAdmin(): if os.name != 'nt': @@ -31,7 +26,7 @@ def isUserAdmin(): # Requires Windows XP SP2 or higher! try: return ctypes.windll.shell32.IsUserAnAdmin() - except: + except: # noqa traceback.print_exc() print("Admin check failed, assuming not an admin.") return False @@ -61,6 +56,7 @@ def ensure_binary(value): windll, ) from ctypes.wintypes import BOOL, DWORD, HANDLE, HINSTANCE, HKEY, HWND + PHANDLE = POINTER(HANDLE) PDWORD = POINTER(DWORD) SEE_MASK_NOCLOSEPROCESS = 0x00000040 @@ -76,8 +72,8 @@ def ensure_binary(value): class ShellExecuteInfo(Structure): """ -https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-shellexecuteexa -https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/ns-shellapi-_shellexecuteinfoa + https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-shellexecuteexa + https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/ns-shellapi-_shellexecuteinfoa """ _fields_ = [ @@ -95,7 +91,7 @@ class ShellExecuteInfo(Structure): ('hKeyClass', HKEY), ('dwHotKey', DWORD), ('hIcon', HANDLE), - ('hProcess', HANDLE) + ('hProcess', HANDLE), ] def __init__(self, **kwargs): @@ -108,7 +104,7 @@ def __init__(self, **kwargs): PShellExecuteInfo = POINTER(ShellExecuteInfo) ShellExecuteEx = windll.Shell32.ShellExecuteExA - ShellExecuteEx.argtypes = (PShellExecuteInfo, ) + ShellExecuteEx.argtypes = (PShellExecuteInfo,) ShellExecuteEx.restype = BOOL @@ -135,7 +131,7 @@ def runAsAdmin(cmdLine=None, wait=True): if cmdLine is None: cmdLine = [python_exe] + sys.argv - elif not hasattr(cmdLine, "__iter__") or isinstance(cmdLine, text_type): + elif not hasattr(cmdLine, "__iter__") or isinstance(cmdLine, str): raise ValueError("cmdLine is not a sequence.") cmd = '"%s"' % (cmdLine[0],) @@ -148,13 +144,15 @@ def runAsAdmin(cmdLine=None, wait=True): # the more complex ShellExecuteEx() must be used. # procHandle = win32api.ShellExecute(0, lpVerb, cmd, params, cmdDir, showCmd) - execute_info = ShellExecuteInfo(nShow=showCmd, - fMask=SEE_MASK_NOCLOSEPROCESS, - lpVerb=lpVerb, - lpFile=cmd, - lpParameters=params, - hwnd=None, - lpDirectory=None) + execute_info = ShellExecuteInfo( + nShow=showCmd, + fMask=SEE_MASK_NOCLOSEPROCESS, + lpVerb=lpVerb, + lpFile=cmd, + lpParameters=params, + hwnd=None, + lpDirectory=None, + ) successful = ShellExecuteEx(byref(execute_info)) diff --git a/menuinst/utils.py b/menuinst/utils.py index ecfd7dd8..288d8523 100644 --- a/menuinst/utils.py +++ b/menuinst/utils.py @@ -169,7 +169,7 @@ def quote_string(cls, s: Sequence[str]): return s @classmethod - def ensure_pad(cls, name: str, pad: str ="_"): + def ensure_pad(cls, name: str, pad: str = "_"): """ Examples: @@ -191,7 +191,7 @@ def quote_args(cls, args: Sequence[str]) -> Sequence[str]: @classmethod def quote_string(cls, s: str) -> str: quoted = shlex.quote(s) - if quoted.startswith("'") and not '"' in quoted: + if quoted.startswith("'") and '"' not in quoted: quoted = f'"{quoted[1:-1]}"' return quoted @@ -234,7 +234,11 @@ def deep_update(mapping: Mapping, *updating_mappings: Iterable[Mapping]) -> Mapp updated_mapping = mapping.copy() for updating_mapping in updating_mappings: for k, v in updating_mapping.items(): - if k in updated_mapping and isinstance(updated_mapping[k], dict) and isinstance(v, dict): + if ( + k in updated_mapping + and isinstance(updated_mapping[k], dict) + and isinstance(v, dict) + ): updated_mapping[k] = deep_update(updated_mapping[k], v) else: updated_mapping[k] = v @@ -282,12 +286,12 @@ def python_executable(base_prefix: Optional[os.PathLike] = None) -> Sequence[str # If the base env (installation root) # ships a usable Python, use that one if base_prefix_python.is_file(): - return (str(base_prefix_python), ) + return (str(base_prefix_python),) # the base env does not have python, # use the conda-standalone wrapper return (sys.executable, "python") # in non-frozen executables: - return (sys.executable, ) + return (sys.executable,) def elevate_as_needed(func: Callable) -> Callable: @@ -355,7 +359,7 @@ def wrapper_elevate( except Exception: logger.warn( "Error occurred! Falling back to user mode. Exception:\n%s", - traceback.format_exc() + traceback.format_exc(), ) else: os.environ.pop("_MENUINST_RECURSING", None) diff --git a/news/116-pre-commit b/news/116-pre-commit index 977f1fdd..a5d1d069 100644 --- a/news/116-pre-commit +++ b/news/116-pre-commit @@ -16,4 +16,4 @@ ### Other -* Enable and apply pre-commit with isort. (#116) +* Enable and apply pre-commit with isort/flake8/black. (#116, #125) diff --git a/tests/_legacy/test_menu_creation.py b/tests/_legacy/test_menu_creation.py index 2e9875f7..7320cd82 100644 --- a/tests/_legacy/test_menu_creation.py +++ b/tests/_legacy/test_menu_creation.py @@ -10,30 +10,29 @@ def file_exist(mode, name): - file = os.path.join(dirs_src[mode]['start'][0], - 'Anaconda3 (64-bit) - Test Menu', - '%s.lnk' % name) + file = os.path.join( + dirs_src[mode]['start'][0], 'Anaconda3 (64-bit) - Test Menu', '%s.lnk' % name + ) return os.path.exists(file) menu_dir = os.path.dirname(__file__) -@pytest.mark.skipif(sys.platform != "win32", - reason="Windows-only tests") -class TestWindowsShortcuts(object): +@pytest.mark.skipif(sys.platform != "win32", reason="Windows-only tests") +class TestWindowsShortcuts(object): def test_install_folders_exist(self): for mode in ["user", "system"]: for path, _ in dirs_src[mode].values(): assert os.path.exists(path) def test_create_and_remove_shortcut(self): - nonadmin=os.path.join(sys.prefix, ".nonadmin") + nonadmin = os.path.join(sys.prefix, ".nonadmin") shortcut = os.path.join(menu_dir, "menu-windows.json") has_nonadmin = os.path.exists(nonadmin) name = 'Anaconda Prompt' for mode in ["user", "system"]: - if mode=="user": + if mode == "user": open(nonadmin, 'a').close() menuinst.install(shortcut, remove=False) assert file_exist(mode, name) @@ -45,7 +44,7 @@ def test_create_and_remove_shortcut(self): open(nonadmin, 'a').close() def test_create_shortcut_env(self): - nonadmin=os.path.join(sys.prefix, ".nonadmin") + nonadmin = os.path.join(sys.prefix, ".nonadmin") open(nonadmin, 'a').close() shortcut = os.path.join(menu_dir, "menu-windows.json") test_env_name = 'test_env' @@ -59,7 +58,7 @@ def test_create_shortcut_env(self): run_command("remove", "-n", test_env_name, "--all") def test_root_prefix(self): - nonadmin=os.path.join(sys.prefix, ".nonadmin") + nonadmin = os.path.join(sys.prefix, ".nonadmin") open(nonadmin, 'a').close() shortcut = os.path.join(menu_dir, "menu-windows.json") root_prefix = os.path.join(menu_dir, 'temp_env') diff --git a/tests/_legacy/test_win32.py b/tests/_legacy/test_win32.py index f9ec138a..67d5080b 100644 --- a/tests/_legacy/test_win32.py +++ b/tests/_legacy/test_win32.py @@ -11,8 +11,8 @@ log = getLogger(__name__) -@pytest.mark.skipif(sys.platform != "win32", - reason="Windows-only tests") + +@pytest.mark.skipif(sys.platform != "win32", reason="Windows-only tests") def test_quote_args_1(): args = [ "%windir%\\System32\\cmd.exe", @@ -23,5 +23,5 @@ def test_quote_args_1(): assert quote_args(args) == [ "\"%windir%\\System32\\cmd.exe\"", "/K", - "\"\"c:\\Users\\Francisco García Carrión Martínez\\Anaconda 3\\Scripts\\activate.bat\" \"c:\\Users\\Francisco García Carrión Martínez\\Anaconda 3\"\"", + "\"\"c:\\Users\\Francisco García Carrión Martínez\\Anaconda 3\\Scripts\\activate.bat\" \"c:\\Users\\Francisco García Carrión Martínez\\Anaconda 3\"\"", # noqa ] diff --git a/tests/conftest.py b/tests/conftest.py index c05796f7..9921cffa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -59,10 +59,12 @@ def mock_locations(monkeypatch, tmp_path): def windows_locations(preferred_mode, check_other_mode, key): return tmp_path / key + monkeypatch.setattr(knownfolders, "folder_path", windows_locations) def osx_base_location(self): return tmp_path + monkeypatch.setattr(MacOSMenuItem, "_base_location", osx_base_location) # For Linux diff --git a/tests/test_api.py b/tests/test_api.py index 35cede74..840f64b5 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -19,9 +19,11 @@ def check_output_from_shortcut(delete_files, json_path, expected_output=None): if PLATFORM == "win": with open(abs_json_path) as f: contents = f.read() - with NamedTemporaryFile(suffix=json_path, mode="w", delete=False) as tmp: + with NamedTemporaryFile(suffix=json_path, mode="w", delete=False) as tmp: win_output_file = tmp.name + ".out" - contents = contents.replace("__WIN_OUTPUT_FILE__", win_output_file.replace("\\", "\\\\")) + contents = contents.replace( + "__WIN_OUTPUT_FILE__", win_output_file.replace("\\", "\\\\") + ) tmp.write(contents) abs_json_path = tmp.name delete_files.append(abs_json_path) @@ -41,7 +43,11 @@ def check_output_from_shortcut(delete_files, json_path, expected_output=None): output = subprocess.check_output(cmd, shell=True, universal_newlines=True) elif PLATFORM == 'osx': app_location = paths[0] - executable = next(p for p in (app_location / "Contents" / "MacOS").iterdir() if not p.name.endswith('-script')) + executable = next( + p + for p in (app_location / "Contents" / "MacOS").iterdir() + if not p.name.endswith('-script') + ) process = subprocess.run([str(executable)], text=True, capture_output=True) if process.returncode: print(process.stdout, file=sys.stdout) @@ -76,17 +82,23 @@ def test_install_prefix(delete_files): def test_precommands(delete_files): - check_output_from_shortcut(delete_files, "precommands.json", expected_output="rhododendron and bees") + check_output_from_shortcut( + delete_files, "precommands.json", expected_output="rhododendron and bees" + ) @pytest.mark.skipif(PLATFORM != "osx", reason="macOS only") def test_entitlements(delete_files): - paths, _ = check_output_from_shortcut(delete_files, "entitlements.json", expected_output="entitlements") + paths, _ = check_output_from_shortcut( + delete_files, "entitlements.json", expected_output="entitlements" + ) # verify signature app_dir = next(p for p in paths if p.name.endswith('.app')) subprocess.check_call(["/usr/bin/codesign", "--verbose", "--verify", str(app_dir)]) - launcher = next(p for p in (app_dir / "Contents" / "MacOS").iterdir() if not p.name.endswith('-script')) + launcher = next( + p for p in (app_dir / "Contents" / "MacOS").iterdir() if not p.name.endswith('-script') + ) subprocess.check_call(["/usr/bin/codesign", "--verbose", "--verify", str(launcher)]) for path in app_dir.rglob("Info.plist"): @@ -107,9 +119,13 @@ def test_entitlements(delete_files): @pytest.mark.skipif(PLATFORM != "osx", reason="macOS only") def test_no_entitlements_no_signature(delete_files): - paths, _ = check_output_from_shortcut(delete_files, "sys-prefix.json", expected_output=sys.prefix) + paths, _ = check_output_from_shortcut( + delete_files, "sys-prefix.json", expected_output=sys.prefix + ) app_dir = next(p for p in paths if p.name.endswith('.app')) - launcher = next(p for p in (app_dir / "Contents" / "MacOS").iterdir() if not p.name.endswith('-script')) + launcher = next( + p for p in (app_dir / "Contents" / "MacOS").iterdir() if not p.name.endswith('-script') + ) with pytest.raises(subprocess.CalledProcessError): subprocess.check_call(["/usr/bin/codesign", "--verbose", "--verify", str(app_dir)]) with pytest.raises(subprocess.CalledProcessError): @@ -119,9 +135,7 @@ def test_no_entitlements_no_signature(delete_files): @pytest.mark.skipif(PLATFORM != "osx", reason="macOS only") def test_info_plist(delete_files): paths, _ = check_output_from_shortcut( - delete_files, - "entitlements.json", - expected_output="entitlements" + delete_files, "entitlements.json", expected_output="entitlements" ) app_dir = next(p for p in paths if p.name.endswith('.app')) diff --git a/tests/test_conda.py b/tests/test_conda.py index f062b6a1..a60ab0bc 100644 --- a/tests/test_conda.py +++ b/tests/test_conda.py @@ -30,7 +30,9 @@ def new_environment(tmpdir, *packages): try: prefix = str(tmpdir / "prefix") print("--- CREATING", prefix, "---") - stdout, stderr, retcode = run_command("create", prefix, "-y", "--offline", *[str(p) for p in packages]) + stdout, stderr, retcode = run_command( + "create", prefix, "-y", "--offline", *[str(p) for p in packages] + ) assert not retcode for stream in (stdout, stderr): if "menuinst Exception" in stream: diff --git a/tests/test_schema.py b/tests/test_schema.py index 2718cbf7..6819c1b7 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -29,7 +29,6 @@ def test_MenuItemMetadata_synced_with_OptionalMenuItemMetadata(): fields_as_optional = BasePlatformSpecific.__fields__ assert fields_as_required.keys() == fields_as_optional.keys() for (_, required), (_, optional) in zip( - sorted(fields_as_required.items()), - sorted(fields_as_optional.items()) + sorted(fields_as_required.items()), sorted(fields_as_optional.items()) ): assert required.field_info.description == optional.field_info.description diff --git a/tests/test_windows.py b/tests/test_windows.py index c31df583..c6297f32 100644 --- a/tests/test_windows.py +++ b/tests/test_windows.py @@ -1,5 +1,4 @@ import os -import sys import time from pathlib import Path @@ -27,6 +26,7 @@ def cleanup(): # be using it, but since we know these are synthetic and made up, # we try not to pollute the registry too much registry._reg_exe("delete", fr"HKCU\Software\Classes\.menuinst-{name}") + request.addfinalizer(cleanup) registry.register_file_extension( @@ -49,11 +49,7 @@ def cleanup(): if not output_file.exists(): raise AssertionError("Output file was never created") finally: - registry.unregister_file_extension( - extension=extension, - identifier=identifier, - mode="user" - ) + registry.unregister_file_extension(extension=extension, identifier=identifier, mode="user") output_file.unlink() os.startfile(input_file) # this will raise UI if not headless, ignore @@ -96,7 +92,7 @@ def test_protocols(tmp_path): registry.unregister_url_protocol( protocol=f"menuinst-{name}", identifier=f"menuinst.protocol.menuinst-{name}", - mode="user" + mode="user", ) output_file.unlink()