diff --git a/.github/workflows/static-analysis-and-test.yml b/.github/workflows/static-analysis-and-test.yml index e9d153dc..3e8c1165 100644 --- a/.github/workflows/static-analysis-and-test.yml +++ b/.github/workflows/static-analysis-and-test.yml @@ -35,6 +35,8 @@ jobs: - name: Format with black run: tox -e black + - name: Check imports with deptry + run: tox -e deptry test: # We want to run on external PRs, but not on our own internal PRs as they'll @@ -47,7 +49,7 @@ jobs: strategy: matrix: os: ['ubuntu-latest', 'windows-latest'] - python: ['3.8', '3.9', '3.10', '3.11'] + python: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] # Works around the depreciation of python 3.7 for ubuntu # https://github.com/actions/setup-python/issues/544 include: diff --git a/preditor/about_module.py b/preditor/about_module.py index 99c27975..7750631a 100644 --- a/preditor/about_module.py +++ b/preditor/about_module.py @@ -5,12 +5,10 @@ import sys import textwrap -from future.utils import with_metaclass - import preditor -class AboutModule(with_metaclass(abc.ABCMeta, object)): +class AboutModule(object, metaclass=abc.ABCMeta): """Base class for the `preditor.plug.about_module` entry point. Create a subclass of this method and expose the class object to the entry point. diff --git a/preditor/cli.py b/preditor/cli.py index 82e2e161..e2670263 100644 --- a/preditor/cli.py +++ b/preditor/cli.py @@ -1,8 +1,8 @@ from __future__ import absolute_import import logging +import shutil import sys -from distutils.spawn import find_executable import click from click.core import ParameterSource @@ -129,7 +129,7 @@ def shortcut(path, public, name, target, args, description): # Resolve the full path to the preditor exe. if target in ("preditor", "preditorw"): - target = find_executable(target) + target = shutil.which(target) parameter_source = click.get_current_context().get_parameter_source('name') if parameter_source == ParameterSource.DEFAULT: diff --git a/preditor/enum.py b/preditor/enum.py index 18a722d0..ce684e19 100644 --- a/preditor/enum.py +++ b/preditor/enum.py @@ -2,12 +2,8 @@ import abc import re -from builtins import str as text from numbers import Number -from future.utils import iteritems, with_metaclass -from past.builtins import long - # ============================================================================= # CLASSES # ============================================================================= @@ -79,7 +75,7 @@ def __str__(cls): # ============================================================================= -class Enum(with_metaclass(abc.ABCMeta, object)): +class Enum(object, metaclass=abc.ABCMeta): """A basic enumerator class. Enumerators are named values that act as identifiers. Typically, a @@ -204,9 +200,9 @@ def __eq__(self, value): return False if isinstance(value, Enum): return self.number == value.number - if isinstance(value, (int, long)): + if isinstance(value, int): return self.number == value - if isinstance(value, str) or isinstance(value, text): + if isinstance(value, str): if self._compareStr(value): return True return False @@ -296,7 +292,7 @@ def _compareStr(self, inStr): # ============================================================================= -class EnumGroup(with_metaclass(_MetaEnumGroup, object)): +class EnumGroup(object, metaclass=_MetaEnumGroup): """A container class for collecting, organizing, and accessing Enums. An EnumGroup class is a container for Enum objects. It provides @@ -428,7 +424,7 @@ def append(cls, *args, **kwargs): raise ValueError('Enums given as ordered arguments must have a label.') for e in args: setattr(cls, cls._labelToVarName(e.label), e) - for n, e in iteritems(kwargs): + for n, e in kwargs.items(): setattr(cls, n, e) # reset All and Nothing -- this is necessary so that All is regenerated # and so that Nothing is not included when finding the member Enums. @@ -585,7 +581,7 @@ def __init_enums__(cls): orderedEnums = sorted( [ (k, v) - for k, v in iteritems(cls.__dict__) + for k, v in cls.__dict__.items() if isinstance(v, Enum) and k not in ('All', 'Nothing') ], key=lambda i: i[1]._creationOrder, diff --git a/preditor/gui/console.py b/preditor/gui/console.py index 4515a981..82776daf 100644 --- a/preditor/gui/console.py +++ b/preditor/gui/console.py @@ -5,7 +5,6 @@ import sys import time import traceback -from builtins import str as text from functools import partial from typing import Optional @@ -344,7 +343,7 @@ def insertFromMimeData(self, mimeData): r'\"\<\>\?\`\-\=\[\]\\\;\'\,\.\/ \t\n]' ) ) - newText = text(txt) + newText = str(txt) for each in exp.findall(newText): newText = newText.replace(each, '?') diff --git a/preditor/gui/drag_tab_bar.py b/preditor/gui/drag_tab_bar.py index a2c4fa3e..da689d10 100644 --- a/preditor/gui/drag_tab_bar.py +++ b/preditor/gui/drag_tab_bar.py @@ -4,7 +4,6 @@ from functools import partial from pathlib import Path -import six from Qt.QtCore import QByteArray, QMimeData, QPoint, QRect, Qt from Qt.QtGui import QColor, QCursor, QDrag, QPixmap, QRegion from Qt.QtWidgets import ( @@ -513,7 +512,7 @@ def save_and_link_file(self, workbox): filename = workbox.__filename__() if filename and Path(filename).is_file(): workbox.__set_file_monitoring_enabled__(False) - directory = six.text_type(Path(filename).parent) if filename else "" + directory = str(Path(filename).parent) if filename else "" success = workbox.__save_as__(directory=directory) if not success: return diff --git a/preditor/gui/loggerwindow.py b/preditor/gui/loggerwindow.py index 93c225e8..29916712 100644 --- a/preditor/gui/loggerwindow.py +++ b/preditor/gui/loggerwindow.py @@ -9,7 +9,6 @@ import shutil import sys import warnings -from builtins import bytes from datetime import datetime, timedelta from enum import IntEnum from functools import partial @@ -25,11 +24,9 @@ QInputDialog, QMenu, QMessageBox, - QTextBrowser, QTextEdit, QToolButton, QToolTip, - QVBoxLayout, ) from .. import ( @@ -44,7 +41,7 @@ resourcePath, ) from ..delayable_engine import DelayableEngine -from ..gui import Dialog, Window, handleMenuHovered, loadUi, tab_widget_for_tab +from ..gui import Window, handleMenuHovered, loadUi, tab_widget_for_tab from ..gui.fuzzy_search.fuzzy_search import FuzzySearch from ..gui.group_tab_widget.grouped_tab_models import GroupTabListItemModel from ..logging_config import LoggingConfig @@ -302,7 +299,6 @@ def connectSignals(self): ) self.uiClearBeforeRunningCHK.toggled.connect(self.setClearBeforeRunning) self.uiEditorVerticalCHK.toggled.connect(self.adjustWorkboxOrientation) - self.uiEnvironmentVarsACT.triggered.connect(self.showEnvironmentVars) self.uiAboutPreditorACT.triggered.connect(self.show_about) # Prefs on disk @@ -2165,17 +2161,6 @@ def show_about(self): msg = about_preditor(instance=self) QMessageBox.information(self, 'About PrEditor', '
{}
'.format(msg)) - def showEnvironmentVars(self): - dlg = Dialog(self) - lyt = QVBoxLayout(dlg) - lbl = QTextBrowser(dlg) - lyt.addWidget(lbl) - dlg.setWindowTitle('Blurdev Environment Variable Help') - with open(resourcePath('environment_variables.html')) as f: - lbl.setText(f.read().replace('\n', '')) - dlg.setMinimumSize(600, 400) - dlg.show() - def showEvent(self, event): super(LoggerWindow, self).showEvent(event) self.updateIndentationsUseTabs() diff --git a/preditor/gui/ui/loggerwindow.ui b/preditor/gui/ui/loggerwindow.ui index b9229630..367d8ed5 100644 --- a/preditor/gui/ui/loggerwindow.ui +++ b/preditor/gui/ui/loggerwindow.ui @@ -955,7 +955,6 @@ Must be at least 1 - diff --git a/preditor/osystem.py b/preditor/osystem.py index d54cf69d..12ff699b 100644 --- a/preditor/osystem.py +++ b/preditor/osystem.py @@ -11,7 +11,6 @@ import os import subprocess import sys -from builtins import str as text import preditor @@ -39,10 +38,10 @@ def getPointerSize(): def pythonPath(pyw=False, architecture=None): if settings.OS_TYPE != 'Windows': return 'python' - from distutils.sysconfig import get_python_inc + import sysconfig # Unable to pull the path from the registry just use the current python path - basepath = os.path.split(get_python_inc())[0] + basepath = os.path.split(sysconfig.get_path('include'))[0] # build the path to the python executable. If requested use pythonw instead of # python return os.path.join(basepath, 'python{w}.exe'.format(w=pyw and 'w' or '')) @@ -215,6 +214,8 @@ def normalize(i): removePaths = set([normalize(x) for x in preditor.core._removeFromPATHEnv]) + # TODO: Replace blurpath code with a plugin for reverting process specific + # env var changes when launching a subprocess. # blurpath records any paths it adds to the PATH variable and other env variable # modifications it makes, revert these changes. try: @@ -239,7 +240,7 @@ def normalize(i): ] path = os.path.pathsep.join(paths) # subprocess does not accept unicode in python 2 - if sys.version_info[0] == 2 and isinstance(path, text): + if sys.version_info[0] == 2 and isinstance(path, str): path = path.encode('utf8') env['PATH'] = path diff --git a/preditor/prefs.py b/preditor/prefs.py index 16f4ff68..260cd558 100644 --- a/preditor/prefs.py +++ b/preditor/prefs.py @@ -12,8 +12,6 @@ import sys from pathlib import Path -import six - from . import resourcePath, utils # cache of all the preferences @@ -191,7 +189,7 @@ def create_stamped_path(core_name, filepath, sub_dir='workboxes', time_str=None) path.parent.mkdir(exist_ok=True) - path = six.text_type(path) + path = str(path) return path @@ -284,7 +282,7 @@ def get_backup_version_info(core_name, workbox_id, versionType, backup_file=None idx += 1 idx = min(idx, count - 1) - filepath = six.text_type(files[idx]) + filepath = str(files[idx]) display_idx = idx + 1 return filepath, display_idx, count @@ -322,7 +320,7 @@ def update_pref_args(core_name, pref_dict, old_name, update_data): orig_pref = pref_dict.pop(old_name) pref = orig_pref[:] if isinstance(orig_pref, list) else orig_pref - if isinstance(pref, six.text_type): + if isinstance(pref, str): replacements = update_data.get("replace", []) for replacement in replacements: pref = pref.replace(*replacement) @@ -332,11 +330,11 @@ def update_pref_args(core_name, pref_dict, old_name, update_data): newfilepath = create_stamped_path(core_name, pref) orig_filepath = workbox_dir / orig_pref if orig_filepath.is_file(): - orig_filepath = six.text_type(orig_filepath) + orig_filepath = str(orig_filepath) if not Path(newfilepath).is_file(): shutil.copy(orig_filepath, newfilepath) - newfilepath = six.text_type(Path(newfilepath).relative_to(workbox_dir)) + newfilepath = str(Path(newfilepath).relative_to(workbox_dir)) pref_dict.update({"backup_file": newfilepath}) diff --git a/preditor/resource/environment_variables.html b/preditor/resource/environment_variables.html deleted file mode 100644 index 86abd627..00000000 --- a/preditor/resource/environment_variables.html +++ /dev/null @@ -1,26 +0,0 @@ - - - -

