From 00c63e69dbf0c9aba9c1b4fd4507fa30bba60cbc Mon Sep 17 00:00:00 2001 From: Mike Hendricks Date: Fri, 6 Jun 2025 12:28:52 -0700 Subject: [PATCH 1/2] Drop support for Python 2, replace setup.cfg with pyproject.toml --- .../workflows/static-analysis-and-test.yml | 10 +- .pre-commit-config.yaml | 11 +- CONTRIBUTING.md | 5 +- README.md | 3 +- pyproject.toml | 115 +++++++++++++++++- requirements.txt | 3 +- setup.cfg | 101 --------------- setup.py | 7 -- tox.ini | 31 +---- 9 files changed, 134 insertions(+), 152 deletions(-) delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.github/workflows/static-analysis-and-test.yml b/.github/workflows/static-analysis-and-test.yml index 628368fb..ce2e6152 100644 --- a/.github/workflows/static-analysis-and-test.yml +++ b/.github/workflows/static-analysis-and-test.yml @@ -27,7 +27,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install tox + python -m pip install tox coverage[toml] - name: Lint with flake8 run: tox -e flake8 @@ -35,9 +35,6 @@ jobs: - name: Format with black run: tox -e black - - name: Py 2 and 3 compatibility - run: tox -e modernize - test: # We want to run on external PRs, but not on our own internal PRs as they'll @@ -49,9 +46,9 @@ jobs: strategy: matrix: - os: ['ubuntu-latest'] + os: ['ubuntu-latest', 'windows-latest'] python: ['3.8', '3.9', '3.10', '3.11'] - # Works around the depreciation of python 3.6, 3.7 for ubuntu + # Works around the depreciation of python 3.7 for ubuntu # https://github.com/actions/setup-python/issues/544 include: - os: 'ubuntu-22.04' @@ -85,7 +82,6 @@ jobs: include-hidden-files: true retention-days: 1 - coverage: # We want to run on external PRs, but not on our own internal PRs as they'll # be run by the push to the branch. Without this if check, checks are diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8818c33c..b7bcf9b6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,8 @@ --- +ci: + autoupdate_schedule: quarterly + skip: [black, flake8] + repos: - repo: https://github.com/psf/black @@ -12,14 +16,9 @@ repos: - id: flake8 additional_dependencies: - flake8-bugbear==22.12.6 + - Flake8-pyproject - pep8-naming==0.13.3 -- repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.2.0 - hooks: - - id: setup-cfg-fmt - args: [--min-py3-version=3.6] - - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index db47c412..fef3c5fb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,7 @@ A set of GitHub Action workflows are in place to perform the following style and **Styling** -Styling or linting is performed via [flake8] along with the plugins [flake8-bugbear] & [pep8-naming]. A minor amount of configuration has been added to _[setup.cfg]_ in order to provide better compatibility with our formatter black (see next section). +Styling or linting is performed via [flake8] along with the plugins [flake8-bugbear], [Flake8-pyproject] & [pep8-naming]. A minor amount of configuration has been added to _[pyproject.toml]_ in order to provide better compatibility with our formatter black (see next section). **Formatting** @@ -69,8 +69,9 @@ Releases are made manually by project managers and will automatically be uploade [flake8]: https://github.com/PyCQA/flake8 [flake8-bugbear]: https://github.com/PyCQA/flake8-bugbear +[Flake8-pyproject]: https://github.com/john-hen/Flake8-pyproject [pep8-naming]: https://github.com/PyCQA/pep8-naming -[setup.cfg]: https://github.com/blurstudio/preditor/blob/master/setup.cfg +[pyproject.toml]: https://github.com/blurstudio/hab/blob/master/pyproject.toml [black]: https://github.com/psf/black [Issues]: https://github.com/blurstudio/preditor/issues [create a new issue]: https://github.com/blurstudio/preditor/issues/new diff --git a/README.md b/README.md index 4f8ba648..974f4e12 100644 --- a/README.md +++ b/README.md @@ -128,8 +128,7 @@ The more mature QScintilla workbox requires a few extra dependencies that must be passed manually. It hasn't been added to `extras_require` because we plan to split it into its own pip module due to it requiring PyQt5 which is a little hard to get working inside of DCC's that ship with PySide2 by default. Here is the -python 3 pip install command(For python 2 you will need to compile QScintilla -yourself.) +python 3 pip install command. - `pip install preditor PyQt5, QScintilla>=2.11.4 aspell-python-py3` diff --git a/pyproject.toml b/pyproject.toml index 14c076a7..da171105 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,15 +1,128 @@ [build-system] requires = [ "setuptools >= 44.1.1", - "setuptools_scm[toml] >= 4, <6", + "setuptools_scm[toml] >= 4", "wheel >= 0.36", ] build-backend = "setuptools.build_meta" +[project] +name = "PrEditor" +description = "A python REPL and Editor and console based on Qt." +authors = [{name = "Blur Studio", email = "opensource@blur.com"}] +license = {text = "LGPL-3.0"} +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +requires-python = ">=3.7" +dependencies = [ + "Qt.py", + "configparser>=4.0.2", + "future>=0.18.2", + "signalslot>=0.1.2", + "importlib-metadata>=4.8.3", +] +dynamic = ["version"] + +[project.readme] +file = "README.md" +content-type = "text/markdown" + +[project.urls] +Homepage = "https://github.com/blurstudio/PrEditor" +Source = "https://github.com/blurstudio/PrEditor" +Tracker = "https://github.com/blurstudio/PrEditor/issues" + +[project.optional-dependencies] +cli =[ + "click>=7.1.2", + "click-default-group", +] +dev =[ + "black", + "build", + "covdefaults", + "coverage", + "flake8", + "flake8-bugbear", + "Flake8-pyproject", + "pep8-naming", + "pytest", + "tox", +] +shortcut =[ + "casement>=0.1.0;platform_system=='Windows'", +] + +[project.scripts] +preditor = "preditor.cli:cli" + +[project.gui-scripts] +preditorw = "preditor.cli:cli" + +[project.entry-points."preditor.plug.about_module"] +PrEditor = "preditor.about_module:AboutPreditor" +Qt = "preditor.about_module:AboutQt" +Python = "preditor.about_module:AboutPython" +Exe = "preditor.about_module:AboutExe" + +[project.entry-points."preditor.plug.editors"] +TextEdit = "preditor.gui.workbox_text_edit:WorkboxTextEdit" +QScintilla = "preditor.gui.workboxwidget:WorkboxWidget" + +[project.entry-points."preditor.plug.logging_handlers"] +PrEditor = "preditor.gui.logger_window_handler:LoggerWindowHandler" + + +[tool.setuptools] +include-package-data = true +platforms = ["any"] +license-files = ["LICENSE"] + +[tool.setuptools.packages.find] +exclude = ["tests"] +namespaces = false + [tool.setuptools_scm] write_to = "preditor/version.py" version_scheme = "release-branch-semver" +[tool.flake8] +select = ["B", "C", "E", "F", "N", "W", "B9"] +extend-ignore = [ + "E203", + "E501", + "E722", + "N802", + "N803", + "N806", + "N815", + "N816", + "W503", +] +max-line-length = "80" +exclude = [ + "*.egg-info", + "*.pyc", + ".cache", + ".eggs", + ".git", + ".tox", + "__pycache__", + "build", + "dist", + "docs", + "shared-venv", +] + + [tool.black] skip-string-normalization = true diff --git a/requirements.txt b/requirements.txt index a965fa3e..86b3ad81 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ +build configparser>=4.0.2 future>=0.18.2 -importlib-metadata>=4.8.3;python_version>="3.6" +importlib-metadata>=4.8.3 Qt.py signalslot>=0.1.2 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 37c256ce..00000000 --- a/setup.cfg +++ /dev/null @@ -1,101 +0,0 @@ -[metadata] -name = PrEditor -version = file: preditor/version.py -description = A python REPL and Editor and console based on Qt. -long_description = file: README.md -long_description_content_type = text/markdown -url = https://github.com/blurstudio/PrEditor.git -author = Blur Studio -author_email = opensource@blur.com -license = LGPL-3.0 -license_file = LICENSE -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Developers - License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 2 - Programming Language :: Python :: 3 - Programming Language :: Python :: Implementation :: CPython - Programming Language :: Python :: Implementation :: PyPy -platform = any - -[options] -packages = find: -install_requires = - Qt.py - configparser>=4.0.2 - future>=0.18.2 - signalslot>=0.1.2 - importlib-metadata>=4.8.3;python_version>="3.6" -python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.* -include_package_data = True -setup_requires = - setuptools - setuptools-scm[toml]>=4,<6 - wheel - -[options.packages.find] -exclude = tests - -[options.entry_points] -console_scripts = - preditor = preditor.cli:cli -gui_scripts = - preditorw = preditor.cli:cli -preditor.plug.about_module = - PrEditor = preditor.about_module:AboutPreditor - Qt = preditor.about_module:AboutQt - Python = preditor.about_module:AboutPython - Exe = preditor.about_module:AboutExe -preditor.plug.editors = - TextEdit = preditor.gui.workbox_text_edit:WorkboxTextEdit - QScintilla = preditor.gui.workboxwidget:WorkboxWidget -preditor.plug.logging_handlers = - PrEditor = preditor.gui.logger_window_handler:LoggerWindowHandler - -[options.extras_require] -cli = - click>=7.1.2 - click-default-group -dev = - black - covdefaults - coverage - flake8 - flake8-bugbear - pep8-naming - pytest - tox -shortcut = - casement>=0.1.0;platform_system=="Windows" - -[bdist_wheel] -universal = 1 - -[flake8] -select = B, C, E, F, N, W, B9 -extend-ignore = - E203, - E501, - E722, - N802, - N803, - N806, - N815, - N816, - W503, -max-line-length = 80 -exclude = - *.egg-info - *.pyc - .cache - .eggs - .git - .tox - __pycache__ - build - dist - docs - shared-venv diff --git a/setup.py b/setup.py deleted file mode 100644 index ed0f7d70..00000000 --- a/setup.py +++ /dev/null @@ -1,7 +0,0 @@ -# You should not need to modify the contents below. -# Make changes to setup.cfg and pyproject.toml instead. -from __future__ import absolute_import - -from setuptools import setup - -setup(use_scm_version=True) diff --git a/tox.ini b/tox.ini index 35888549..6ed10f6b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,16 +1,10 @@ [tox] -envlist = modernize,begin,py{27,36,37,38,39,310,311},end,black,flake8 +envlist = begin,py{37,38,39,310,311},end,black,flake8 skip_missing_interpreters = True -skipsdist = True [testenv] changedir = {toxinidir} -# NOTE: we can't install PyQt using pip in python 2, use system-site-packages -sitepackages = - {py27}: True -setenv = - {py27}: PYTHONWARNINGS=ignore:DEPRECATION::pip._internal.cli.base_command -skip_install = True +package = editable deps = -rrequirements.txt covdefaults @@ -20,22 +14,8 @@ deps = QScintilla>=2.11.4;python_version>="3.5" Qt.py commands = - # Ensure the version.py file is created - python setup.py egg_info - coverage run -m pytest {tty:--color=yes} {posargs:tests/} -[testenv:modernize] -# Test compatibility with python 2 and 3 -deps = - modernize -commands = - # Check for python 3 compliance - python -m compileall -f -q -x \.tox|shared-venv . - # Note: `-f numliterals -f except -f dict_six` always report failure so we can't include them - # in this test. - python -m modernize -f print -f import -f basestring -f unicode_type --enforce ./preditor - [testenv:begin] basepython = python3 deps = @@ -44,14 +24,14 @@ deps = commands = coverage erase -[testenv:py{27,36,37,38,39,310,311}] +[testenv:py{37,38,39,310,311}] depends = begin [testenv:end] basepython = python3 depends = begin - py{27,36,37,38,39,310,311} + py{37,38,39,310,311} parallel_show_output = True deps = coverage @@ -69,8 +49,9 @@ commands = [testenv:flake8] basepython = python3 deps = - flake8==5.0.4 flake8-bugbear==22.12.6 + Flake8-pyproject + flake8==5.0.4 pep8-naming==0.13.3 commands = python -m flake8 . From 7ff6b64e3c751b8f1c9ef9c1a4c6245eea6389b2 Mon Sep 17 00:00:00 2001 From: Mike Hendricks Date: Fri, 6 Jun 2025 15:04:17 -0700 Subject: [PATCH 2/2] Remove six and pkg_resources Now that Python 2 support has ended they are no longer needed. --- preditor/about_module.py | 7 +------ preditor/delayable_engine/__init__.py | 14 +++----------- preditor/gui/group_tab_widget/__init__.py | 3 +-- preditor/gui/level_buttons.py | 6 +----- preditor/gui/loggerwindow.py | 5 ++--- preditor/plugins.py | 20 ++++---------------- preditor/scintilla/documenteditor.py | 3 +-- tests/test_stream.py | 13 ++++++------- 8 files changed, 19 insertions(+), 52 deletions(-) diff --git a/preditor/about_module.py b/preditor/about_module.py index 204cb521..8223211c 100644 --- a/preditor/about_module.py +++ b/preditor/about_module.py @@ -5,7 +5,6 @@ import sys import textwrap -import six from future.utils import with_metaclass import preditor @@ -52,11 +51,7 @@ def generate(cls, instance=None): text = plug.text() except Exception as error: text = "Error processing: {}".format(error) - if six.PY3: - text = textwrap.indent(text, cls.indent) - else: - text = ['{}{}'.format(cls.indent, line) for line in text.split('\n')] - text = "\n".join(text) + text = textwrap.indent(text, cls.indent) # Build the output string including the version information if provided. if version is not None: diff --git a/preditor/delayable_engine/__init__.py b/preditor/delayable_engine/__init__.py index ac55dea7..b4031171 100644 --- a/preditor/delayable_engine/__init__.py +++ b/preditor/delayable_engine/__init__.py @@ -4,21 +4,13 @@ import warnings import weakref from collections import OrderedDict +from collections.abc import MutableSet -import six from Qt import QtCompat from Qt.QtCore import QObject, QTimer, Signal from .delayables import Delayable -try: - from collections.abc import MutableSet -except ImportError: - # Due to the older versions of six installed by default with DCC's like - # Maya 2020, we can't rely on six.moves.collections_abc, so handle - # the py 2.7 import - from collections import MutableSet - # https://stackoverflow.com/a/7829569 class OrderedSet(MutableSet): @@ -108,7 +100,7 @@ def add_delayable(self, delayable): Raises: KeyError: A invalid key identifier string was passed. """ - if isinstance(delayable, six.string_types): + if isinstance(delayable, str): if delayable in self.delayables: # Don't replace the instance if a string is passed return @@ -276,7 +268,7 @@ def remove_delayable(self, delayable): delayable (Delayable or str): A Delayable instance or the key identifier. Remove this delayable from the current documents if it was added. """ - if isinstance(delayable, six.string_types): + if isinstance(delayable, str): if delayable not in self.delayables: return delayable = self.delayables[delayable] diff --git a/preditor/gui/group_tab_widget/__init__.py b/preditor/gui/group_tab_widget/__init__.py index 45a4028d..5d06d6a5 100644 --- a/preditor/gui/group_tab_widget/__init__.py +++ b/preditor/gui/group_tab_widget/__init__.py @@ -3,7 +3,6 @@ import os import re -import six from Qt.QtCore import Qt from Qt.QtGui import QIcon from Qt.QtWidgets import QHBoxLayout, QMessageBox, QToolButton, QWidget @@ -104,7 +103,7 @@ def add_new_tab(self, group, title="Workbox", group_fmt=None): if isinstance(group, int): group_title = self.tabText(group) parent = self.widget(group) - elif isinstance(group, six.string_types): + elif isinstance(group, str): group_title = group index = self.index_for_text(group) if index != -1: diff --git a/preditor/gui/level_buttons.py b/preditor/gui/level_buttons.py index 509fc031..37c71c9d 100644 --- a/preditor/gui/level_buttons.py +++ b/preditor/gui/level_buttons.py @@ -4,7 +4,6 @@ import types from functools import partial -import six from Qt.QtGui import QIcon from Qt.QtWidgets import QAction, QMenu, QToolButton @@ -182,10 +181,7 @@ def setLevel(self, level): # Emit our signal self.level_changed.emit(level=level) - if six.PY3: - root.setLevel = types.MethodType(setLevel, root) - else: - root.setLevel = types.MethodType(setLevel, root, type(root)) + root.setLevel = types.MethodType(setLevel, root) return root diff --git a/preditor/gui/loggerwindow.py b/preditor/gui/loggerwindow.py index 0d90161c..256c46a1 100644 --- a/preditor/gui/loggerwindow.py +++ b/preditor/gui/loggerwindow.py @@ -11,7 +11,6 @@ from functools import partial import __main__ -import six from Qt import QtCompat, QtCore, QtWidgets from Qt.QtCore import QByteArray, Qt, QTimer, Signal, Slot from Qt.QtGui import QCursor, QFont, QIcon, QTextCursor @@ -428,7 +427,7 @@ def workbox_for_name(cls, name, show=False, visible=False): workbox = logger.current_workbox() # If name is a string, find first tab with that name - elif isinstance(name, six.string_types): + elif isinstance(name, str): split = name.split('/', 1) if len(split) < 2: return None @@ -792,7 +791,7 @@ def recordPrefs(self, manual=False): 'wordWrap': self.uiWordWrapACT.isChecked(), 'clearBeforeRunning': self.uiClearBeforeRunningACT.isChecked(), 'uiSelectTextACT': self.uiSelectTextACT.isChecked(), - 'toolbarStates': six.text_type(self.saveState().toHex(), 'utf-8'), + 'toolbarStates': str(self.saveState().toHex(), 'utf-8'), 'consoleFont': self.console().font().toString(), 'uiAutoSaveSettingssACT': self.uiAutoSaveSettingssACT.isChecked(), 'uiAutoPromptACT': self.uiAutoPromptACT.isChecked(), diff --git a/preditor/plugins.py b/preditor/plugins.py index a27bcfa9..4ebf513e 100644 --- a/preditor/plugins.py +++ b/preditor/plugins.py @@ -2,12 +2,7 @@ import logging -import six - -if six.PY3: - from importlib_metadata import EntryPoint, entry_points -else: - import pkg_resources +from importlib_metadata import EntryPoint, entry_points _logger = logging.getLogger(__name__) @@ -109,12 +104,8 @@ def logging_handlers(self, name=None): @classmethod def iterator(cls, group=None, name=None): """Iterates over the requested entry point yielding results.""" - if six.PY3: - for ep in entry_points().select(group=group): - yield ep - else: - for ep in pkg_resources.iter_entry_points(group, name=name): - yield ep + for ep in entry_points().select(group=group): + yield ep @classmethod def from_string(cls, value, name="", group=""): @@ -123,8 +114,5 @@ def from_string(cls, value, name="", group=""): Example: cls = from_string("preditor.gui.errordialog:ErrorDialog") """ - if six.PY3: - ep = EntryPoint(name=name, value=value, group=group) - else: - ep = pkg_resources.EntryPoint(name, value) + ep = EntryPoint(name=name, value=value, group=group) return ep.load() diff --git a/preditor/scintilla/documenteditor.py b/preditor/scintilla/documenteditor.py index 30f04f56..f49e0dda 100644 --- a/preditor/scintilla/documenteditor.py +++ b/preditor/scintilla/documenteditor.py @@ -19,7 +19,6 @@ from contextlib import contextmanager from functools import partial -import six from PyQt5.Qsci import QsciScintilla from PyQt5.QtCore import QTextCodec from Qt import QtCompat @@ -284,7 +283,7 @@ def commentToggle(self, doWhich=None): """ # If called by 'triggered' signal, clear out passed argument. - if not isinstance(doWhich, six.string_types): + if not isinstance(doWhich, str): doWhich = None comment, result = self.commentCheck() diff --git a/tests/test_stream.py b/tests/test_stream.py index 25d5bcf7..9f16695f 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -4,7 +4,6 @@ import sys import pytest -import six from preditor.stream import STDERR, STDOUT, Director, Manager, install_to_std @@ -16,13 +15,13 @@ def manager(): @pytest.fixture def stdout(manager): - old_stream = six.StringIO() + old_stream = io.StringIO() return Director(manager, "test_out", old_stream=old_stream) @pytest.fixture def stderr(manager): - old_stream = six.StringIO() + old_stream = io.StringIO() return Director(manager, "test_err", old_stream=old_stream) @@ -75,8 +74,8 @@ def __getattribute__(self, name): # the name/encoding of the windows pythonw.exe default streams orig_stdout = sys.stdout orig_stderr = sys.stderr - sys.stdout = NamedTextIOWrapper(six.BytesIO(), name='nul', encoding='cp1252') - sys.stderr = NamedTextIOWrapper(six.BytesIO(), name='nul', encoding='cp1252') + sys.stdout = NamedTextIOWrapper(io.BytesIO(), name='nul', encoding='cp1252') + sys.stderr = NamedTextIOWrapper(io.BytesIO(), name='nul', encoding='cp1252') try: # Build a director here that will grab the nul streams # so we can check that they don't store them in .old_stream @@ -196,8 +195,8 @@ def test_install_to_std(): # restore the original FLO's so we don't break other tests. stdout = sys.stdout stderr = sys.stderr - temp_out = six.StringIO() - temp_err = six.StringIO() + temp_out = io.StringIO() + temp_err = io.StringIO() sys.stdout = temp_out sys.stderr = temp_err try: