diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..0f06e16 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,33 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python +name: pre-commit + +on: + pull_request: + push: + branches: + - master + # Allow to run this workflow manually from the Actions tab + workflow_dispatch: + +env: + FORCE_COLOR: 1 + +jobs: + pre-commit: + name: linting + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - uses: pre-commit/action@v3.0.1 + with: + extra_args: --all-files --show-diff-on-failure + env: + PRE_COMMIT_COLOR: always + - uses: pre-commit-ci/lite-action@v1.0.2 + if: always() + with: + msg: Apply pre-commit code formatting diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8b3bcbc..50629dd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,33 +17,32 @@ jobs: fail-fast: false matrix: os: - - ["ubuntu", "ubuntu-20.04"] + - ["ubuntu", "ubuntu-latest"] config: # [Python version, tox env] - - ["3.9", "release-check"] - - ["3.9", "lint"] - - ["3.7", "py37"] - - ["3.8", "py38"] - - ["3.9", "py39"] - - ["3.10", "py310"] - - ["3.11", "py311"] - - ["3.12", "py312"] - - ["3.13.0-alpha - 3.13.0", "py313"] - - ["pypy-3.9", "pypy3"] - - ["3.9", "docs"] - - ["3.9", "coverage"] + - ["3.11", "release-check"] + - ["3.8", "py38"] + - ["3.9", "py39"] + - ["3.10", "py310"] + - ["3.11", "py311"] + - ["3.12", "py312"] + - ["3.13", "py313"] + - ["pypy-3.10", "pypy3"] + - ["3.11", "docs"] + - ["3.11", "coverage"] runs-on: ${{ matrix.os[1] }} if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name name: ${{ matrix.config[1] }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.config[0] }} + allow-prereleases: true - name: Pip cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ matrix.config[0] }}-${{ hashFiles('setup.*', 'tox.ini') }} @@ -55,7 +54,11 @@ jobs: python -m pip install --upgrade pip pip install tox - name: Test + if: ${{ !startsWith(runner.os, 'Mac') }} run: tox -e ${{ matrix.config[1] }} + - name: Test (macOS) + if: ${{ startsWith(runner.os, 'Mac') }} + run: tox -e ${{ matrix.config[1] }}-universal2 - name: Coverage if: matrix.config[1] == 'coverage' run: | diff --git a/.meta.toml b/.meta.toml index 4f73e55..c213e4f 100644 --- a/.meta.toml +++ b/.meta.toml @@ -2,14 +2,14 @@ # https://github.com/zopefoundation/meta/tree/master/config/pure-python [meta] template = "pure-python" -commit-id = "6f8d8c51" +commit-id = "85622de1" [python] with-pypy = true with-docs = true with-sphinx-doctests = true with-windows = false -with-future-python = true +with-future-python = false with-macos = false [tox] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..7ab398c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,28 @@ +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python +minimum_pre_commit_version: '3.6' +repos: + - repo: https://github.com/pycqa/isort + rev: "5.13.2" + hooks: + - id: isort + - repo: https://github.com/hhatto/autopep8 + rev: "v2.3.1" + hooks: + - id: autopep8 + args: [--in-place, --aggressive, --aggressive] + - repo: https://github.com/asottile/pyupgrade + rev: v3.17.0 + hooks: + - id: pyupgrade + args: [--py38-plus] + - repo: https://github.com/isidentical/teyit + rev: 0.4.3 + hooks: + - id: teyit + - repo: https://github.com/PyCQA/flake8 + rev: "7.1.1" + hooks: + - id: flake8 + additional_dependencies: + - flake8-debugger == 4.1.2 diff --git a/CHANGES.rst b/CHANGES.rst index c4c685b..2c6b4b6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,10 +2,12 @@ Changes ========= -4.1 (unreleased) +5.0 (unreleased) ================ -- Nothing changed yet. +- Add final support for Python 3.13. + +- Drop support for Python 3.7. 4.0 (2023-11-13) diff --git a/MANIFEST.in b/MANIFEST.in index a1103d6..a8b9c14 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,6 +5,7 @@ include *.rst include *.txt include buildout.cfg include tox.ini +include .pre-commit-config.yaml recursive-include docs *.py recursive-include docs *.rst diff --git a/docs/conf.py b/docs/conf.py index 8abf14b..8af5f4d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,9 +1,8 @@ -# -*- coding: utf-8 -*- # # transaction documentation build configuration file, created by # sphinx-quickstart on Wed May 16 16:43:53 2012. # -# This file is execfile()d with the current directory set to its containing dir. +# This file is execfile()d with the current directory set to its containing dir # # Note that not all possible configuration values are present in this # autogenerated file. @@ -11,26 +10,29 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import sys + import pkg_resources + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) sys.path.append(os.path.abspath('../')) rqmt = pkg_resources.require('transaction')[0] -# -- General configuration ----------------------------------------------------- +# -- General configuration ----------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # 1.8 was the last version that runs on Python 2; 2.0+ requires Python 3. # `autodoc_default_options` was new in 1.8 needs_sphinx = "1.8" -# Add any Sphinx extension module names here, as strings. They can be extensions +# Add any Sphinx extension module names here as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', @@ -48,14 +50,14 @@ source_suffix = '.rst' # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. -project = u'transaction' -copyright = u'2012, Zope Foundation Contributors' +project = 'transaction' +copyright = '2012-2024, Zope Foundation Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -68,40 +70,41 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] -# The reST default role (used for this markup: `text`) to use for all documents. +# The reST default role (used for this markup: `text`) to use for all +# documents. default_role = 'obj' # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] -# -- Options for HTML output --------------------------------------------------- +# -- Options for HTML output --------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. @@ -110,26 +113,26 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -138,123 +141,131 @@ # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} -html_sidebars = { '**': ['globaltoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html'], } +# html_sidebars = {} +html_sidebars = {'**': ['globaltoc.html', + 'relations.html', + 'sourcelink.html', + 'searchbox.html'], + } # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'transactiondoc' -# -- Options for LaTeX output -------------------------------------------------- +# -- Options for LaTeX output -------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # Additional stuff for the LaTeX preamble. + # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). +# (source start file, target name, title, author, documentclass [howto/manual]) latex_documents = [ - ('index', 'transaction.tex', u'transaction Documentation', - u'Zope Foundation Contributors', 'manual'), + ('index', 'transaction.tex', 'transaction Documentation', + 'Zope Foundation Contributors', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True -# -- Options for manual page output -------------------------------------------- +# -- Options for manual page output -------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'transaction', u'transaction Documentation', - [u'Zope Foundation Contributors'], 1) + ('index', 'transaction', 'transaction Documentation', + ['Zope Foundation Contributors'], 1) ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False -# -- Options for Texinfo output ------------------------------------------------ +# -- Options for Texinfo output ------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'transaction', u'transaction Documentation', - u'Zope Foundation Contributors', 'transaction', 'One line description of project.', - 'Miscellaneous'), + ('index', + 'transaction', + 'transaction Documentation', + 'Zope Foundation Contributors', + 'transaction', + 'One line description of project.', + 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # Sphinx 1.8+ prefers this to `autodoc_default_flags`. It's documented that # either True or None mean the same thing as just setting the flag, but diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..959c8ef --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,21 @@ +# +# Generated from: +# https://github.com/zopefoundation/meta/tree/master/config/pure-python + +[build-system] +requires = ["setuptools<74"] +build-backend = "setuptools.build_meta" + +[tool.coverage.run] +branch = true +source = ["transaction"] + +[tool.coverage.report] +fail_under = 99 +precision = 2 +ignore_errors = true +show_missing = true +exclude_lines = ["pragma: no cover", "pragma: nocover", "except ImportError:", "raise NotImplementedError", "if __name__ == '__main__':", "self.fail", "raise AssertionError", "raise unittest.Skip"] + +[tool.coverage.html] +directory = "parts/htmlcov" diff --git a/setup.cfg b/setup.cfg index 306ffb2..a73efa3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,5 @@ # Generated from: # https://github.com/zopefoundation/meta/tree/master/config/pure-python -[bdist_wheel] -universal = 0 [flake8] doctests = 1 diff --git a/setup.py b/setup.py index a105f06..6422a74 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ from setuptools import setup -version = '4.1.dev0' +version = '5.0.dev0' here = os.path.abspath(os.path.dirname(__file__)) @@ -32,6 +32,7 @@ def _read_file(filename): version=version, description='Transaction management for Python', long_description=README, + long_description_content_type='text/x-rst', classifiers=[ "Development Status :: 6 - Mature", "License :: OSI Approved :: Zope Public License", @@ -42,12 +43,12 @@ def _read_file(filename): "Operating System :: Unix", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Framework :: ZODB", @@ -66,7 +67,7 @@ def _read_file(filename): package_dir={'': 'src'}, include_package_data=True, zip_safe=False, - python_requires='>=3.7', + python_requires='>=3.8', install_requires=[ 'zope.interface', ], diff --git a/src/transaction/_compat.py b/src/transaction/_compat.py deleted file mode 100644 index 757cb19..0000000 --- a/src/transaction/_compat.py +++ /dev/null @@ -1,10 +0,0 @@ -def text_(s): - if not isinstance(s, str): # pragma: no cover - s = s.decode('utf-8') - return s - - -def native_(s, encoding='latin-1', errors='strict'): - if isinstance(s, str): - return s - return str(s, encoding, errors) diff --git a/src/transaction/_manager.py b/src/transaction/_manager.py index d8c0aa8..8ca13f0 100644 --- a/src/transaction/_manager.py +++ b/src/transaction/_manager.py @@ -22,7 +22,6 @@ from zope.interface import implementer -from transaction._compat import text_ from transaction._transaction import Transaction from transaction.interfaces import AlreadyInTransaction from transaction.interfaces import ITransactionManager @@ -185,11 +184,13 @@ def run(self, func=None, tries=3): # These are ordinarily strings, but that's # not required. A callable class could override them # to anything. - name = func.__name__ - doc = func.__doc__ + name = func.__name__ or '' + doc = func.__doc__ or '' - name = text_(name) if name else '' - doc = text_(doc) if doc else '' + if isinstance(name, bytes): + name = name.decode('UTF-8') + if isinstance(doc, bytes): + doc = doc.decode('UTF-8') if name and name != '_': if doc: diff --git a/src/transaction/_transaction.py b/src/transaction/_transaction.py index 173f2be..2b3893f 100644 --- a/src/transaction/_transaction.py +++ b/src/transaction/_transaction.py @@ -583,7 +583,7 @@ def note(self, text): def setUser(self, user_name, path="/"): """See `~transaction.interfaces.ITransaction`.""" - self.user = "{} {}".format(text_or_warn(path), text_or_warn(user_name)) + self.user = f"{text_or_warn(path)} {text_or_warn(user_name)}" def setExtendedInfo(self, name, value): """See `~transaction.interfaces.ITransaction`.""" diff --git a/src/transaction/tests/examples.py b/src/transaction/tests/examples.py index c25b28d..79a4fec 100644 --- a/src/transaction/tests/examples.py +++ b/src/transaction/tests/examples.py @@ -21,6 +21,7 @@ class DataManager: Used by the 'datamanager' chapter in the Sphinx docs. """ + def __init__(self): self.state = 0 self.sp = 0 diff --git a/src/transaction/tests/test__manager.py b/src/transaction/tests/test__manager.py index 0d6a183..2ae6cdc 100644 --- a/src/transaction/tests/test__manager.py +++ b/src/transaction/tests/test__manager.py @@ -42,14 +42,14 @@ def test_interface(self): def test_ctor(self): tm = self._makeOne() - self.assertTrue(tm._txn is None) + self.assertIsNone(tm._txn) self.assertEqual(len(tm._synchs), 0) def test_begin_wo_existing_txn_wo_synchs(self): from transaction._transaction import Transaction tm = self._makeOne() tm.begin() - self.assertTrue(isinstance(tm._txn, Transaction)) + self.assertIsInstance(tm._txn, Transaction) def test_begin_wo_existing_txn_w_synchs(self): from transaction._transaction import Transaction @@ -57,8 +57,8 @@ def test_begin_wo_existing_txn_w_synchs(self): synch = DummySynch() tm.registerSynch(synch) tm.begin() - self.assertTrue(isinstance(tm._txn, Transaction)) - self.assertTrue(tm._txn in synch._txns) + self.assertIsInstance(tm._txn, Transaction) + self.assertIn(tm._txn, synch._txns) def test_begin_w_existing_txn(self): class Existing: @@ -69,14 +69,14 @@ def abort(self): tm = self._makeOne() tm._txn = txn = Existing() tm.begin() - self.assertFalse(tm._txn is txn) + self.assertIsNot(tm._txn, txn) self.assertTrue(txn._aborted) def test_get_wo_existing_txn(self): from transaction._transaction import Transaction tm = self._makeOne() txn = tm.get() - self.assertTrue(isinstance(txn, Transaction)) + self.assertIsInstance(txn, Transaction) def test_get_w_existing_txn(self): class Existing: @@ -111,7 +111,7 @@ def test_registerSynch(self): synch = DummySynch() tm.registerSynch(synch) self.assertEqual(len(tm._synchs), 1) - self.assertTrue(synch in tm._synchs) + self.assertIn(synch, tm._synchs) def test_unregisterSynch(self): tm = self._makeOne() @@ -125,8 +125,8 @@ def test_unregisterSynch(self): tm.unregisterSynch(synch1) self.assertTrue(tm.registeredSynchs()) self.assertEqual(len(tm._synchs), 1) - self.assertFalse(synch1 in tm._synchs) - self.assertTrue(synch2 in tm._synchs) + self.assertNotIn(synch1, tm._synchs) + self.assertIn(synch2, tm._synchs) tm.unregisterSynch(synch2) self.assertFalse(tm.registeredSynchs()) @@ -255,7 +255,7 @@ def test_attempts_w_valid_count(self): tm = self._makeOne() found = list(tm.attempts(1)) self.assertEqual(len(found), 1) - self.assertTrue(found[0] is tm) + self.assertIs(found[0], tm) def test_attempts_stop_on_success(self): tm = self._makeOne() @@ -337,9 +337,9 @@ def test_attempts_w_default_count(self): found = list(tm.attempts()) self.assertEqual(len(found), 3) for attempt in found[:-1]: - self.assertTrue(isinstance(attempt, Attempt)) - self.assertTrue(attempt.manager is tm) - self.assertTrue(found[-1] is tm) + self.assertIsInstance(attempt, Attempt) + self.assertIs(attempt.manager, tm) + self.assertIs(found[-1], tm) def test_run(self): import transaction.interfaces @@ -880,7 +880,7 @@ def test_explicit_mode(self): with self.assertRaises(AlreadyInTransaction): tm.begin() - self.assertTrue(t is tm.get()) + self.assertIs(t, tm.get()) self.assertFalse(tm.isDoomed()) tm.doom() @@ -973,7 +973,7 @@ def sortKey(self): def check(self, method): if self.tracing: # pragma: no cover - print('{} calling method {}'.format(str(self.tracing), method)) + print(f'{str(self.tracing)} calling method {method}') if method in self.errors: raise TestTxnException("error %s" % method) diff --git a/src/transaction/tests/test__transaction.py b/src/transaction/tests/test__transaction.py index 4f1258c..ffb19b3 100644 --- a/src/transaction/tests/test__transaction.py +++ b/src/transaction/tests/test__transaction.py @@ -70,23 +70,23 @@ def test_ctor_defaults(self): logger = DummyLogger() with Monkey(_transaction, _LOGGER=logger): txn = self._makeOne() - self.assertTrue(isinstance(txn._synchronizers, WeakSet)) + self.assertIsInstance(txn._synchronizers, WeakSet) self.assertEqual(len(txn._synchronizers), 0) - self.assertTrue(txn._manager is None) + self.assertIsNone(txn._manager) self.assertEqual(txn.user, "") self.assertEqual(txn.description, "") - self.assertTrue(txn._savepoint2index is None) + self.assertIsNone(txn._savepoint2index) self.assertEqual(txn._savepoint_index, 0) self.assertEqual(txn._resources, []) self.assertEqual(txn._adapters, {}) self.assertEqual(txn._voted, {}) self.assertEqual(txn.extension, {}) - self.assertTrue(txn._extension is txn.extension) # legacy - self.assertTrue(txn.log is logger) + self.assertIs(txn._extension, txn.extension) # legacy + self.assertIs(txn.log, logger) self.assertEqual(len(logger._log), 1) self.assertEqual(logger._log[0][0], 'debug') self.assertEqual(logger._log[0][1], 'new transaction') - self.assertTrue(txn._failure_traceback is None) + self.assertIsNone(txn._failure_traceback) self.assertEqual(txn._before_commit, []) self.assertEqual(txn._after_commit, []) @@ -94,7 +94,7 @@ def test_ctor_w_syncs(self): from transaction.weakset import WeakSet synchs = WeakSet() txn = self._makeOne(synchronizers=synchs) - self.assertTrue(txn._synchronizers is synchs) + self.assertIs(txn._synchronizers, synchs) def test_isDoomed(self): from transaction._transaction import Status @@ -205,11 +205,11 @@ def test_savepoint_empty(self): with Monkey(_transaction, _LOGGER=logger): txn = self._makeOne() sp = txn.savepoint() - self.assertTrue(isinstance(sp, Savepoint)) - self.assertTrue(sp.transaction is txn) + self.assertIsInstance(sp, Savepoint) + self.assertIs(sp.transaction, txn) self.assertEqual(sp._savepoints, []) self.assertEqual(txn._savepoint_index, 1) - self.assertTrue(isinstance(txn._savepoint2index, WeakKeyDictionary)) + self.assertIsInstance(txn._savepoint2index, WeakKeyDictionary) self.assertEqual(txn._savepoint2index[sp], 1) def test_savepoint_non_optimistc_resource_wo_support(self): @@ -227,8 +227,8 @@ def test_savepoint_non_optimistc_resource_wo_support(self): txn._resources.append(resource) self.assertRaises(TypeError, txn.savepoint) self.assertEqual(txn.status, Status.COMMITFAILED) - self.assertTrue(isinstance(txn._failure_traceback, StringIO)) - self.assertTrue('TypeError' in txn._failure_traceback.getvalue()) + self.assertIsInstance(txn._failure_traceback, StringIO) + self.assertIn('TypeError', txn._failure_traceback.getvalue()) self.assertEqual(len(logger._log), 2) self.assertEqual(logger._log[0][0], 'error') self.assertTrue(logger._log[0][1].startswith('Error in abort')) @@ -336,7 +336,7 @@ def free(self, txn): mgr = txn._manager = _Mgr(txn) txn.commit() self.assertEqual(txn.status, Status.COMMITTED) - self.assertTrue(mgr._txn is None) + self.assertIsNone(mgr._txn) self.assertEqual(logger._log[0][0], 'debug') self.assertEqual(logger._log[0][1], 'commit') @@ -413,8 +413,8 @@ def afterCompletion(self, txn): logger._clear() txn.commit() for synch in synchs: - self.assertTrue(synch._before is txn) - self.assertTrue(synch._after is txn) + self.assertIs(synch._before, txn) + self.assertIs(synch._after, txn) def test_commit_w_afterCommitHooks(self): from transaction import _transaction @@ -511,8 +511,8 @@ def tpc_begin(self, txn): txn._resources.append(broken) self.assertRaises(ValueError, txn.commit) for synch in synchs: - self.assertTrue(synch._before is txn) - self.assertTrue(synch._after is txn) # called in _cleanup + self.assertIs(synch._before, txn) + self.assertIs(synch._after, txn) # called in _cleanup def test_commit_clears_resources(self): class DM: @@ -650,7 +650,7 @@ def test__commitResources_normal(self): for r in resources: self.assertTrue(r._b and r._c and r._v and r._f) self.assertFalse(r._a and r._x) - self.assertTrue(id(r) in txn._voted) + self.assertIn(id(r), txn._voted) self.assertEqual(len(logger._log), 2) self.assertEqual(logger._log[0][0], 'debug') self.assertEqual(logger._log[0][1], 'commit Resource: aaa') @@ -747,13 +747,13 @@ def test__commitResources_error_in_tpc_vote(self): for r in resources: self.assertTrue(r._b and r._c) if r._key == 'aaa': - self.assertTrue(id(r) in txn._voted) + self.assertIn(id(r), txn._voted) self.assertTrue(r._v) self.assertFalse(r._f) self.assertFalse(r._a) self.assertTrue(r._x) else: - self.assertFalse(id(r) in txn._voted) + self.assertNotIn(id(r), txn._voted) self.assertFalse(r._v) self.assertFalse(r._f) self.assertTrue(r._a and r._x) @@ -776,7 +776,7 @@ def test__commitResources_error_in_tpc_finish(self): self.assertRaises(ValueError, txn._commitResources) for r in resources: self.assertTrue(r._b and r._c and r._v) - self.assertTrue(id(r) in txn._voted) + self.assertIn(id(r), txn._voted) if r._key == 'aaa': self.assertTrue(r._f) else: @@ -811,7 +811,7 @@ def free(self, txn): mgr = txn._manager = _Mgr(txn) txn.abort() self.assertEqual(txn.status, Status.ACTIVE) - self.assertTrue(mgr._txn is None) + self.assertIsNone(mgr._txn) self.assertEqual(logger._log[0][0], 'debug') self.assertEqual(logger._log[0][1], 'abort') @@ -1026,8 +1026,8 @@ def abort(self, txn): t._resources.append(broken) self.assertRaises(ValueError, t.abort) for synch in synchs: - self.assertTrue(synch._before is t) - self.assertTrue(synch._after is t) # called in _cleanup + self.assertIs(synch._before, t) + self.assertIs(synch._after, t) # called in _cleanup self.assertIsNot(t._synchronizers, ws) def test_abort_synchronizer_error_w_resources(self): @@ -1068,8 +1068,8 @@ def map(self, func): t.abort() for synch in synchs.synchs: - self.assertTrue(synch._before is t) - self.assertTrue(synch._after is t) # called in _cleanup + self.assertIs(synch._before, t) + self.assertIs(synch._after, t) # called in _cleanup self.assertIsNot(t._synchronizers, synchs) self.assertTrue(resource._a) @@ -1389,7 +1389,7 @@ def test_setExtendedInfo_single(self): txn = self._makeOne() txn.setExtendedInfo('frob', 'qux') self.assertEqual(txn.extension, {'frob': 'qux'}) - self.assertTrue(txn._extension is txn._extension) # legacy + self.assertIs(txn._extension, txn._extension) # legacy def test_setExtendedInfo_multiple(self): txn = self._makeOne() @@ -1397,7 +1397,7 @@ def test_setExtendedInfo_multiple(self): txn.setExtendedInfo('baz', 'spam') txn.setExtendedInfo('frob', 'quxxxx') self.assertEqual(txn._extension, {'frob': 'quxxxx', 'baz': 'spam'}) - self.assertTrue(txn._extension is txn._extension) # legacy + self.assertIs(txn._extension, txn._extension) # legacy def test__extension_settable(self): # Because ZEO sets it. I'll fix ZEO, but maybe something else will @@ -1489,7 +1489,7 @@ def _callFUT(self, oid): return rm_key(oid) def test_miss(self): - self.assertTrue(self._callFUT(object()) is None) + self.assertIsNone(self._callFUT(object())) def test_hit(self): self.assertEqual(self._callFUT(Resource('zzz')), 'zzz') @@ -1515,8 +1515,8 @@ def test_ctor_w_savepoint_oblivious_resource_optimistic(self): resource = object() sp = self._makeOne(txn, True, resource) self.assertEqual(len(sp._savepoints), 1) - self.assertTrue(isinstance(sp._savepoints[0], NoRollbackSavepoint)) - self.assertTrue(sp._savepoints[0].datamanager is resource) + self.assertIsInstance(sp._savepoints[0], NoRollbackSavepoint) + self.assertIs(sp._savepoints[0].datamanager, resource) def test_ctor_w_savepoint_aware_resources(self): class _Aware: @@ -1527,10 +1527,10 @@ def savepoint(self): another = _Aware() sp = self._makeOne(txn, True, one, another) self.assertEqual(len(sp._savepoints), 2) - self.assertTrue(isinstance(sp._savepoints[0], _Aware)) - self.assertTrue(sp._savepoints[0] is one) - self.assertTrue(isinstance(sp._savepoints[1], _Aware)) - self.assertTrue(sp._savepoints[1] is another) + self.assertIsInstance(sp._savepoints[0], _Aware) + self.assertIs(sp._savepoints[0], one) + self.assertIsInstance(sp._savepoints[1], _Aware) + self.assertIs(sp._savepoints[1], another) def test_valid_wo_transacction(self): sp = self._makeOne(None, True, object()) @@ -1578,7 +1578,7 @@ def savepoint(self): resource = _GonnaRaise() sp = self._makeOne(txn, False, resource) self.assertRaises(ValueError, sp.rollback) - self.assertTrue(txn._raia is sp) + self.assertIs(txn._raia, sp) self.assertTrue(txn._sarce) @@ -1595,8 +1595,8 @@ def test_ctor(self): dm = object() txn = object() asp = self._makeOne(dm, txn) - self.assertTrue(asp.datamanager is dm) - self.assertTrue(asp.transaction is txn) + self.assertIs(asp.datamanager, dm) + self.assertIs(asp.transaction, txn) def test_rollback(self): class _DM: @@ -1614,8 +1614,8 @@ def _unjoin(self, datamanager): txn = _TXN() asp = self._makeOne(dm, txn) asp.rollback() - self.assertTrue(dm._aborted is txn) - self.assertTrue(txn._unjoin is dm) + self.assertIs(dm._aborted, txn) + self.assertIs(txn._unjoin, dm) class NoRollbackSavepointTests(unittest.TestCase): @@ -1630,7 +1630,7 @@ def _makeOne(self, datamanager): def test_ctor(self): dm = object() nrsp = self._makeOne(dm) - self.assertTrue(nrsp.datamanager is dm) + self.assertIs(nrsp.datamanager, dm) def test_rollback(self): dm = object() @@ -1697,7 +1697,6 @@ def _3(): def test_gh5(self): from transaction import _transaction - from transaction._compat import native_ buffer = _transaction._makeTracebackBuffer() @@ -1705,7 +1704,7 @@ def test_gh5(self): buffer.write(s) buffer.seek(0) - self.assertEqual(buffer.read(), native_(s, 'utf-8')) + self.assertEqual(buffer.read(), s) class Resource: diff --git a/src/transaction/tests/test_savepoint.py b/src/transaction/tests/test_savepoint.py index 1b4d9f4..f7f0de2 100644 --- a/src/transaction/tests/test_savepoint.py +++ b/src/transaction/tests/test_savepoint.py @@ -31,17 +31,17 @@ def testRollbackRollsbackDataManagersThatJoinedLater(self): dm2 = savepointsample.SampleSavepointDataManager() dm2['name'] = 'sally' - self.assertTrue('name' in dm) - self.assertTrue('job' in dm) - self.assertTrue('salary' in dm) - self.assertTrue('name' in dm2) + self.assertIn('name', dm) + self.assertIn('job', dm) + self.assertIn('salary', dm) + self.assertIn('name', dm2) sp1.rollback() - self.assertTrue('name' in dm) - self.assertFalse('job' in dm) - self.assertFalse('salary' in dm) - self.assertFalse('name' in dm2) + self.assertIn('name', dm) + self.assertNotIn('job', dm) + self.assertNotIn('salary', dm) + self.assertNotIn('name', dm2) def test_commit_after_rollback_for_dm_that_joins_after_savepoint(self): # There was a problem handling data managers that joined after a diff --git a/src/transaction/tests/test_weakset.py b/src/transaction/tests/test_weakset.py index ad32dea..2700e03 100644 --- a/src/transaction/tests/test_weakset.py +++ b/src/transaction/tests/test_weakset.py @@ -79,7 +79,7 @@ def test_as_weakref_list(self): del dummy3 gc.collect() refs = w.as_weakref_list() - self.assertTrue(isinstance(refs, list)) + self.assertIsInstance(refs, list) L = [x() for x in refs] # L is a list, but it does not have a guaranteed order. self.assertTrue(list, type(L)) diff --git a/tox.ini b/tox.ini index d50444f..b01282e 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,6 @@ minversion = 3.18 envlist = release-check lint - py37 py38 py39 py310 @@ -20,53 +19,50 @@ envlist = usedevelop = true package = wheel wheel_build_env = .pkg -pip_pre = py313: true deps = + setuptools <74 zope.testrunner - Sphinx setenv = - py312: VIRTUALENV_PIP=23.1.2 - py312: PIP_REQUIRE_VIRTUALENV=0 commands = zope-testrunner --test-path=src {posargs:-vc} sphinx-build -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest extras = test docs + +[testenv:setuptools-latest] +basepython = python3 +deps = + git+https://github.com/pypa/setuptools.git\#egg=setuptools + zope.testrunner + [testenv:release-check] description = ensure that the distribution is ready to release basepython = python3 skip_install = true deps = + setuptools <74 twine build check-manifest check-python-versions >= 0.20.0 wheel +commands_pre = commands = check-manifest - check-python-versions + check-python-versions --only setup.py,tox.ini,.github/workflows/tests.yml python -m build --sdist --no-isolation twine check dist/* [testenv:lint] +description = This env runs all linters configured in .pre-commit-config.yaml basepython = python3 skip_install = true deps = - isort - flake8 -commands = - isort --check-only --diff {toxinidir}/src {toxinidir}/setup.py - flake8 src setup.py - -[testenv:isort-apply] -basepython = python3 -skip_install = true + pre-commit commands_pre = -deps = - isort commands = - isort {toxinidir}/src {toxinidir}/setup.py [] + pre-commit run --all-files --show-diff-on-failure [testenv:docs] basepython = python3 @@ -81,30 +77,11 @@ basepython = python3 allowlist_externals = mkdir deps = - coverage + coverage[toml] zope.testrunner - Sphinx commands = mkdir -p {toxinidir}/parts/htmlcov coverage run -m zope.testrunner --test-path=src {posargs:-vc} coverage run -a -m sphinx -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest - coverage html --ignore-errors - coverage report --ignore-errors --show-missing --fail-under=99 - -[coverage:run] -branch = True -source = transaction - -[coverage:report] -precision = 2 -exclude_lines = - pragma: no cover - pragma: nocover - except ImportError: - raise NotImplementedError - if __name__ == '__main__': - self.fail - raise AssertionError - -[coverage:html] -directory = parts/htmlcov + coverage html + coverage report