blurdev

-
-

blurdev Site variables:

-

These variables should be defined at a user or system level to configure blurdev.

-

BDEV_DESIGNERPLUG_*: Used to add a collection of designer plugins to QDesigner. Should be set to "XMLPATH,MODULE_DIR". XMLPATH is the full path to a xml file listing plugins to load. MODULE_DIR is a path that needs added to sys.path so the modules in XMLPATH are importable. You can use environment variables in these strings, they will be expanded. This is used by QDesigner, nothing else.

-

BDEV_OFFLINE: If set to 1, this indicates that blurdev is not running on the "Blur" network. This causes the [* Offline] section of blurdev/resource/settings.ini to override the [Default] and [*] sections. When blurdev is imported it adds all env vars defined in settings.ini for the current operating system and Default if they are not already defined in os.environ.

-

BDEV_PATH_PREFS: This environment variable points to where per-computer user prefs are stored.

-

BDEV_PATH_PREFS_SHARED: This environment variable points to where shared user prefs are stored. This is often on the network and includes the os's logged in username in the path. If BDEV_OFFLINE is set to 1 this may point to the BDEV_PATH_PREFS location.

-

-

blurdev Temp variables:

-

These variables should not be defined all the time.

-

BDEV_DISABLE_AUTORUN: Set to "true" to disable the autorun.bat script used at blur. If this is not set when Maya shuts down, maya takes minutes to close. Maya uses several subprocess calls when closing and for some reason the doskey calls in the script take much longer than normal.

