Skip to content

Commit

Permalink
Drop support for Python 2.7 up to 3.6.
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Howitz committed May 24, 2023
1 parent f7b3a32 commit 4eafe1e
Show file tree
Hide file tree
Showing 13 changed files with 153 additions and 230 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

- Drop support for Python 2.7, 3.5, 3.6.

- Drop support for deprecated ``python setup.py test.``.


3.1.0 (2023-03-17)
==================
Expand Down
16 changes: 4 additions & 12 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,7 @@ def _read_file(filename):
"Operating System :: Microsoft :: Windows",
"Operating System :: Unix",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
Expand All @@ -57,8 +53,8 @@ def _read_file(filename):
"Programming Language :: Python :: Implementation :: PyPy",
"Framework :: ZODB",
],
author="Zope Corporation",
author_email="zodb-dev@zope.org",
author="Zope Foundation and Contributors",
author_email="zodb-dev@zope.dev",
url="https://github.com/zopefoundation/transaction",
project_urls={
'Issue Tracker': ('https://github.com/zopefoundation/'
Expand All @@ -71,17 +67,13 @@ def _read_file(filename):
package_dir={'': 'src'},
include_package_data=True,
zip_safe=False,
test_suite="transaction.tests",
tests_require=tests_require,
python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*',
python_requires='>=3.7',
install_requires=[
'zope.interface',
],
extras_require={
'docs': ['Sphinx', 'repoze.sphinx.autointerface'],
'test': tests_require,
'testing': ['nose', 'coverage'] + tests_require,
'testing': ['coverage'] + tests_require,
},
entry_points="""\
"""
)
6 changes: 3 additions & 3 deletions src/transaction/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
#: A thread-safe `~ITransactionManager`
from transaction._manager import ThreadTransactionManager

# NB: "with transaction:" does not work under Python 3 because they worked
# NB: "with transaction:" does not work because they worked
# really hard to break looking up special methods like __enter__ and __exit__
# via getattr and getattribute; see http://bugs.python.org/issue12022. On
# Python 3, you must use ``with transaction.manager`` instead.
# via getattr and getattribute; see http://bugs.python.org/issue12022.
# You must use ``with transaction.manager`` instead.

#: The default transaction manager (a `~.ThreadTransactionManager`). All other
#: functions in this module refer to this object.
Expand Down
75 changes: 5 additions & 70 deletions src/transaction/_compat.py
Original file line number Diff line number Diff line change
@@ -1,75 +1,10 @@
import sys


PY3 = sys.version_info[0] == 3
JYTHON = sys.platform.startswith('java')

if PY3: # pragma: no cover
text_type = str
else: # pragma: no cover
# py2
text_type = unicode # noqa: F821 undefined name 'unicode'


def text_(s):
if not isinstance(s, text_type): # pragma: no cover
if not isinstance(s, str): # pragma: no cover
s = s.decode('utf-8')
return s


if PY3: # pragma: no cover
def native_(s, encoding='latin-1', errors='strict'):
if isinstance(s, text_type):
return s
return str(s, encoding, errors)
else: # pragma: no cover
def native_(s, encoding='latin-1', errors='strict'):
if isinstance(s, text_type):
return s.encode(encoding, errors)
return str(s)

if PY3: # pragma: no cover
from io import StringIO
else: # pragma: no cover
from io import BytesIO

# Prevent crashes in IPython when writing tracebacks if a commit fails
# ref:
# https://github.com/ipython/ipython/issues/9126#issuecomment-174966638

class StringIO(BytesIO):
def write(self, s):
s = native_(s, encoding='utf-8')
super(StringIO, self).write(s)


if PY3: # pragma: no cover
def reraise(tp, value, tb=None):
if value.__traceback__ is not tb: # pragma: no cover
raise value.with_traceback(tb)
raise value

else: # pragma: no cover
def exec_(code, globs=None, locs=None):
"""Execute code in a namespace."""
if globs is None:
frame = sys._getframe(1)
globs = frame.f_globals
if locs is None:
locs = frame.f_locals
del frame
elif locs is None:
locs = globs
exec("""exec code in globs, locs""")

exec_("""def reraise(tp, value, tb=None):
raise tp, value, tb
""")

# isort: off

try: # pragma: no cover
from threading import get_ident as get_thread_ident
except ImportError: # pragma: no cover
# PY2
from thread import get_ident as get_thread_ident # noqa: F401 unused
def native_(s, encoding='latin-1', errors='strict'):
if isinstance(s, str):
return s
return str(s, encoding, errors)
21 changes: 9 additions & 12 deletions src/transaction/_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

from zope.interface import implementer

from transaction._compat import reraise
from transaction._compat import text_
from transaction._transaction import Transaction
from transaction.interfaces import AlreadyInTransaction
Expand Down Expand Up @@ -60,7 +59,7 @@ def _new_transaction(txn, synchs):


@implementer(ITransactionManager)
class TransactionManager(object):
class TransactionManager:
"""Single-thread implementation of
`~transaction.interfaces.ITransactionManager`.
"""
Expand Down Expand Up @@ -183,20 +182,18 @@ def run(self, func=None, tries=3):
if tries <= 0:
raise ValueError("tries must be > 0")

# These are ordinarily native strings, but that's
# These are ordinarily strings, but that's
# not required. A callable class could override them
# to anything, and a Python 2.7 file could have
# imported `from __future__ import unicode_literals`
# which gets unicode docstrings.
# to anything.
name = func.__name__
doc = func.__doc__

name = text_(name) if name else u''
doc = text_(doc) if doc else u''
name = text_(name) if name else ''
doc = text_(doc) if doc else ''

if name and name != u'_':
if name and name != '_':
if doc:
doc = name + u'\n\n' + doc
doc = name + '\n\n' + doc
else:
doc = name

Expand Down Expand Up @@ -290,7 +287,7 @@ def run(self, func=None, tries=3):
return self.manager.run(func, tries)


class Attempt(object):
class Attempt:

success = False

Expand All @@ -302,7 +299,7 @@ def _retry_or_raise(self, t, v, tb):
self.manager.abort()
if retry:
return retry # suppress the exception if necessary
reraise(t, v, tb) # otherwise reraise the exception
raise v.with_traceback(tb) # otherwise reraise the exception

def __enter__(self):
return self.manager.__enter__()
Expand Down
47 changes: 23 additions & 24 deletions src/transaction/_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,15 @@
############################################################################
import logging
import sys
import threading
import traceback
import warnings
import weakref
from io import StringIO

from zope.interface import implementer

from transaction import interfaces
from transaction._compat import StringIO
from transaction._compat import get_thread_ident
from transaction._compat import reraise
from transaction._compat import text_type
from transaction.interfaces import TransactionFailedError
from transaction.weakset import WeakSet

Expand All @@ -45,10 +43,10 @@ def _makeTracebackBuffer(): # pragma NO COVER
def _makeLogger(): # pragma NO COVER
if _LOGGER is not None:
return _LOGGER
return logging.getLogger("txn.%d" % get_thread_ident())
return logging.getLogger("txn.%d" % threading.get_ident())


class Status(object):
class Status:
# ACTIVE is the initial state.
ACTIVE = "Active"

Expand All @@ -62,15 +60,15 @@ class Status(object):
COMMITFAILED = "Commit failed"


class _NoSynchronizers(object):
class _NoSynchronizers:

@staticmethod
def map(_f):
"""Do nothing."""


@implementer(interfaces.ITransaction)
class Transaction(object):
class Transaction:
"""Default implementation of `~transaction.interfaces.ITransaction`."""

# Assign an index to each savepoint so we can invalidate later savepoints
Expand All @@ -84,8 +82,8 @@ class Transaction(object):

# Meta data. extended_info is also metadata, but is initialized to an
# empty dict in __init__.
_user = u""
_description = u""
_user = ""
_description = ""

def __init__(self, synchronizers=None, manager=None):
self.status = Status.ACTIVE
Expand Down Expand Up @@ -190,8 +188,9 @@ def join(self, resource):
self.status is not Status.DOOMED):
# TODO: Should it be possible to join a committing transaction?
# I think some users want it.
raise ValueError("expected txn status %r or %r, but it's %r" % (
Status.ACTIVE, Status.DOOMED, self.status))
raise ValueError(
f"expected txn status {Status.ACTIVE!r} or {Status.DOOMED!r},"
f" but it's {self.status!r}")
self._resources.append(resource)

