From 749c1a2f0bde87a6e6d8df9366e4c90666efd189 Mon Sep 17 00:00:00 2001 From: Bernie Hackett Date: Mon, 2 Jul 2018 10:56:43 -0700 Subject: [PATCH] PYTHON-1467 - PyMongo no longer supports Python 2.6 --- .evergreen/config.yml | 79 +++------------------ .evergreen/run-atlas-tests.sh | 20 ++---- .evergreen/run-tests.sh | 5 +- .travis.yml | 1 - CONTRIBUTING.rst | 2 +- README.rst | 7 +- RELEASE.rst | 4 +- bson/decimal128.py | 20 +----- bson/json_util.py | 34 +-------- bson/py3compat.py | 2 +- doc/changelog.rst | 17 +++++ doc/installation.rst | 19 +++-- doc/python3.rst | 8 +-- pymongo/monitoring.py | 5 +- pymongo/pool.py | 6 +- pymongo/server_description.py | 12 +--- pymongo/ssl_context.py | 2 +- pymongo/ssl_match_hostname.py | 4 +- pymongo/ssl_support.py | 2 +- setup.py | 31 +++----- test/__init__.py | 9 +-- test/atlas/test_connection.py | 8 +-- test/mod_wsgi_test/README.rst | 2 +- test/test_bson_corpus.py | 30 ++------ test/test_client.py | 3 - test/test_crud.py | 4 +- test/test_json_util.py | 121 ++++++++++++++------------------ test/test_replica_set_client.py | 3 - test/test_ssl.py | 4 +- test/test_transactions.py | 4 +- tox.ini | 6 +- 31 files changed, 141 insertions(+), 333 deletions(-) diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 4bb31df9f..e570518d7 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -855,10 +855,11 @@ axes: - id: os-fully-featured display_name: OS values: - - id: linux-64-amzn-test - display_name: "Amazon Linux (Enterprise)" - run_on: linux-64-amzn-test - batchtime: 10080 # 7 days + # https://jira.mongodb.org/browse/BUILD-5453 + #- id: linux-64-amzn-test + # display_name: "Amazon Linux (Enterprise)" + # run_on: linux-64-amzn-test + # batchtime: 10080 # 7 days - id: ubuntu-14.04 display_name: "Ubuntu 14.04" @@ -1013,10 +1014,6 @@ axes: - id: python-version display_name: "Python" values: - - id: "2.6" - display_name: "Python 2.6" - variables: - PYTHON_BINARY: "/opt/python/2.6/bin/python" - id: "2.7" display_name: "Python 2.7" variables: @@ -1061,10 +1058,6 @@ axes: - id: mod-wsgi-version display_name: "mod_wsgi version" values: - - id: "2" - display_name: "mod_wsgi 2.8" - variables: - MOD_WSGI_VERSION: "2" - id: "3" display_name: "mod_wsgi 3.5" variables: @@ -1122,11 +1115,7 @@ axes: values: # There is (currently) no vs2008 distro. The Microsoft Visual # C++ Compiler for Python 2.7 has been installed, with CPython - # 2.6 and 2.7, on the vs2015 distro. - - id: "2.6" - display_name: "Python 2.6" - variables: - PYTHON_BINARY: "/cygdrive/c/python/Python26/python.exe" + # 2.7, on the vs2015 distro. - id: "2.7" display_name: "Python 2.7" variables: @@ -1339,9 +1328,6 @@ buildvariants: # on Windows with the Microsoft Visual C++ Compiler for Python 2.7 or Visual Studio 2015. - matrix_name: "tests-windows-vs2015-python-version-27plus" matrix_spec: {windows-vs2015-python-version: "*", auth-ssl: "*"} - exclude_spec: - - windows-vs2015-python-version: "2.6" - auth-ssl: "*" display_name: "Windows 64 Visual Studio 2015 ${windows-vs2015-python-version} ${auth-ssl}" run_on: windows-64-vs2015-test tasks: @@ -1353,51 +1339,6 @@ buildvariants: - ".3.0" - ".2.6" -# Test CPython 2.6 against all versions on MongoDB >= 2.6 -# on Windows with the Microsoft Visual C++ Compiler for Python 2.7. -# Python 2.6.6 (the last 2.6 version with Windows installers) bundles -# OpenSSL 0.9.8, which doesn't support TLS 1.1+. MongoDB 4.0+ requires -# TLS 1.1+ by default. -- matrix_name: "tests-windows-vs2015-python-version-26" - matrix_spec: {windows-vs2015-python-version: "2.6", auth: "*", ssl: "*"} - exclude_spec: - - windows-vs2015-python-version: "*" - auth: "noauth" - ssl: "ssl" - display_name: "Windows 64 Visual Studio 2015 ${windows-vs2015-python-version} ${auth} ${ssl}" - run_on: windows-64-vs2015-test - rules: - - if: - windows-vs2015-python-version: "*" - auth: "*" - ssl: "nossl" - then: - add_tasks: - - ".latest" - - ".4.0" - - if: - windows-vs2015-python-version: "*" - auth: "noauth" - ssl: "nossl" - then: - add_tasks: - - ".3.6" - - ".3.4" - - ".3.2" - - ".3.0" - - ".2.6" - - if: - windows-vs2015-python-version: "*" - auth: "auth" - ssl: "ssl" - then: - add_tasks: - - ".3.6" - - ".3.4" - - ".3.2" - - ".3.0" - - ".2.6" - # Storage engine tests on RHEL 6.2 (x86_64) with Python 2.7. - matrix_name: "tests-storage-engines" matrix_spec: {storage-engine: "*", python-version: "2.7"} @@ -1453,11 +1394,7 @@ buildvariants: - name: "test-enterprise-auth" - matrix_name: "tests-mod-wsgi" - matrix_spec: {"python-version": ["2.6", "2.7", "3.4", "3.6"], "mod-wsgi-version": "*"} - exclude_spec: - # mod_wsgi 2.8 segfaults with the toolchain python 2.7, regardless of distro - python-version: ["2.7", "3.4", "3.6"] - mod-wsgi-version: ["2"] + matrix_spec: {"python-version": ["2.7", "3.4", "3.6"], "mod-wsgi-version": "*"} display_name: "${mod-wsgi-version} ${python-version}" run_on: rhel62-small tasks: @@ -1479,7 +1416,7 @@ buildvariants: - name: "doctests" - matrix_name: "cdecimal" - matrix_spec: {python-version: ["2.6", "2.7"]} + matrix_spec: {python-version: ["2.7"]} display_name: "cdecimal ${python-version}" batchtime: 10080 # 7 days run_on: diff --git a/.evergreen/run-atlas-tests.sh b/.evergreen/run-atlas-tests.sh index 624533fd5..7660d5349 100644 --- a/.evergreen/run-atlas-tests.sh +++ b/.evergreen/run-atlas-tests.sh @@ -7,24 +7,16 @@ export JAVA_HOME=/opt/java/jdk8 IMPL=$(${PYTHON_BINARY} -c "import platform, sys; sys.stdout.write(platform.python_implementation())") PYTHON_VERSION=$(${PYTHON_BINARY} -c "import sys; print('.'.join(map(str, sys.version_info[:2])))") -if [ $PYTHON_VERSION = "2.6" -o $IMPL = "Jython" ]; then - # TODO - When Jython has its own virtualenv install use it instead. - /opt/python/2.6/bin/virtualenv -p ${PYTHON_BINARY} --never-download --no-wheel atlastest - . atlastest/bin/activate - trap "deactivate; rm -rf atlastest" EXIT HUP - pip install certifi - if [ $PYTHON_VERSION = "2.6" ]; then - pip install unittest2 - fi - PYTHON=python -elif [ $IMPL = "PyPy" -a $PYTHON_VERSION = "3.2" ]; then +if [ $IMPL = "Jython" -o $IMPL = "PyPy" ]; then $PYTHON_BINARY -m virtualenv --never-download --no-wheel atlastest . atlastest/bin/activate trap "deactivate; rm -rf atlastest" EXIT HUP pip install certifi - # Portable pypy3.2 can't load CA certs from the system. - # https://github.com/squeaky-pl/portable-pypy/issues/15 - export SSL_CERT_FILE=$(python -c "import certifi; print(certifi.where())") + if [ $PYTHON_VERSION = "3.2" ]; then + # Portable pypy3.2 can't load CA certs from the system. + # https://github.com/squeaky-pl/portable-pypy/issues/15 + export SSL_CERT_FILE=$(python -c "import certifi; print(certifi.where())") + fi PYTHON=python else PYTHON=$PYTHON_BINARY diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index c69586acc..eb2933a7b 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -44,10 +44,7 @@ if [ -z "$PYTHON_BINARY" ]; then exit 1 fi else - # wheel and pip are dropping support for Python 2.6. Avoid virtualenv - # automatically upgrading its bundled versions to new versions that - # might fail in 2.6. - $VIRTUALENV --no-download pymongotestvenv || $VIRTUALENV pymongotestvenv + $VIRTUALENV pymongotestvenv . pymongotestvenv/bin/activate PYTHON=python trap "deactivate; rm -rf pymongotestvenv" EXIT HUP diff --git a/.travis.yml b/.travis.yml index 20c5e0d6d..8def32c63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python python: - - 2.6 - 2.7 - 3.4 - 3.5 diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 6028fcab6..a65fbbd30 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -19,7 +19,7 @@ that might not be of interest or that has already been addressed. Supported Interpreters ---------------------- -PyMongo supports CPython 2.6, 2.7, 3.4+, PyPy, and PyPy3. Language +PyMongo supports CPython 2.7, 3.4+, PyPy, and PyPy3. Language features not supported by all interpreters can not be used. Style Guide diff --git a/README.rst b/README.rst index 72a2d2ae8..175e20d47 100644 --- a/README.rst +++ b/README.rst @@ -88,7 +88,7 @@ is incompatible with PyMongo. Dependencies ============ -PyMongo supports CPython 2.6, 2.7, 3.4+, PyPy, and PyPy3. +PyMongo supports CPython 2.7, 3.4+, PyPy, and PyPy3. Optional dependencies: @@ -136,7 +136,6 @@ Other optional packages: Additional dependencies are: - (to generate documentation) sphinx_ -- (to run the tests under Python 2.6) unittest2_ Examples ======== @@ -188,8 +187,7 @@ Testing ======= The easiest way to run the tests is to run **python setup.py test** in -the root of the distribution. Note that you will need unittest2_ to -run the tests under Python 2.6. +the root of the distribution. To verify that PyMongo works with Gevent's monkey-patching:: @@ -200,4 +198,3 @@ Or with Eventlet's:: $ python green_framework_test.py eventlet .. _sphinx: http://sphinx.pocoo.org/ -.. _unittest2: https://pypi.python.org/pypi/unittest2 diff --git a/RELEASE.rst b/RELEASE.rst index 506d1d898..c7a030d52 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -31,9 +31,9 @@ that changes the major version number. Doing a Release --------------- -1. Test releases on Python 2.6-2.7 and 3.4+ on Windows, Linux and OSX, +1. Test releases on Python 2.7 and 3.4+ on Windows, Linux and OSX, with and without the C extensions. It's generally enough to just run the - tests on 2.6, 2.7, 3.4 and the latest 3.x version with and without the + tests on 2.7, 3.4 and the latest 3.x version with and without the extensions on a single platform, and then just test any version on the other platforms as a sanity check. `python setup.py test` will build the extensions and test. `python tools/clean.py` will remove the extensions, diff --git a/bson/decimal128.py b/bson/decimal128.py index e06ce0401..0c0fc10c6 100644 --- a/bson/decimal128.py +++ b/bson/decimal128.py @@ -35,21 +35,6 @@ else: "An implementation of int.from_bytes for python 2.x." return _int(_hexlify(value), 16) -if sys.version_info[:2] == (2, 6): - def _bit_length(num): - """bit_length for python 2.6""" - if num: - # bin() was new in 2.6. Note that this won't work - # for values less than 0, which we never have here. - return len(bin(num)) - 2 - # bit_length(0) is 0, but len(bin(0)) - 2 is 1 - return 0 -else: - def _bit_length(num): - """bit_length for python >= 2.7""" - # num could be int or long in python 2.7 - return num.bit_length() - _PACK_64 = struct.Struct("> 48 - # Have to convert bytearray to bytes for python 2.6. # cdecimal only accepts a tuple for digits. digits = tuple( - int(digit) for digit in str(_from_bytes(bytes(arr), 'big'))) + int(digit) for digit in str(_from_bytes(arr, 'big'))) with decimal.localcontext(_DEC128_CTX) as ctx: return ctx.create_decimal((sign, digits, exponent)) diff --git a/bson/json_util.py b/bson/json_util.py index ba99ff2e7..871f71143 100644 --- a/bson/json_util.py +++ b/bson/json_util.py @@ -107,21 +107,12 @@ but it will be faster as there is less recursion. import base64 import datetime +import json import math import re import sys import uuid -if sys.version_info[:2] == (2, 6): - # In Python 2.6, json does not include object_pairs_hook. Use simplejson - # instead. - try: - import simplejson as json - except ImportError: - import json -else: - import json - from pymongo.errors import ConfigurationError import bson @@ -143,13 +134,6 @@ from bson.timestamp import Timestamp from bson.tz_util import utc -try: - json.loads("{}", object_pairs_hook=dict) - _HAS_OBJECT_PAIRS_HOOK = True -except TypeError: - _HAS_OBJECT_PAIRS_HOOK = False - - _RE_OPT_TABLE = { "i": re.I, "l": re.L, @@ -245,10 +229,6 @@ class JSONMode: class JSONOptions(CodecOptions): """Encapsulates JSON options for :func:`dumps` and :func:`loads`. - Raises :exc:`~pymongo.errors.ConfigurationError` on Python 2.6 if - `simplejson >= 2.1.0 `_ is not - installed and document_class is not the default (:class:`dict`). - :Parameters: - `strict_number_long`: If ``True``, :class:`~bson.int64.Int64` objects are encoded to MongoDB Extended JSON's *Strict mode* type @@ -301,11 +281,6 @@ class JSONOptions(CodecOptions): "JSONOptions.datetime_representation must be one of LEGACY, " "NUMBERLONG, or ISO8601 from DatetimeRepresentation.") self = super(JSONOptions, cls).__new__(cls, *args, **kwargs) - if not _HAS_OBJECT_PAIRS_HOOK and self.document_class != dict: - raise ConfigurationError( - "Support for JSONOptions.document_class on Python 2.6 " - "requires simplejson >= 2.1.0" - "(https://pypi.python.org/pypi/simplejson) to be installed.") if json_mode not in (JSONMode.LEGACY, JSONMode.RELAXED, JSONMode.CANONICAL): @@ -430,11 +405,8 @@ def loads(s, *args, **kwargs): Accepts optional parameter `json_options`. See :class:`JSONOptions`. """ json_options = kwargs.pop("json_options", DEFAULT_JSON_OPTIONS) - if _HAS_OBJECT_PAIRS_HOOK: - kwargs["object_pairs_hook"] = lambda pairs: object_pairs_hook( - pairs, json_options) - else: - kwargs["object_hook"] = lambda obj: object_hook(obj, json_options) + kwargs["object_pairs_hook"] = lambda pairs: object_pairs_hook( + pairs, json_options) return json.loads(s, *args, **kwargs) diff --git a/bson/py3compat.py b/bson/py3compat.py index b401d57e2..8e3c722a5 100644 --- a/bson/py3compat.py +++ b/bson/py3compat.py @@ -36,7 +36,7 @@ if PY3: def b(s): # BSON and socket operations deal in binary data. In # python 3 that means instances of `bytes`. In python - # 2.6 and 2.7 you can create an alias for `bytes` using + # 2.7 you can create an alias for `bytes` using # the b prefix (e.g. b'foo'). # See http://python3porting.com/problems.html#nicer-solutions return codecs.latin_1_encode(s)[0] diff --git a/doc/changelog.rst b/doc/changelog.rst index 0b2bd4039..2bdce3591 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -1,6 +1,23 @@ Changelog ========= +Changes in Version 3.8.0 +------------------------ + +.. warning:: PyMongo no longer supports Python 2.6. RHEL 6 users should install + Python 2.7 or newer from `Red Hat Software Collections + `_. CentOS 6 users should install Python + 2.7 or newer from `SCL + `_ + +Issues Resolved +............... + +See the `PyMongo 3.8 release notes in JIRA`_ for the list of resolved issues +in this release. + +.. _PyMongo 3.8 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=19904 + Changes in Version 3.7.0 ------------------------ diff --git a/doc/installation.rst b/doc/installation.rst index 00c10d9a6..6f97ea42c 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -45,7 +45,7 @@ To upgrade do:: Dependencies ------------ -PyMongo supports CPython 2.6, 2.7, 3.4+, PyPy, and PyPy3. +PyMongo supports CPython 2.7, 3.4+, PyPy, and PyPy3. Optional dependencies: @@ -135,13 +135,12 @@ OSX and Xcode versions. **Snow Leopard (10.6)** - Xcode 3 with 'UNIX Development Support'. **Snow Leopard Xcode 4**: The Python versions shipped with OSX 10.6.x -are universal binaries. They support i386, PPC, and (in the case of python2.6) -x86_64. Xcode 4 removed support for PPC, causing the distutils version shipped -with Apple's builds of Python to fail to build the C extensions if you have -Xcode 4 installed. There is a workaround:: +are universal binaries. They support i386, PPC, and x86_64. Xcode 4 removed +support for PPC, causing the distutils version shipped with Apple's builds of +Python to fail to build the C extensions if you have Xcode 4 installed. There +is a workaround:: - # For Apple-supplied Python2.6 (installed at /usr/bin/python2.6) and - # some builds from python.org + # For some Python builds from python.org $ env ARCHFLAGS='-arch i386 -arch x86_64' python -m easy_install pymongo See `http://bugs.python.org/issue11623 `_ @@ -185,8 +184,8 @@ requirements apply to both CPython and ActiveState's ActivePython: For Python 3.5 and newer install Visual Studio 2015. For Python 3.4 install Visual Studio 2010. You must use the full version of Visual Studio 2010 as Visual C++ Express does not provide 64-bit compilers. Make sure that -you check the "x64 Compilers and Tools" option under Visual C++. For Python 2.6 -and 2.7 install the `Microsoft Visual C++ Compiler for Python 2.7`_. +you check the "x64 Compilers and Tools" option under Visual C++. For Python 2.7 +install the `Microsoft Visual C++ Compiler for Python 2.7`_. 32-bit Windows ~~~~~~~~~~~~~~ @@ -195,7 +194,7 @@ For Python 3.5 and newer install Visual Studio 2015. For Python 3.4 install Visual C++ 2010 Express. -For Python 2.6 and 2.7 install the `Microsoft Visual C++ Compiler for Python 2.7`_ +For Python 2.7 install the `Microsoft Visual C++ Compiler for Python 2.7`_ .. _`Microsoft Visual C++ Compiler for Python 2.7`: https://www.microsoft.com/en-us/download/details.aspx?id=44266 diff --git a/doc/python3.rst b/doc/python3.rst index 62c817f65..366a1b55d 100644 --- a/doc/python3.rst +++ b/doc/python3.rst @@ -97,8 +97,8 @@ Python 3 you must pass ``encoding='latin-1'`` to pickle.loads:: If you need to pickle ObjectIds using Python 3 and unpickle them using Python 2 you must use ``protocol <= 2``:: - Python 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04) - [GCC 4.9.3] on linux + Python 3.6.5 (default, Jun 21 2018, 15:09:09) + [GCC 7.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import pickle >>> from bson.objectid import ObjectId @@ -108,8 +108,8 @@ you must use ``protocol <= 2``:: >>> pickle.dumps(oid, protocol=2) b'\x80\x02cbson.objectid\nObjectId\nq\x00)\x81q\x01c_codecs\nencode\...' - Python 2.6.9 (unknown, Feb 26 2014, 12:39:10) - [GCC 4.7.3] on linux2 + Python 2.7.15 (default, Jun 21 2018, 15:00:48) + [GCC 7.3.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import pickle >>> pickle.loads('\x80\x02cbson.objectid\nObjectId\nq\x00)\x81q\x01c_codecs\nencode\...') diff --git a/pymongo/monitoring.py b/pymongo/monitoring.py index c3eab383a..92b838cbe 100644 --- a/pymongo/monitoring.py +++ b/pymongo/monitoring.py @@ -284,10 +284,7 @@ class ServerListener(_EventListener): def _to_micros(dur): """Convert duration 'dur' to microseconds.""" - if hasattr(dur, 'total_seconds'): - return int(dur.total_seconds() * 10e5) - # Python 2.6 - return dur.microseconds + (dur.seconds + dur.days * 24 * 3600) * 1000000 + return int(dur.total_seconds() * 10e5) def _validate_event_listeners(option, listeners): diff --git a/pymongo/pool.py b/pymongo/pool.py index dc97ecc67..66c1d0541 100644 --- a/pymongo/pool.py +++ b/pymongo/pool.py @@ -101,7 +101,7 @@ except ImportError: # ':' is not a valid character for a hostname. If we get # here a few things have to be true: # - We're on a recent version of python 2.7 (2.7.9+). - # 2.6 and older 2.7 versions don't support SNI. + # Older 2.7 versions don't support SNI. # - We're on Windows XP or some unusual Unix that doesn't # have inet_pton. # - The application is using IPv6 literals with TLS, which @@ -272,7 +272,7 @@ def _raise_connection_failure(address, error, msg_prefix=None): if isinstance(error, socket.timeout): raise NetworkTimeout(msg) elif isinstance(error, SSLError) and 'timed out' in str(error): - # CPython 2.6, 2.7, PyPy 2.x, and PyPy3 do not distinguish network + # CPython 2.7, PyPy 2.x, and PyPy3 do not distinguish network # timeouts from other SSLErrors (https://bugs.python.org/issue10272). # Luckily, we can work around this limitation because the phrase # 'timed out' appears in all the timeout related SSLErrors raised @@ -766,7 +766,7 @@ def _create_connection(address, options): Can raise socket.error. - This is a modified version of create_connection from CPython >= 2.6. + This is a modified version of create_connection from CPython >= 2.7. """ host, port = address diff --git a/pymongo/server_description.py b/pymongo/server_description.py index 8ac758055..b77785549 100644 --- a/pymongo/server_description.py +++ b/pymongo/server_description.py @@ -20,16 +20,6 @@ from pymongo.ismaster import IsMaster from pymongo.monotonic import time as _time -def _total_seconds(delta): - """Total seconds in the duration.""" - if hasattr(delta, 'total_seconds'): - return delta.total_seconds() - - # Python 2.6. - return ((delta.days * 86400 + delta.seconds) * 10 ** 6 + - delta.microseconds) / 10.0 ** 6 - - class ServerDescription(object): """Immutable representation of one server. @@ -82,7 +72,7 @@ class ServerDescription(object): if ismaster.last_write_date: # Convert from datetime to seconds. delta = ismaster.last_write_date - EPOCH_NAIVE - self._last_write_date = _total_seconds(delta) + self._last_write_date = delta.total_seconds() else: self._last_write_date = None diff --git a/pymongo/ssl_context.py b/pymongo/ssl_context.py index 2874fdd97..6afb2d2d5 100644 --- a/pymongo/ssl_context.py +++ b/pymongo/ssl_context.py @@ -25,7 +25,7 @@ class SSLContext(object): This implements an API similar to ssl.SSLContext from python 3.2 but does not implement methods or properties that would be - incompatible with ssl.wrap_socket from python 2.6. + incompatible with ssl.wrap_socket from python 2.7 < 2.7.9. You must pass protocol which must be one of the PROTOCOL_* constants defined in the ssl module. ssl.PROTOCOL_SSLv23 is recommended for maximum diff --git a/pymongo/ssl_match_hostname.py b/pymongo/ssl_match_hostname.py index 53a32fd5f..49e3dd657 100644 --- a/pymongo/ssl_match_hostname.py +++ b/pymongo/ssl_match_hostname.py @@ -1,11 +1,11 @@ # Backport of the match_hostname logic from python 3.5, with small -# changes to support IP address matching on python 2.6, 2.7, 3.3, and 3.4. +# changes to support IP address matching on python 2.7 and 3.4. import re import sys try: - # Python 3.3+, or the ipaddress module from pypi. + # Python 3.4+, or the ipaddress module from pypi. from ipaddress import ip_address except ImportError: ip_address = lambda address: None diff --git a/pymongo/ssl_support.py b/pymongo/ssl_support.py index 6c6337401..a8343b925 100644 --- a/pymongo/ssl_support.py +++ b/pymongo/ssl_support.py @@ -123,7 +123,7 @@ if HAVE_SSL: # up to date versions of MongoDB 2.4 and above already disable # SSLv2 and SSLv3, python disables SSLv2 by default in >= 2.7.7 # and >= 3.3.4 and SSLv3 in >= 3.4.3. There is no way for us to do - # any of this explicitly for python 2.6 or 2.7 before 2.7.9. + # any of this explicitly for python 2.7 before 2.7.9. ctx.options |= getattr(ssl, "OP_NO_SSLv2", 0) ctx.options |= getattr(ssl, "OP_NO_SSLv3", 0) # OpenSSL >= 1.0.0 diff --git a/setup.py b/setup.py index 2d3eb58c3..8357dd83d 100755 --- a/setup.py +++ b/setup.py @@ -97,21 +97,16 @@ class test(Command): if self.distribution.tests_require: self.distribution.fetch_build_eggs(self.distribution.tests_require) if self.xunit_output: - if sys.version_info[:2] == (2, 6): - self.distribution.fetch_build_eggs( - ["unittest-xml-reporting>=1.14.0,<2.0.0a0"]) - else: - self.distribution.fetch_build_eggs(["unittest-xml-reporting"]) + self.distribution.fetch_build_eggs(["unittest-xml-reporting"]) self.run_command('egg_info') build_ext_cmd = self.reinitialize_command('build_ext') build_ext_cmd.inplace = 1 self.run_command('build_ext') # Construct a TextTestRunner directly from the unittest imported from - # test (this will be unittest2 under Python 2.6), which creates a - # TestResult that supports the 'addSkip' method. setuptools will by - # default create a TextTestRunner that uses the old TestResult class, - # resulting in DeprecationWarnings instead of skipping tests under 2.6. + # test, which creates a TestResult that supports the 'addSkip' method. + # setuptools will by default create a TextTestRunner that uses the old + # TestResult class. from test import unittest, PymongoTestRunner, test_cases if self.test_suite is None: all_tests = unittest.defaultTestLoader.discover(self.test_module) @@ -223,8 +218,8 @@ class doc(Command): " %s/\n" % (mode, path)) -if sys.platform == 'win32' and sys.version_info > (2, 6): - # 2.6's distutils.msvc9compiler can raise an IOError when failing to +if sys.platform == 'win32': + # distutils.msvc9compiler can raise an IOError when failing to # find the compiler build_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError, IOError) @@ -294,7 +289,7 @@ http://api.mongodb.org/python/current/installation.html#osx def build_extension(self, ext): name = ext.name - if sys.version_info[:3] >= (2, 6, 0): + if sys.version_info[:3] >= (2, 7, 0): try: build_ext.build_extension(self, ext) except build_errors: @@ -310,7 +305,7 @@ http://api.mongodb.org/python/current/installation.html#osx warnings.warn(self.warning_message % ("The %s extension " "module" % (name,), "PyMongo supports python " - ">= 2.6.")) + ">= 2.7.")) ext_modules = [Extension('bson._cbson', include_dirs=['bson'], @@ -343,15 +338,6 @@ else: extra_opts = { "packages": ["bson", "pymongo", "gridfs"] } -if sys.version_info[:2] == (2, 6): - try: - import unittest2 - except ImportError: - # The setuptools version on Solaris 11 is incapable - # of recognizing if unittest2 is already installed. - # It's also incapable of installing any version of - # unittest2 newer than 0.8.0 - extra_opts['tests_require'] = "unittest2<=0.8.0" if "--no_ext" in sys.argv: sys.argv.remove("--no_ext") @@ -388,7 +374,6 @@ setup( "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", diff --git a/test/__init__.py b/test/__init__.py index 9ecf34eb9..9d854360a 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -20,6 +20,7 @@ import socket import sys import threading import time +import unittest import warnings try: @@ -28,14 +29,8 @@ try: except ImportError: HAVE_IPADDRESS = False -if sys.version_info[:2] == (2, 6): - import unittest2 as unittest - from unittest2 import SkipTest -else: - import unittest - from unittest import SkipTest - from functools import wraps +from unittest import SkipTest import pymongo import pymongo.errors diff --git a/test/atlas/test_connection.py b/test/atlas/test_connection.py index 667660abd..4241f5923 100644 --- a/test/atlas/test_connection.py +++ b/test/atlas/test_connection.py @@ -17,13 +17,7 @@ import os import ssl import sys - -# Don't drag in PyMongo's entire test suite -# just to get the right unittest module. -if sys.version_info[:2] == (2, 6): - import unittest2 as unittest -else: - import unittest +import unittest sys.path[0:0] = [""] diff --git a/test/mod_wsgi_test/README.rst b/test/mod_wsgi_test/README.rst index 5ca4e933e..2ea50c907 100644 --- a/test/mod_wsgi_test/README.rst +++ b/test/mod_wsgi_test/README.rst @@ -38,7 +38,7 @@ Compile mod_wsgi ................ Compile mod_wsgi for each combination for Python and mod_wsgi version in the -test matrix. For example, to compile mod_wsgi 3.4 for Python 2.6 on a +test matrix. For example, to compile mod_wsgi 3.4 for Python 2.7 on a RedHat-like Linux:: sudo yum install -y httpd httpd-devel diff --git a/test/test_bson_corpus.py b/test/test_bson_corpus.py index ed6b8fe8c..8f56eaf75 100644 --- a/test/test_bson_corpus.py +++ b/test/test_bson_corpus.py @@ -18,19 +18,12 @@ import binascii import codecs import functools import glob +import json import os import sys from decimal import DecimalException -if sys.version_info[:2] == (2, 6): - try: - import simplejson as json - except ImportError: - import json -else: - import json - sys.path[0:0] = [""] from bson import BSON, json_util @@ -92,17 +85,11 @@ to_bson_uuid_04 = functools.partial(BSON.encode, codec_options=codec_options_uuid_04) to_bson = functools.partial(BSON.encode, codec_options=codec_options) decode_bson = lambda bbytes: BSON(bbytes).decode(codec_options=codec_options) -if json_util._HAS_OBJECT_PAIRS_HOOK: - decode_extjson = functools.partial( - json_util.loads, - json_options=json_util.JSONOptions(json_mode=JSONMode.CANONICAL, - document_class=SON)) - loads = functools.partial(json.loads, object_pairs_hook=SON) -else: - decode_extjson = functools.partial( - json_util.loads, - json_options=json_util.CANONICAL_JSON_OPTIONS) - loads = json.loads +decode_extjson = functools.partial( + json_util.loads, + json_options=json_util.JSONOptions(json_mode=JSONMode.CANONICAL, + document_class=SON)) +loads = functools.partial(json.loads, object_pairs_hook=SON) class TestBSONCorpus(unittest.TestCase): @@ -177,7 +164,7 @@ def create_test(case_spec): # Test round-tripping canonical extended json. decoded_json = decode_extjson(cEJ) self.assertJsonEqual(encode_extjson(decoded_json), cEJ) - if not lossy and json_util._HAS_OBJECT_PAIRS_HOOK: + if not lossy: self.assertEqual(encode_bson(decoded_json), cB) # Test round-tripping degenerate bson. @@ -190,9 +177,6 @@ def create_test(case_spec): decoded_json = decode_extjson(dEJ) self.assertJsonEqual(encode_extjson(decoded_json), cEJ) if not lossy: - # We don't need to check json_util._HAS_OBJECT_PAIRS_HOOK - # because degenerate_extjson is always a single key so - # the order cannot be changed. self.assertEqual(encode_bson(decoded_json), cB) # Test round-tripping relaxed extended json. diff --git a/test/test_client.py b/test/test_client.py index 7f6cb8bb2..60deae7b4 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -911,9 +911,6 @@ class TestClient(IntegrationTest): @client_context.require_ipv6 def test_ipv6(self): if client_context.ssl: - # http://bugs.python.org/issue13034 - if sys.version_info[:2] == (2, 6): - raise SkipTest("Python 2.6 can't parse SANs") if not HAVE_IPADDRESS: raise SkipTest("Need the ipaddress module to test with SSL") diff --git a/test/test_crud.py b/test/test_crud.py index 3df8eb472..1151f9764 100644 --- a/test/test_crud.py +++ b/test/test_crud.py @@ -131,11 +131,9 @@ def run_operation(collection, test): else: for arg_name in list(arguments): c2s = camel_to_snake(arg_name) - # PyMongo accepts sort as list of tuples. Asserting len=1 - # because ordering dicts from JSON in 2.6 is unwieldy. + # PyMongo accepts sort as list of tuples. if arg_name == "sort": sort_dict = arguments[arg_name] - assert len(sort_dict) == 1, 'test can only have 1 sort key' arguments[arg_name] = list(iteritems(sort_dict)) # Named "key" instead not fieldName. if arg_name == "fieldName": diff --git a/test/test_json_util.py b/test/test_json_util.py index 99513e880..a2f7a550e 100644 --- a/test/test_json_util.py +++ b/test/test_json_util.py @@ -26,8 +26,7 @@ from pymongo.errors import ConfigurationError from bson import json_util, EPOCH_AWARE, EPOCH_NAIVE, SON from bson.json_util import (DatetimeRepresentation, - STRICT_JSON_OPTIONS, - _HAS_OBJECT_PAIRS_HOOK) + STRICT_JSON_OPTIONS) from bson.binary import (ALL_UUID_REPRESENTATIONS, Binary, MD5_SUBTYPE, USER_DEFINED_SUBTYPE, JAVA_LEGACY, CSHARP_LEGACY, STANDARD) @@ -64,11 +63,10 @@ class TestJsonUtil(unittest.TestCase): self.round_trip({"ref": DBRef("foo", 5, "db")}) self.round_trip({"ref": DBRef("foo", ObjectId())}) - if _HAS_OBJECT_PAIRS_HOOK: - # Check order. - self.assertEqual( - '{"$ref": "collection", "$id": 1, "$db": "db"}', - json_util.dumps(DBRef('collection', 1, 'db'))) + # Check order. + self.assertEqual( + '{"$ref": "collection", "$id": 1, "$db": "db"}', + json_util.dumps(DBRef('collection', 1, 'db'))) def test_datetime(self): # only millis, not micros @@ -226,15 +224,14 @@ class TestJsonUtil(unittest.TestCase): json_util.loads( '{"r": {"$regex": ".*", "$options": "ilm"}}')['r']) - if _HAS_OBJECT_PAIRS_HOOK: - # Check order. - self.assertEqual( - '{"$regex": ".*", "$options": "mx"}', - json_util.dumps(Regex('.*', re.M | re.X))) + # Check order. + self.assertEqual( + '{"$regex": ".*", "$options": "mx"}', + json_util.dumps(Regex('.*', re.M | re.X))) - self.assertEqual( - '{"$regex": ".*", "$options": "mx"}', - json_util.dumps(re.compile(b'.*', re.M | re.X))) + self.assertEqual( + '{"$regex": ".*", "$options": "mx"}', + json_util.dumps(re.compile(b'.*', re.M | re.X))) def test_minkey(self): self.round_trip({"m": MinKey()}) @@ -247,9 +244,7 @@ class TestJsonUtil(unittest.TestCase): res = json_util.dumps(dct, default=json_util.default) rtdct = json_util.loads(res) self.assertEqual(dct, rtdct) - - if _HAS_OBJECT_PAIRS_HOOK: - self.assertEqual('{"ts": {"$timestamp": {"t": 4, "i": 13}}}', res) + self.assertEqual('{"ts": {"$timestamp": {"t": 4, "i": 13}}}', res) def test_uuid(self): doc = {'uuid': uuid.UUID('f47ac10b-58cc-4372-a567-0e02b2c3d479')} @@ -257,33 +252,30 @@ class TestJsonUtil(unittest.TestCase): self.assertEqual( '{"uuid": {"$uuid": "f47ac10b58cc4372a5670e02b2c3d479"}}', json_util.dumps(doc)) - - if _HAS_OBJECT_PAIRS_HOOK: - self.assertEqual( + self.assertEqual( + '{"uuid": ' + '{"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "03"}}', + json_util.dumps( + doc, json_options=json_util.STRICT_JSON_OPTIONS)) + self.assertEqual( + '{"uuid": ' + '{"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "04"}}', + json_util.dumps( + doc, json_options=json_util.JSONOptions( + strict_uuid=True, uuid_representation=STANDARD))) + self.assertEqual( + doc, json_util.loads( '{"uuid": ' - '{"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "03"}}', - json_util.dumps( - doc, json_options=json_util.STRICT_JSON_OPTIONS)) - self.assertEqual( - '{"uuid": ' - '{"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "04"}}', - json_util.dumps( - doc, json_options=json_util.JSONOptions( - strict_uuid=True, uuid_representation=STANDARD))) - self.assertEqual( - doc, json_util.loads( - '{"uuid": ' - '{"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "03"}}')) + '{"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "03"}}')) for uuid_representation in ALL_UUID_REPRESENTATIONS: options = json_util.JSONOptions( strict_uuid=True, uuid_representation=uuid_representation) self.round_trip(doc, json_options=options) # Ignore UUID representation when decoding BSON binary subtype 4. - if _HAS_OBJECT_PAIRS_HOOK: - self.assertEqual(doc, json_util.loads( - '{"uuid": ' - '{"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "04"}}', - json_options=options)) + self.assertEqual(doc, json_util.loads( + '{"uuid": ' + '{"$binary": "9HrBC1jMQ3KlZw4CssPUeQ==", "$type": "04"}}', + json_options=options)) def test_binary(self): if PY3: @@ -312,24 +304,22 @@ class TestJsonUtil(unittest.TestCase): self.assertTrue('"$type": "00"' in json_bin_dump) self.assertEqual(bin_type_dict, json_util.loads('{"bin": {"$type": 0, "$binary": "AAECAwQ="}}')) + json_bin_dump = json_util.dumps(md5_type_dict) + # Check order. + self.assertEqual( + '{"md5": {"$binary": "IG43GK8JL9HRL4DK53HMrA==",' + + ' "$type": "05"}}', + json_bin_dump) - if _HAS_OBJECT_PAIRS_HOOK: - json_bin_dump = json_util.dumps(md5_type_dict) - # Check order. - self.assertEqual( - '{"md5": {"$binary": "IG43GK8JL9HRL4DK53HMrA==",' - + ' "$type": "05"}}', - json_bin_dump) + self.assertEqual(md5_type_dict, + json_util.loads('{"md5": {"$type": 5, "$binary":' + ' "IG43GK8JL9HRL4DK53HMrA=="}}')) - self.assertEqual(md5_type_dict, - json_util.loads('{"md5": {"$type": 5, "$binary":' - ' "IG43GK8JL9HRL4DK53HMrA=="}}')) - - json_bin_dump = json_util.dumps(custom_type_dict) - self.assertTrue('"$type": "80"' in json_bin_dump) - self.assertEqual(custom_type_dict, - json_util.loads('{"custom": {"$type": 128, "$binary":' - ' "aGVsbG8="}}')) + json_bin_dump = json_util.dumps(custom_type_dict) + self.assertTrue('"$type": "80"' in json_bin_dump) + self.assertEqual(custom_type_dict, + json_util.loads('{"custom": {"$type": 128, "$binary":' + ' "aGVsbG8="}}')) # Handle mongoexport where subtype >= 128 self.assertEqual(128, @@ -347,13 +337,12 @@ class TestJsonUtil(unittest.TestCase): res = json_util.dumps(code) self.assertEqual(code, json_util.loads(res)) - if _HAS_OBJECT_PAIRS_HOOK: - # Check order. - self.assertEqual('{"$code": "return z", "$scope": {"z": 2}}', res) + # Check order. + self.assertEqual('{"$code": "return z", "$scope": {"z": 2}}', res) - no_scope = Code('function() {}') - self.assertEqual( - '{"$code": "function() {}"}', json_util.dumps(no_scope)) + no_scope = Code('function() {}') + self.assertEqual( + '{"$code": "function() {}"}', json_util.dumps(no_scope)) def test_undefined(self): jsn = '{"name": {"$undefined": true}}' @@ -375,13 +364,9 @@ class TestJsonUtil(unittest.TestCase): self.assertEqual({"foo": "bar"}, json_util.loads( '{"foo": "bar"}', json_options=json_util.JSONOptions(document_class=dict))) - if not _HAS_OBJECT_PAIRS_HOOK: - self.assertRaises( - ConfigurationError, json_util.JSONOptions, document_class=SON) - else: - self.assertEqual(SON([("foo", "bar"), ("b", 1)]), json_util.loads( - '{"foo": "bar", "b": 1}', - json_options=json_util.JSONOptions(document_class=SON))) + self.assertEqual(SON([("foo", "bar"), ("b", 1)]), json_util.loads( + '{"foo": "bar", "b": 1}', + json_options=json_util.JSONOptions(document_class=SON))) class TestJsonUtilRoundtrip(IntegrationTest): diff --git a/test/test_replica_set_client.py b/test/test_replica_set_client.py index 72fc683c3..7c8d216b8 100644 --- a/test/test_replica_set_client.py +++ b/test/test_replica_set_client.py @@ -196,9 +196,6 @@ class TestReplicaSetClient(TestReplicaSetClientBase): @client_context.require_ipv6 def test_ipv6(self): if client_context.ssl: - # http://bugs.python.org/issue13034 - if sys.version_info[:2] == (2, 6): - raise SkipTest("Python 2.6 can't parse SANs") if not HAVE_IPADDRESS: raise SkipTest("Need the ipaddress module to test with SSL") diff --git a/test/test_ssl.py b/test/test_ssl.py index d367e3db1..1b5d56da2 100644 --- a/test/test_ssl.py +++ b/test/test_ssl.py @@ -257,9 +257,7 @@ class TestSSL(IntegrationTest): self.assertClientWorks(client) - # Python 2.6 often can't read SANs from the peer cert. - # http://bugs.python.org/issue13034 - if HAVE_IPADDRESS and sys.version_info[:2] > (2, 6): + if HAVE_IPADDRESS: client = MongoClient('127.0.0.1', ssl=True, ssl_certfile=CLIENT_PEM, diff --git a/test/test_transactions.py b/test/test_transactions.py index eb8e74dcf..38290c8d2 100644 --- a/test/test_transactions.py +++ b/test/test_transactions.py @@ -247,11 +247,9 @@ class TestTransactions(IntegrationTest): for arg_name in list(arguments): c2s = camel_to_snake(arg_name) - # PyMongo accepts sort as list of tuples. Asserting len=1 - # because ordering dicts from JSON in 2.6 is unwieldy. + # PyMongo accepts sort as list of tuples. if arg_name == "sort": sort_dict = arguments[arg_name] - assert len(sort_dict) == 1, 'test can only have 1 sort key' arguments[arg_name] = list(iteritems(sort_dict)) # Named "key" instead not fieldName. if arg_name == "fieldName": diff --git a/tox.ini b/tox.ini index 91e9f9725..340ba5131 100644 --- a/tox.ini +++ b/tox.ini @@ -4,13 +4,9 @@ # and then run "tox" from this directory. [tox] -envlist = py26, py27, py33, py34, py35, pypy, pypy3 +envlist = py27, py34, py35, py36, pypy, pypy3 skip_missing_interpreters = True [testenv] commands = {envpython} setup.py --no_ext test - -[testenv:py26] -deps = - unittest2