-

BDEV_STYLESHEET: Used to override the stylesheet when initalizing blurdev.

-

BDEV_TOOL_ENVIRONMENT: Forces blurdev to initialize with this treegrunt environment name. When saving prefs, this environment name change will not be saved. This is mostly used to ensure that launching a subprocess or farm job happens on the same treegrunt environment.

-

-

trax

-
-

TRAX_SPOOL_DIRECTORY_OVERRIDE: If defined, all messages will be stored the directory of this variable. Each message filename will have a "[mapping.mount()]_" prefix added giving you a hint to which server it would have ended up on. Defining this variable effectively disables all spool messages, and gives you a way to see what each spool message would look like. If your code depends on trax.api.spool.waitForCompletion, it will never complete.

-

Notes

-
-

Any variable names containing a * are wildcards. This allows you to add as many instances of that environment variable type as needed.

- - diff --git a/preditor/scintilla/lang/language.py b/preditor/scintilla/lang/language.py index 4b06fe72..8f834433 100644 --- a/preditor/scintilla/lang/language.py +++ b/preditor/scintilla/lang/language.py @@ -2,11 +2,7 @@ import re import sys - -try: - from configparser import ConfigParser -except ImportError: - from ConfigParser import ConfigParser +from configparser import ConfigParser from .. import Qsci diff --git a/preditor/scintilla/lexers/maxscriptlexer.py b/preditor/scintilla/lexers/maxscriptlexer.py index 01c56655..6f84c0bb 100644 --- a/preditor/scintilla/lexers/maxscriptlexer.py +++ b/preditor/scintilla/lexers/maxscriptlexer.py @@ -1,9 +1,6 @@ from __future__ import absolute_import import re -from builtins import str as text - -from future.utils import iteritems from .. import Qsci, QsciScintilla @@ -35,7 +32,7 @@ def __init__(self, parent=None): 7: 'SmartHighlight', } - for key, value in iteritems(self._styles): + for key, value in self._styles.items(): setattr(self, value, key) def description(self, style): @@ -214,7 +211,7 @@ def styleText(self, start, end): # cache objects used by processChunk that do not need updated every time it is # called - self.hlkwords = set(text(self.keywords(self.SmartHighlight)).lower().split()) + self.hlkwords = set(str(self.keywords(self.SmartHighlight)).lower().split()) self.chunkRegex = re.compile('([^A-Za-z0-9]*)([A-Za-z0-9]*)') kwrds = set(MS_KEYWORDS.split()) diff --git a/preditor/settings.py b/preditor/settings.py index 96fff081..dcee3842 100644 --- a/preditor/settings.py +++ b/preditor/settings.py @@ -1,14 +1,10 @@ #!/usr/bin/env python from __future__ import absolute_import, print_function +import configparser import os import sys -try: - import configparser -except Exception: - import ConfigParser as configparser # noqa: N813 - # define the default environment variables OS_TYPE = '' if os.name == 'posix': diff --git a/preditor/utils/text_search.py b/preditor/utils/text_search.py index c0cf2dc0..a3d49f75 100644 --- a/preditor/utils/text_search.py +++ b/preditor/utils/text_search.py @@ -4,10 +4,8 @@ import re from collections import deque -from future.utils import with_metaclass - -class TextSearch(with_metaclass(abc.ABCMeta, object)): +class TextSearch(object, metaclass=abc.ABCMeta): """Base class used to search and markup text for matches to a search term. Parameters: diff --git a/pyproject.toml b/pyproject.toml index 7f127a0b..888a9fd9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,3 +110,36 @@ skip-string-normalization = true [tool.isort] profile = "black" + +[tool.deptry] +# Don't analyze dev requirements used by tox +pep621_dev_dependency_groups = ["dev"] + +[tool.deptry.package_module_name_map] +PyQt6-QScintilla = "pyqt6_qscintilla" +QScintilla = "qscintilla" + +[tool.deptry.per_rule_ignores] +DEP001 = [ + "aspell", + # Ignore the maya dcc example plugin import for this check + "maya", + # The user must choose from the various Qt bindings used by Qt.py + "PyQt4", + "PyQt5", + "PyQt6", + "PySide2", + "PySide6", + "QtSiteConfig", + # TODO: Replace blurpath code with a plugin for reverting process specific + # env var changes when launching a subprocess. + "blurpath", +] +DEP003 = [ + "__main__", + "QtSiteConfig", +] +DEP002 = [ + "PyQt6-QScintilla", + "QScintilla", +] diff --git a/requirements.txt b/requirements.txt index c16958c5..7c475c32 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,4 @@ charset-normalizer -configparser>=4.0.2 -future>=0.18.2 importlib-metadata>=4.8.3 Qt.py>=1.4.4 signalslot>=0.1.2 diff --git a/tox.ini b/tox.ini index 6ed10f6b..46e2db3c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = begin,py{37,38,39,310,311},end,black,flake8 +envlist = begin,py{37,38,39,310,311,312,313},end,black,flake8,deptry skip_missing_interpreters = True [testenv] @@ -24,14 +24,14 @@ deps = commands = coverage erase -[testenv:py{37,38,39,310,311}] +[testenv:py{37,38,39,310,311,312,313}] depends = begin [testenv:end] basepython = python3 depends = begin - py{37,38,39,310,311} + py{37,38,39,310,311,312,313} parallel_show_output = True deps = coverage @@ -55,3 +55,13 @@ deps = pep8-naming==0.13.3 commands = python -m flake8 . + +[testenv:deptry] +# Only the 0.24.0 version of deptry seems to work when running in tox so py3.11+ +basepython = python3.11 +deps = + deptry>=0.24.0 + # Install the optional dependencies users may use (excluding qt plugin versions) + .[cli,dev,shortcut] +commands = + python -m deptry .