From a5fb52968adb1b7fa54fc5b732597b8fc71961f5 Mon Sep 17 00:00:00 2001 From: Martin Gallwey Date: Wed, 9 Sep 2026 15:49:26 +0100 Subject: [PATCH 1/2] Drop Python 2 support; require Python >= 3.6 Removes the isP2/StandardError/unicode compatibility shims scattered across session.py, crypt.py, encodedsession.py, exception.py, and datatype.py, along with the vendored dbapi20 test suite's Python 2 branches. Declares python_requires>=3.6 in setup.py, drops the ipaddress backport dependency (stdlib since 3.3) and the Python 2 classifiers, and updates README/CONTRIBUTING accordingly. Also trims .pylint-rc of disable entries for Python2-only lint checks that no longer exist in current pylint. --- .pylint-rc | 75 +------------------------------------ CONTRIBUTING.rst | 5 +-- README.rst | 4 +- pynuodb/crypt.py | 78 ++++++++------------------------------- pynuodb/datatype.py | 8 ++-- pynuodb/encodedsession.py | 5 +-- pynuodb/exception.py | 28 +++----------- pynuodb/session.py | 20 +++------- setup.py | 5 +-- tests/dbapi20.py | 25 ++++--------- tests/nuodb_blob_test.py | 7 +--- tests/perf/compare.py | 2 - 12 files changed, 45 insertions(+), 217 deletions(-) diff --git a/.pylint-rc b/.pylint-rc index 96dbb96..2cc2a94 100644 --- a/.pylint-rc +++ b/.pylint-rc @@ -54,86 +54,13 @@ confidence= # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=print-statement, - parameter-unpacking, - unpacking-in-except, - old-raise-syntax, - backtick, - long-suffix, - old-ne-operator, - old-octal-literal, - import-star-module-level, - non-ascii-bytes-literal, - invalid-unicode-literal, - raw-checker-failed, +disable=raw-checker-failed, bad-inline-option, locally-disabled, - locally-enabled, file-ignored, suppressed-message, useless-suppression, deprecated-pragma, - apply-builtin, - basestring-builtin, - buffer-builtin, - cmp-builtin, - coerce-builtin, - execfile-builtin, - file-builtin, - long-builtin, - raw_input-builtin, - reduce-builtin, - standarderror-builtin, - unicode-builtin, - xrange-builtin, - coerce-method, - delslice-method, - getslice-method, - setslice-method, - no-absolute-import, - old-division, - dict-iter-method, - dict-view-method, - next-method-called, - metaclass-assignment, - indexing-exception, - raising-string, - reload-builtin, - oct-method, - hex-method, - nonzero-method, - cmp-method, - input-builtin, - round-builtin, - intern-builtin, - unichr-builtin, - map-builtin-not-iterating, - zip-builtin-not-iterating, - range-builtin-not-iterating, - filter-builtin-not-iterating, - using-cmp-argument, - eq-without-hash, - div-method, - idiv-method, - rdiv-method, - exception-message-attribute, - invalid-str-codec, - sys-max-int, - bad-python3-import, - deprecated-string-function, - deprecated-str-translate-call, - deprecated-itertools-function, - deprecated-types-field, - next-method-defined, - dict-items-not-iterating, - dict-keys-not-iterating, - dict-values-not-iterating, - deprecated-operator-function, - deprecated-urllib-function, - xreadlines-attribute, - deprecated-sys-function, - exception-escape, - comprehension-escape, # Removed in latest pylint and fights with flake8 etc. bad-continuation, # Added for pynuodb diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index a020c73..8412397 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -8,15 +8,14 @@ Requirements Developers should use virtualenv to maintain multiple side-by-side environments to test with. Specifically, all contributions must be -tested with both 2.7.6 and 3.4.3 to ensure the library is syntax -compatible between the two versions. +tested with Python >= 3.6 to ensure the library remains compatible. Dependencies ~~~~~~~~~~~~ Here was my basic setup on Mac OS X: - | virtualenv --python=/usr/bin/python2.7 ~/.venv/pynuodb + | virtualenv --python=/usr/bin/python3 ~/.venv/pynuodb | source ~/.venv/pynuodb/bin/activate | pip install mock | pip install nose diff --git a/README.rst b/README.rst index c91d9b4..9d03324 100644 --- a/README.rst +++ b/README.rst @@ -14,11 +14,11 @@ with limited support and testing from NuoDB. Requirements ------------ -* Python >= 2.7 +* Python >= 3.6 - Recommended Python version >= 3.8 - - Tested with CPython_ 2.7 and 3.6 + - Tested with CPython_ 3.6 * NuoDB_ >= 6.0.2 diff --git a/pynuodb/crypt.py b/pynuodb/crypt.py index 028f3a4..38fd00d 100644 --- a/pynuodb/crypt.py +++ b/pynuodb/crypt.py @@ -32,7 +32,6 @@ import hashlib import random import binascii -import sys try: from typing import Optional # pylint: disable=unused-import @@ -57,8 +56,6 @@ arc4Imported = False AESImported = False -isP2 = sys.version[0] == '2' - def get_ciphers(): # type: () -> str @@ -66,68 +63,28 @@ def get_ciphers(): return ("AES-256-CTR,AES-128-CTR," if AESImported else '') + "RC4" -# We use a bytearray for our sending buffer because we need to construct it. -# If we were using only Python3, then our received data could be stored in a -# bytes object which might be slightly more efficient. But in Python2 a bytes -# object is the same as a string so it can't be portable: if use bytes we need -# different code to extract it on P2 vs. P3. -# -# Instead, we'll use a bytearray for the received data as well as the sending -# data for as long as we need to support Python 2. - -if isP2: - def bytesToArray(data): - # type: (bytes) -> bytearray - """Convert bytes to a bytearray. - - On Python 2 bytes is a string so we have to ord each character. - """ - return bytearray([ord(c) for c in data]) # type: ignore - - def arrayToStr(data): - # type: (bytearray) -> str - """Convert a bytearray to a str. - - On Python 2 we can just use the str() constructor. If we use decode - we get back a unicode string not a string.. - """ - return str(data) +def bytesToArray(data): + # type: (bytes) -> bytearray + """Convert bytes to a bytearray.""" + return bytearray(data) - def hexstrToBytes(hexstr): - # type: (Optional[str]) -> Optional[bytes] - """Convert a hex string to bytes.""" - return binascii.unhexlify(hexstr) if hexstr is not None else None -else: - def bytesToArray(data): - # type: (bytes) -> bytearray - """Convert bytes to a bytearray. - On Python 3 bytes is a binary string so we can just convert it. - """ - return bytearray(data) +def arrayToStr(data): + # type: (bytearray) -> str + """Convert a bytearray to a str: assume UTF-8 always.""" + return data.decode('utf-8') - def arrayToStr(data): - # type: (bytearray) -> str - """Convert a bytearray to a str. - On Python 3 we must decode: assume UTF-8 always. - """ - return data.decode('utf-8') - - def hexstrToBytes(hexstr): - # type: (Optional[str]) -> Optional[bytes] - """Convert a hex string to bytes.""" - return bytes.fromhex(hexstr) if hexstr is not None else None # pylint: disable=no-member +def hexstrToBytes(hexstr): + # type: (Optional[str]) -> Optional[bytes] + """Convert a hex string to bytes.""" + return bytes.fromhex(hexstr) if hexstr is not None else None def toHex(bigInt): # type: (int) -> str """Convert an integer into a hex string.""" - if isP2: - hexStr = (hex(bigInt)[2:])[:-1] - else: - # Python 3 will no longer insert an L for type formatting - hexStr = hex(bigInt)[2:] + hexStr = hex(bigInt)[2:] # Some platforms assume hex strings are even length: add padding if needed if len(hexStr) % 2 == 1: hexStr = '0' + hexStr @@ -435,13 +392,8 @@ def transform(self, data): # type: (bytes) -> bytes """Perform a byte by byte RC4 transform on the stream. - Python 2: - automatically handles encoding bytes into an extended ASCII - encoding [0,255] w/ 1 byte per character - - Python 3: - bytes objects must be converted into extended ASCII, latin-1 uses - the desired range of [0,255] + bytes objects must be converted into extended ASCII, latin-1 uses + the desired range of [0,255]. For utf-8 strings (characters consisting of more than 1 byte) the values are broken into 1 byte sections and shifted. The RC4 stream diff --git a/pynuodb/datatype.py b/pynuodb/datatype.py index dca144d..57b87a5 100644 --- a/pynuodb/datatype.py +++ b/pynuodb/datatype.py @@ -81,7 +81,6 @@ def timezone_aware(tstamp, tz_info): """Return a Timestamp that uses the provided timezone.""" return tz_info.localize(tstamp, is_dst=None) # type: ignore[attr-defined] -isP2 = sys.version[0] == '2' TICKSDAY = 86400 LOCALZONE = tzlocal.get_localzone() @@ -104,8 +103,7 @@ def __new__(cls, data): # I can't figure out how to get mypy to be OK with this. if isinstance(data, bytearray): return bytes.__new__(cls, data) # type: ignore - # In Python2 there's no distinction between str and bytes :( - if isinstance(data, str) and not isP2: + if isinstance(data, str): return bytes.__new__(cls, data.encode('latin-1')) # type: ignore return bytes.__new__(cls, data) # type: ignore @@ -114,9 +112,9 @@ def __str__(self): # This is pretty terrible but it's what the old version did. # What does it really mean to run str(Binary)? That should probably # be illegal, but I'm sure lots of code does "%s" % (Binary(x)) or - # the equivalent. In Python 3 we have to remove the 'b' prefix too. + # the equivalent. We have to remove the 'b' prefix too. # I'll leave this for consideration at some future time. - return repr(self)[1:-1] if isP2 else repr(self)[2:-1] + return repr(self)[2:-1] @property def string(self): diff --git a/pynuodb/encodedsession.py b/pynuodb/encodedsession.py index e3b3ae1..80a265d 100644 --- a/pynuodb/encodedsession.py +++ b/pynuodb/encodedsession.py @@ -45,7 +45,6 @@ # fallback to pytz if python < 3.9 from pytz import timezone as ZoneInfo -isP2 = sys.version[0] == '2' REMOVE_FORMAT = 0 @@ -98,8 +97,6 @@ class EncodedSession(session.Session): # pylint: disable=too-many-public-method closed = False __output = None # type: bytearray - # If we did not need to be compatible with Python 2 this should be bytes - # But in Python 2, bytes is just another name for str, so use bytearray __input = None # type: bytearray __inpos = 0 # type: int __encryption = True @@ -645,7 +642,7 @@ def putString(self, value): :type value: str """ - data = bytes(value) if isP2 else value.encode('utf-8') # type: ignore + data = value.encode('utf-8') length = len(data) if length < 40: self.__output.append(protocol.UTF8LEN0 + length) diff --git a/pynuodb/exception.py b/pynuodb/exception.py index f0c2e58..314b71c 100644 --- a/pynuodb/exception.py +++ b/pynuodb/exception.py @@ -6,8 +6,6 @@ See the LICENSE file provided with this software. """ -import sys - try: from typing import Iterable, NoReturn # pylint: disable=unused-import except ImportError: @@ -20,34 +18,20 @@ 'ProgrammingError', 'NotSupportedError', 'EndOfStream', 'db_error_handler'] -isP2 = sys.version[0] == '2' - # These exceptions are defined by PEP 249. # See the PEP for a fuller description of each one. -if isP2: - class Warning(StandardError): # type: ignore # pylint: disable=redefined-builtin - """Raised for important warnings.""" - - pass +class Warning(Exception): # pylint: disable=redefined-builtin + """Raised for important warnings.""" - class Error(StandardError): # type: ignore - """The base class of all other error exceptions.""" - - pass -else: - # Mypy is not smart enough to realize we'll only define one set of classes - # so disable type checking - class Warning(Exception): # type: ignore # pylint: disable=redefined-builtin - """Raised for important warnings.""" + pass - pass - class Error(Exception): # type: ignore - """The base class of all other error exceptions.""" +class Error(Exception): + """The base class of all other error exceptions.""" - pass + pass class InterfaceError(Error): diff --git a/pynuodb/session.py b/pynuodb/session.py index e870a21..d47fc9b 100644 --- a/pynuodb/session.py +++ b/pynuodb/session.py @@ -16,15 +16,10 @@ import socket import struct -import sys from ipaddress import ip_address +from urllib.parse import urlparse import xml.etree.ElementTree as ET -try: - from urllib.parse import urlparse -except ImportError: - from urlparse import urlparse # type: ignore - try: from typing import Dict, Generator, Iterable, Mapping # pylint: disable=unused-import from typing import Optional, Tuple, Union # pylint: disable=unused-import @@ -35,8 +30,6 @@ from . import crypt -isP2 = sys.version[0] == '2' - NUODB_PORT = 48004 @@ -81,7 +74,7 @@ def strToBool(s): def xmlToString(root): # type: (ET.Element) -> str """Convert an XML Element to a str.""" - return ET.tostring(root, encoding='utf-8' if isP2 else 'unicode') + return ET.tostring(root, encoding='unicode') class Session(object): @@ -180,10 +173,7 @@ def session_options(options): @staticmethod def _to_ipaddr(addr): # type: (str) -> Tuple[str, int] - if isP2 and not isinstance(addr, unicode): # type: ignore - ipaddr = ip_address(unicode(addr, 'utf_8')) # type: ignore - else: - ipaddr = ip_address(addr) + ipaddr = ip_address(addr) return (str(ipaddr), ipaddr.version) def _parse_addr(self, addr, ipver): @@ -464,8 +454,8 @@ def send(self, message): if isinstance(message, bytearray): data = bytes(message) - elif isinstance(message, bytes) or isP2: - data = message # type: ignore + elif isinstance(message, bytes): + data = message elif isinstance(message, str): data = message.encode('utf-8') else: diff --git a/setup.py b/setup.py index 8b2d0e9..7317e35 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,8 @@ url='https://github.com/nuodb/nuodb-python', license='BSD License', long_description=open(readme).read(), - install_requires=['pytz>=2015.4', 'ipaddress', 'tzlocal', 'jdcal'], + python_requires='>=3.6', + install_requires=['pytz>=2015.4', 'tzlocal', 'jdcal'], extras_require=dict(crypto='cryptography>=2.6.1'), classifiers=[ 'Development Status :: 5 - Production/Stable', @@ -52,8 +53,6 @@ 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: SQL', diff --git a/tests/dbapi20.py b/tests/dbapi20.py index 5f95c52..9603d9f 100644 --- a/tests/dbapi20.py +++ b/tests/dbapi20.py @@ -15,21 +15,14 @@ import unittest import time -import sys -if sys.version[0] >= '3': #python 3.x - _BaseException = Exception - def _failUnless(self, expr, msg=None): - self.assertTrue(expr, msg) -else: #python 2.x - from exceptions import StandardError as _BaseException - def _failUnless(self, expr, msg=None): - self.failUnless(expr, msg) ## deprecated since Python 2.6 +_BaseException = Exception + +def _failUnless(self, expr, msg=None): + self.assertTrue(expr, msg) def str2bytes(sval): - if sys.version_info < (3,0) and isinstance(sval, str): - sval = sval.decode("latin1") - return sval.encode("latin1") #python 3 make unicode into bytes + return sval.encode("latin1") class DatabaseAPI20Test(unittest.TestCase): ''' Test a database self.driver for DB API 2.0 compatibility. @@ -149,12 +142,8 @@ def test_paramstyle(self): def test_Exceptions(self): # Make sure required exceptions exist, and are in the # defined heirarchy. - if sys.version[0] == '3': #under Python 3 StardardError no longer exists - self.assertTrue(issubclass(self.driver.Warning,Exception)) - self.assertTrue(issubclass(self.driver.Error,Exception)) - else: - self.failUnless(issubclass(self.driver.Warning,StandardError)) - self.failUnless(issubclass(self.driver.Error,StandardError)) + self.assertTrue(issubclass(self.driver.Warning,Exception)) + self.assertTrue(issubclass(self.driver.Error,Exception)) _failUnless(self, issubclass(self.driver.InterfaceError,self.driver.Error) diff --git a/tests/nuodb_blob_test.py b/tests/nuodb_blob_test.py index 39828ed..1092915 100644 --- a/tests/nuodb_blob_test.py +++ b/tests/nuodb_blob_test.py @@ -8,11 +8,8 @@ import struct import pynuodb -import sys from . import nuodb_base -systemVersion = sys.version[0] - class TestNuoDBBlob(nuodb_base.NuoBase): def test_blob_prepared(self): @@ -24,9 +21,7 @@ def test_blob_prepared(self): cursor.execute("SELECT ? FROM DUAL", [pynuodb.Binary(binary_data)]) row = cursor.fetchone() - currentRow = str(row[0]) - if systemVersion == '3': - currentRow = bytes(currentRow, 'latin-1') + currentRow = bytes(str(row[0]), 'latin-1') array2 = struct.unpack('hhl', currentRow) assert len(array2) == 3 assert array2[2] == 3 diff --git a/tests/perf/compare.py b/tests/perf/compare.py index 82d83e7..5857e73 100644 --- a/tests/perf/compare.py +++ b/tests/perf/compare.py @@ -6,8 +6,6 @@ --fail-threshold (default 10%), so CI turns a real regression into a failed build. Improvements never fail the build. """ -from __future__ import print_function - import argparse import json import sys From e6c8044e080f63fedb2d70a9b97603fbdb24eeef Mon Sep 17 00:00:00 2001 From: Martin Gallwey Date: Wed, 9 Sep 2026 15:49:26 +0100 Subject: [PATCH 2/2] Raise minimum Python to 3.9 to match modern Cython requirements Cython 3.3.0 (the current release) requires Python >= 3.9, so raise the driver's floor to match ahead of adding Cython-accelerated code. This also lets us drop the pytz-based zoneinfo.ZoneInfo fallback that existed only to cover Python < 3.9, since it is now always available from the stdlib. --- CONTRIBUTING.rst | 2 +- README.rst | 6 ++--- pynuodb/datatype.py | 51 +++++++++++++-------------------------- pynuodb/encodedsession.py | 10 +------- requirements.txt | 1 - setup.py | 10 +++++--- tests/mock_tzs.py | 22 +++-------------- 7 files changed, 32 insertions(+), 70 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 8412397..cc340b9 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -8,7 +8,7 @@ Requirements Developers should use virtualenv to maintain multiple side-by-side environments to test with. Specifically, all contributions must be -tested with Python >= 3.6 to ensure the library remains compatible. +tested with Python >= 3.9 to ensure the library remains compatible. Dependencies ~~~~~~~~~~~~ diff --git a/README.rst b/README.rst index 9d03324..baab8cf 100644 --- a/README.rst +++ b/README.rst @@ -14,11 +14,9 @@ with limited support and testing from NuoDB. Requirements ------------ -* Python >= 3.6 +* Python >= 3.9 - - Recommended Python version >= 3.8 - - - Tested with CPython_ 3.6 + - Tested with CPython_ 3.9 * NuoDB_ >= 6.0.2 diff --git a/pynuodb/datatype.py b/pynuodb/datatype.py index 57b87a5..215a3ff 100644 --- a/pynuodb/datatype.py +++ b/pynuodb/datatype.py @@ -30,10 +30,10 @@ 'TimestampToTicks', 'Binary', 'Vector', 'STRING', 'BINARY', 'NUMBER', 'DATETIME', 'ROWID', 'VECTOR_DOUBLE', 'TypeObjectFromNuodb'] -import sys import decimal from datetime import datetime as Timestamp, date as Date, time as Time from datetime import timedelta as TimeDelta +from datetime import timezone from datetime import tzinfo # pylint: disable=unused-import from pynuodb import protocol @@ -47,39 +47,22 @@ from .exception import DataError from .calendar import ymd2day, day2ymd -# zoneinfo.ZoneInfo is preferred but not introduced until python3.9 -if sys.version_info >= (3, 9): - # used for python>=3.9 with support for zoneinfo.ZoneInfo - from datetime import timezone # pylint: disable=no-name-in-module,ungrouped-imports - UTC = timezone.utc - - def utc_TimeStamp(year, month, day, hour=0, minute=0, second=0, microsecond=0): - # type: (int, int, int, int, int, int, int) -> Timestamp - """Return a Timestamp UTC timezone.""" - return Timestamp(year=year, month=month, day=day, - hour=hour, minute=minute, second=second, - microsecond=microsecond, tzinfo=UTC) - - def timezone_aware(tstamp, tz_info): - # type: (Timestamp, tzinfo) -> Timestamp - """Return a Timestamp that uses the provided timezone.""" - return tstamp.replace(tzinfo=tz_info) - -else: - # used for python<3.9 without support for zoneinfo.ZoneInfo - from pytz import utc as UTC - - def utc_TimeStamp(year, month, day, hour=0, minute=0, second=0, microsecond=0): - # type: (int, int, int, int, int, int, int) -> Timestamp - """Return a Timestamp UTC timezone.""" - dt = Timestamp(year=year, month=month, day=day, - hour=hour, minute=minute, second=second, microsecond=microsecond) - return UTC.localize(dt, is_dst=None) - - def timezone_aware(tstamp, tz_info): - # type: (Timestamp, tzinfo) -> Timestamp - """Return a Timestamp that uses the provided timezone.""" - return tz_info.localize(tstamp, is_dst=None) # type: ignore[attr-defined] +UTC = timezone.utc + + +def utc_TimeStamp(year, month, day, hour=0, minute=0, second=0, microsecond=0): + # type: (int, int, int, int, int, int, int) -> Timestamp + """Return a Timestamp UTC timezone.""" + return Timestamp(year=year, month=month, day=day, + hour=hour, minute=minute, second=second, + microsecond=microsecond, tzinfo=UTC) + + +def timezone_aware(tstamp, tz_info): + # type: (Timestamp, tzinfo) -> Timestamp + """Return a Timestamp that uses the provided timezone.""" + return tstamp.replace(tzinfo=tz_info) + TICKSDAY = 86400 LOCALZONE = tzlocal.get_localzone() diff --git a/pynuodb/encodedsession.py b/pynuodb/encodedsession.py index 80a265d..a963053 100644 --- a/pynuodb/encodedsession.py +++ b/pynuodb/encodedsession.py @@ -15,9 +15,9 @@ import uuid import struct import decimal -import sys import threading import datetime # pylint: disable=unused-import +from zoneinfo import ZoneInfo # pylint: disable=import-error try: from typing import Any, Collection, Dict, List # pylint: disable=unused-import @@ -37,14 +37,6 @@ from . import result_set from .datatype import LOCALZONE_NAME -# ZoneInfo is preferred but not introduced until 3.9 -if sys.version_info >= (3, 9): - # preferred python >= 3.9 - from zoneinfo import ZoneInfo # pylint: disable=import-error -else: - # fallback to pytz if python < 3.9 - from pytz import timezone as ZoneInfo - REMOVE_FORMAT = 0 diff --git a/requirements.txt b/requirements.txt index 93ede57..0e29061 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ tzlocal -pytz>=2015.4 jdcal>=1.4.1 diff --git a/setup.py b/setup.py index 7317e35..9d313fe 100644 --- a/setup.py +++ b/setup.py @@ -43,8 +43,8 @@ url='https://github.com/nuodb/nuodb-python', license='BSD License', long_description=open(readme).read(), - python_requires='>=3.6', - install_requires=['pytz>=2015.4', 'tzlocal', 'jdcal'], + python_requires='>=3.9', + install_requires=['tzlocal', 'jdcal'], extras_require=dict(crypto='cryptography>=2.6.1'), classifiers=[ 'Development Status :: 5 - Production/Stable', @@ -54,7 +54,11 @@ 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', + '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 :: SQL', 'Topic :: Database :: Front-Ends', ], diff --git a/tests/mock_tzs.py b/tests/mock_tzs.py index 75a6a11..fa5fe6b 100644 --- a/tests/mock_tzs.py +++ b/tests/mock_tzs.py @@ -6,24 +6,14 @@ See the LICENSE file provided with this software. """ -import sys import datetime -import pytz - -try: - from zoneinfo import ZoneInfo - HAS_ZONEINFO = True -except ImportError: - HAS_ZONEINFO = False +from zoneinfo import ZoneInfo try: import typing # Define a type for mypy/static typing if typing.TYPE_CHECKING: - if HAS_ZONEINFO: - TZType = ZoneInfo - else: - from pytz.tzinfo import BaseTzInfo as TZType + TZType = ZoneInfo else: TZType = datetime.tzinfo except ImportError: @@ -35,9 +25,7 @@ def get_timezone(name): # type: (str) -> TZType """Return tzinfo for a given TZ name.""" - if HAS_ZONEINFO: - return ZoneInfo(name) # type: ignore[return-value] - return pytz.timezone(name) # type: ignore[return-value] + return ZoneInfo(name) # type: ignore[return-value] UTC = get_timezone("UTC") @@ -48,6 +36,4 @@ def get_timezone(name): def localize(dt, tzinfo=Local): # type: (datetime.datetime, TZType) -> datetime """Localize naive datetime with given timezone.""" - if sys.version_info >= (3, 9): - return dt.replace(tzinfo=tzinfo) - return tzinfo.localize(dt, is_dst=None) + return dt.replace(tzinfo=tzinfo)