From e123741176c1bcc535195921f77127fc9c4d2fed Mon Sep 17 00:00:00 2001 From: Mike Hendricks Date: Fri, 16 Jan 2026 18:11:48 -0800 Subject: [PATCH 1/8] Add deptry for pip requirement tracking --- .../workflows/static-analysis-and-test.yml | 2 ++ preditor/osystem.py | 2 ++ pyproject.toml | 33 +++++++++++++++++++ tox.ini | 12 ++++++- 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/.github/workflows/static-analysis-and-test.yml b/.github/workflows/static-analysis-and-test.yml index e9d153dc..a3589377 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 diff --git a/preditor/osystem.py b/preditor/osystem.py index d54cf69d..6dfe56b9 100644 --- a/preditor/osystem.py +++ b/preditor/osystem.py @@ -215,6 +215,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: 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/tox.ini b/tox.ini index 6ed10f6b..6b6b17da 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},end,black,flake8,deptry skip_missing_interpreters = True [testenv] @@ -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 . From c77a47971d98a01012553b0dd497c565c4704fa6 Mon Sep 17 00:00:00 2001 From: Mike Hendricks Date: Fri, 16 Jan 2026 18:24:06 -0800 Subject: [PATCH 2/8] Remove the old Environment Variable help dialog --- preditor/gui/loggerwindow.py | 16 +----------- preditor/gui/ui/loggerwindow.ui | 1 - preditor/resource/environment_variables.html | 26 -------------------- 3 files changed, 1 insertion(+), 42 deletions(-) delete mode 100644 preditor/resource/environment_variables.html diff --git a/preditor/gui/loggerwindow.py b/preditor/gui/loggerwindow.py index 93c225e8..641c5bfb 100644 --- a/preditor/gui/loggerwindow.py +++ b/preditor/gui/loggerwindow.py @@ -25,11 +25,9 @@ QInputDialog, QMenu, QMessageBox, - QTextBrowser, QTextEdit, QToolButton, QToolTip, - QVBoxLayout, ) from .. import ( @@ -44,7 +42,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 +300,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 +2162,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/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.

- - From 7bd1ab2f1261f5d1e7c3e3487a6950c0fffae754 Mon Sep 17 00:00:00 2001 From: Mike Hendricks Date: Fri, 16 Jan 2026 15:53:41 -0800 Subject: [PATCH 3/8] Remove use of the future package We are done with the python 2 to 3 transition --- preditor/about_module.py | 4 +--- preditor/enum.py | 13 +++++-------- preditor/scintilla/lexers/maxscriptlexer.py | 4 +--- preditor/utils/text_search.py | 4 +--- requirements.txt | 1 - 5 files changed, 8 insertions(+), 18 deletions(-) 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/enum.py b/preditor/enum.py index 18a722d0..2fecdeb9 100644 --- a/preditor/enum.py +++ b/preditor/enum.py @@ -5,9 +5,6 @@ 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 +76,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,7 +201,7 @@ 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 self._compareStr(value): @@ -296,7 +293,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 +425,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 +582,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/scintilla/lexers/maxscriptlexer.py b/preditor/scintilla/lexers/maxscriptlexer.py index 01c56655..e5348ea0 100644 --- a/preditor/scintilla/lexers/maxscriptlexer.py +++ b/preditor/scintilla/lexers/maxscriptlexer.py @@ -3,8 +3,6 @@ import re from builtins import str as text -from future.utils import iteritems - from .. import Qsci, QsciScintilla MS_KEYWORDS = """ @@ -35,7 +33,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): 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/requirements.txt b/requirements.txt index c16958c5..81c02473 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ charset-normalizer configparser>=4.0.2 -future>=0.18.2 importlib-metadata>=4.8.3 Qt.py>=1.4.4 signalslot>=0.1.2 From d1ee6c388eb7bcf27619d233d38a63e753bcdbd5 Mon Sep 17 00:00:00 2001 From: Mike Hendricks Date: Fri, 16 Jan 2026 16:19:16 -0800 Subject: [PATCH 4/8] Remove use of builtins from the future python 2 package --- preditor/enum.py | 3 +-- preditor/gui/console.py | 3 +-- preditor/gui/loggerwindow.py | 1 - preditor/osystem.py | 3 +-- preditor/scintilla/lexers/maxscriptlexer.py | 3 +-- 5 files changed, 4 insertions(+), 9 deletions(-) diff --git a/preditor/enum.py b/preditor/enum.py index 2fecdeb9..ce684e19 100644 --- a/preditor/enum.py +++ b/preditor/enum.py @@ -2,7 +2,6 @@ import abc import re -from builtins import str as text from numbers import Number # ============================================================================= @@ -203,7 +202,7 @@ def __eq__(self, value): return self.number == value.number 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 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/loggerwindow.py b/preditor/gui/loggerwindow.py index 641c5bfb..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 diff --git a/preditor/osystem.py b/preditor/osystem.py index 6dfe56b9..9dfc832b 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 @@ -241,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/scintilla/lexers/maxscriptlexer.py b/preditor/scintilla/lexers/maxscriptlexer.py index e5348ea0..6f84c0bb 100644 --- a/preditor/scintilla/lexers/maxscriptlexer.py +++ b/preditor/scintilla/lexers/maxscriptlexer.py @@ -1,7 +1,6 @@ from __future__ import absolute_import import re -from builtins import str as text from .. import Qsci, QsciScintilla @@ -212,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()) From 829c1278b1f9419a8713c2eb3725afc47cacb739 Mon Sep 17 00:00:00 2001 From: Mike Hendricks Date: Fri, 16 Jan 2026 16:58:44 -0800 Subject: [PATCH 5/8] Remove use of six This wasn't added to the pip deps --- preditor/gui/drag_tab_bar.py | 3 +-- preditor/prefs.py | 12 +++++------- 2 files changed, 6 insertions(+), 9 deletions(-) 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/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}) From 6bb4e7047f904029159d44df2be93be18b26d495 Mon Sep 17 00:00:00 2001 From: Mike Hendricks Date: Fri, 16 Jan 2026 18:07:18 -0800 Subject: [PATCH 6/8] Remove python 2's ConfigParser support --- preditor/scintilla/lang/language.py | 6 +----- preditor/settings.py | 6 +----- requirements.txt | 1 - 3 files changed, 2 insertions(+), 11 deletions(-) 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/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/requirements.txt b/requirements.txt index 81c02473..7c475c32 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ charset-normalizer -configparser>=4.0.2 importlib-metadata>=4.8.3 Qt.py>=1.4.4 signalslot>=0.1.2 From 037d242608859bf678b00f35f3b4270604aee949 Mon Sep 17 00:00:00 2001 From: Mike Hendricks Date: Fri, 16 Jan 2026 18:32:09 -0800 Subject: [PATCH 7/8] Remove distutils uses no longer available in python 3.12 --- preditor/cli.py | 4 ++-- preditor/osystem.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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/osystem.py b/preditor/osystem.py index 9dfc832b..12ff699b 100644 --- a/preditor/osystem.py +++ b/preditor/osystem.py @@ -38,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 '')) From ec13e6b4c1effb62f760c6780af2c32b5916f9bb Mon Sep 17 00:00:00 2001 From: Mike Hendricks Date: Fri, 16 Jan 2026 18:42:58 -0800 Subject: [PATCH 8/8] Enable testing for python 3.12 and 3.13 --- .github/workflows/static-analysis-and-test.yml | 2 +- tox.ini | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/static-analysis-and-test.yml b/.github/workflows/static-analysis-and-test.yml index a3589377..3e8c1165 100644 --- a/.github/workflows/static-analysis-and-test.yml +++ b/.github/workflows/static-analysis-and-test.yml @@ -49,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/tox.ini b/tox.ini index 6b6b17da..46e2db3c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = begin,py{37,38,39,310,311},end,black,flake8,deptry +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