if self._savepoint2index:
Expand Down Expand Up @@ -280,7 +279,7 @@ def commit(self):
try:
t, v, tb = self._saveAndGetCommitishError()
self._callAfterCommitHooks(status=False)
reraise(t, v, tb)
raise v.with_traceback(tb)
finally:
del t, v, tb
else:
Expand Down Expand Up @@ -314,7 +313,7 @@ def _saveAndRaiseCommitishError(self):
tb = None
try:
t, v, tb = self._saveAndGetCommitishError()
reraise(t, v, tb)
raise v.with_traceback(tb)
finally:
del t, v, tb

Expand Down Expand Up @@ -454,7 +453,7 @@ def _commitResources(self):
self._cleanup(L)
finally:
self._synchronizers.map(lambda s: s.afterCompletion(self))
reraise(t, v, tb)
raise v.with_traceback(tb)
finally:
del t, v, tb

Expand Down Expand Up @@ -568,7 +567,7 @@ def abort(self):
self.log.debug("abort")

if tb is not None:
reraise(t, v, tb)
raise v.with_traceback(tb)
finally:
self._free()
del t, v, tb
Expand All @@ -578,13 +577,13 @@ def note(self, text):
if text is not None:
text = text_or_warn(text).strip()
if self.description:
self.description += u"\n" + text
self.description += "\n" + text
else:
self.description = text

def setUser(self, user_name, path=u"/"):
def setUser(self, user_name, path="/"):
"""See `~transaction.interfaces.ITransaction`."""
self.user = u"%s %s" % (text_or_warn(path), text_or_warn(user_name))
self.user = "{} {}".format(text_or_warn(path), text_or_warn(user_name))

def setExtendedInfo(self, name, value):
"""See `~transaction.interfaces.ITransaction`."""
Expand All @@ -604,7 +603,7 @@ def rm_key(rm):


@implementer(interfaces.ISavepoint)
class Savepoint(object):
class Savepoint:
"""Implementation of `~transaction.interfaces.ISavepoint`, a transaction
savepoint.
Expand Down Expand Up @@ -648,7 +647,7 @@ def rollback(self):
transaction._saveAndRaiseCommitishError() # reraises!


class AbortSavepoint(object):
class AbortSavepoint:

def __init__(self, datamanager, transaction):
self.datamanager = datamanager
Expand All @@ -659,7 +658,7 @@ def rollback(self):
self.transaction._unjoin(self.datamanager)


class NoRollbackSavepoint(object):
class NoRollbackSavepoint:

def __init__(self, datamanager):
self.datamanager = datamanager
Expand All @@ -669,11 +668,11 @@ def rollback(self):


def text_or_warn(s):
if isinstance(s, text_type):
if isinstance(s, str):
return s

warnings.warn("Expected text", DeprecationWarning, stacklevel=3)
if isinstance(s, bytes):
return s.decode('utf-8', 'replace')
else:
return text_type(s)
return str(s)
Loading

0 comments on commit 4eafe1e

Please sign in to comment.