diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3bebb95..a36f8eb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 @@ -21,7 +21,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Run tests - run: uv run --with python-dateutil --with six test.py + run: uv run --with python-dateutil --with pytest pytest bson/tests test.py # https://github.com/marketplace/actions/alls-green#why used for branch protection checks check: diff --git a/bson/codec.py b/bson/codec.py index 6aa7e07..566681f 100644 --- a/bson/codec.py +++ b/bson/codec.py @@ -24,10 +24,6 @@ from dateutil.tz import tzutc from binascii import b2a_hex -from six import integer_types, iterkeys, text_type, PY3 -from six.moves import xrange - - utc = tzutc() class MissingClassDefinition(ValueError): @@ -132,7 +128,7 @@ def encode_string(value): def encode_cstring(value): if not isinstance(value, bytes): - value = text_type(value).encode("utf-8") + value = str(value).encode("utf-8") if b"\x00" in value: raise ValueError("Element names may not include NUL bytes.") # A NUL byte is used to delimit our string, accepting one would cause @@ -172,24 +168,26 @@ def encode_double_element(name, value): def encode_string_element(name, value): return b"\x02" + encode_cstring(name) + encode_string(value) - +# any need for this at all with py3?? def _is_string(value): - if isinstance(value, text_type): + if isinstance(value, str): return True - elif isinstance(value, str) or isinstance(value, bytes): - try: - unicode(value, errors='strict') - return True - except: - pass - return False + # this never worked in py3 + # elif isinstance(value, str) or isinstance(value, bytes): + # try: + # unicode(value, errors='strict') + # return True + # except: + # pass + else: + return False def encode_value(name, value, buf, traversal_stack, generator_func, on_unknown=None): if isinstance(value, bool): buf.write(encode_boolean_element(name, value)) - elif isinstance(value, integer_types): + elif isinstance(value, int): if value < -0x80000000 or 0x7FFFFFFFFFFFFFFF >= value > 0x7fffffff: buf.write(encode_int64_element(name, value)) elif value > 0x7FFFFFFFFFFFFFFF: @@ -238,7 +236,7 @@ def encode_value(name, value, buf, traversal_stack, def encode_document(obj, traversal_stack, traversal_parent=None, generator_func=None, on_unknown=None): buf = StringIO() - key_iter = iterkeys(obj) + key_iter = iter(obj) if generator_func is not None: key_iter = generator_func(obj, traversal_stack) for name in key_iter: @@ -256,7 +254,7 @@ def encode_document(obj, traversal_stack, traversal_parent=None, def encode_array(array, traversal_stack, traversal_parent=None, generator_func=None, on_unknown=None): buf = StringIO() - for i in xrange(0, len(array)): + for i in range(0, len(array)): value = array[i] traversal_stack.append(TraversalStep(traversal_parent or array, i)) encode_value(str(i), value, buf, traversal_stack, @@ -295,10 +293,7 @@ def decode_document(data, base, as_array=False): element_type = char_struct.unpack(data[base:base + 1])[0] - if PY3: - ll = data.index(0, base + 1) + 1 - else: - ll = data.index("\x00", base + 1) + 1 + ll = data.index(0, base + 1) + 1 if decode_name: name = data[base + 1:ll - 1] try: diff --git a/bson/network.py b/bson/network.py index d24e5bd..1db716d 100644 --- a/bson/network.py +++ b/bson/network.py @@ -1,7 +1,7 @@ #!/usr/bin/env python from struct import unpack -from six import BytesIO, b +from io import BytesIO from . import dumps, loads @@ -55,9 +55,10 @@ def recvbytes(self, bytes_needed, sock_buf = None): chunk = self.recv(min(bytes_needed - bytes_count, 32768)) part_count = len(chunk) - if type(chunk) == str: - chunk = b(chunk) - + if type(chunk) == str: # this probably never occurs ... + # chunk = b(chunk) + # six.b() encoded to latin-1 + chunk = chunk.encode("latin-1") if part_count < 1: return None diff --git a/bson/objectid.py b/bson/objectid.py index 35061b1..87ef2a8 100644 --- a/bson/objectid.py +++ b/bson/objectid.py @@ -27,15 +27,14 @@ import threading import time -from bson.py3compat import PY3, bytes_from_hex, string_type, text_type +# from bson.py3compat import bytes_from_hex #string_type from bson.tz_util import utc # fnv_1a_24 adaptation taken from MongoDB Python Driver at https://github.com/mongodb/mongo-python-driver/commit/61850357a0e0eeec1a30e1adc0bbf7ebee807358 -if PY3: - _ord = lambda x: x -else: - _ord = ord +# is this needed at all for Py3? +_ord = lambda x: x + # http://isthe.com/chongo/tech/comp/fnv/index.html#FNV-1a def _fnv_1a_24(data, _ord=_ord): """FNV-1a 24 bit hash""" @@ -212,18 +211,17 @@ def __validate(self, oid): """ if isinstance(oid, ObjectId): self.__id = oid.binary - # bytes or unicode in python 2, str in python 3 - elif isinstance(oid, string_type): + elif isinstance(oid, str): if len(oid) == 24: try: - self.__id = bytes_from_hex(oid) + self.__id = bytes.fromhex(oid) except (TypeError, ValueError): _raise_invalid_id(oid) else: _raise_invalid_id(oid) else: raise TypeError("id must be an instance of (bytes, %s, ObjectId), " - "not %s" % (text_type.__name__, type(oid))) + "not %s" % ('str', type(oid))) @property def binary(self): @@ -261,15 +259,13 @@ def __setstate__(self, value): # ObjectIds pickled in python 2.x used `str` for __id. # In python 3.x this has to be converted to `bytes` # by encoding latin-1. - if PY3 and isinstance(oid, text_type): + if isinstance(oid, str): self.__id = oid.encode('latin-1') else: self.__id = oid def __str__(self): - if PY3: - return binascii.hexlify(self.__id).decode() - return binascii.hexlify(self.__id) + return binascii.hexlify(self.__id).decode() def __repr__(self): return "ObjectId('%s')" % (str(self),) diff --git a/bson/py3compat.py b/bson/py3compat.py deleted file mode 100644 index 0a2aa32..0000000 --- a/bson/py3compat.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright 2009-2015 MongoDB, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you -# may not use this file except in compliance with the License. You -# may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. See the License for the specific language governing -# permissions and limitations under the License. - -"""Utility functions and definitions for python3 compatibility.""" - -import sys - -PY3 = sys.version_info[0] == 3 - -if PY3: - import codecs - import _thread as thread - from io import BytesIO as StringIO - MAXSIZE = sys.maxsize - - imap = map - - 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 - # the b prefix (e.g. b'foo'). - # See http://python3porting.com/problems.html#nicer-solutions - return codecs.latin_1_encode(s)[0] - - def bytes_from_hex(h): - return bytes.fromhex(h) - - def iteritems(d): - return iter(d.items()) - - def itervalues(d): - return iter(d.values()) - - def reraise(exctype, value, trace=None): - raise exctype(str(value)).with_traceback(trace) - - def _unicode(s): - return s - - text_type = str - string_type = str - integer_types = int -else: - import thread - - from itertools import imap - try: - from cStringIO import StringIO - except ImportError: - from StringIO import StringIO - - MAXSIZE = sys.maxint - - def b(s): - # See comments above. In python 2.x b('foo') is just 'foo'. - return s - - def bytes_from_hex(h): - return h.decode('hex') - - def iteritems(d): - return d.iteritems() - - def itervalues(d): - return d.itervalues() - - # "raise x, y, z" raises SyntaxError in Python 3 - exec("""def reraise(exctype, value, trace=None): - raise exctype, str(value), trace -""") - - _unicode = unicode - - string_type = basestring - text_type = unicode - integer_types = (int, long) diff --git a/bson/tests/test_array.py b/bson/tests/test_array.py index 029c5a1..e0eee3e 100644 --- a/bson/tests/test_array.py +++ b/bson/tests/test_array.py @@ -2,7 +2,6 @@ from unittest import TestCase from bson import dumps, loads -from six import PY3 class TestArray(TestCase): @@ -74,5 +73,5 @@ def test_long_array(self): def test_encoded_order(self): serialized = dumps(self.doc) - expected = repr(serialized)[1:] if PY3 else repr(serialized) + expected = repr(serialized)[1:] self.assertEqual(expected, '\'\\xea\\x08\\x00\\x00\\x04lyrics\\x00\\xdd\\x08\\x00\\x00\\x020\\x00\\x14\\x00\\x00\\x00Viva La Vida lyrics\\x00\\x021\\x00\\x01\\x00\\x00\\x00\\x00\\x022\\x00!\\x00\\x00\\x00 I used to rule the world\\x00\\x023\\x00-\\x00\\x00\\x00 Seas would rise when I gave the word\\x00\\x024\\x00)\\x00\\x00\\x00 Now in the morning I sleep alone\\x00\\x025\\x00(\\x00\\x00\\x00 Sweep the streets I used to own\\x00\\x026\\x00\\x01\\x00\\x00\\x00\\x00\\x027\\x00 \\x00\\x00\\x00 I used to roll the dice\\x00\\x028\\x00)\\x00\\x00\\x00 Feel the fear in my enemy\\\'s eyes\\x00\\x029\\x00\\\'\\x00\\x00\\x00 Listen as the crowd would sing\\x00\\x0210\\x008\\x00\\x00\\x00 "Now the old king is dead! Long live the king!"\\x00\\x0211\\x00\\x01\\x00\\x00\\x00\\x00\\x0212\\x00"\\x00\\x00\\x00 One minute I held the key\\x00\\x0213\\x00)\\x00\\x00\\x00 Next the walls were closed on me\\x00\\x0214\\x00/\\x00\\x00\\x00 And I discovered that my castles stand\\x00\\x0215\\x001\\x00\\x00\\x00 Upon pillars of salt and pillars of sand\\x00\\x0216\\x00\\x01\\x00\\x00\\x00\\x00\\x0217\\x00)\\x00\\x00\\x00 I hear Jerusalem bells a ringing\\x00\\x0218\\x00)\\x00\\x00\\x00 Roman Cavalry choirs are singing\\x00\\x0219\\x00*\\x00\\x00\\x00 Be my mirror, my sword and shield\\x00\\x0220\\x00+\\x00\\x00\\x00 My missionaries in a foreign field\\x00\\x0221\\x00\\x01\\x00\\x00\\x00\\x00\\x0222\\x00(\\x00\\x00\\x00 For some reason I can\\\'t explain\\x00\\x0223\\x00$\\x00\\x00\\x00 Once you go there was never\\x00\\x0224\\x00\\x1d\\x00\\x00\\x00 Never an honest word\\x00\\x0225\\x00,\\x00\\x00\\x00 And that was when I ruled the world\\x00\\x0226\\x00\\x01\\x00\\x00\\x00\\x00\\x0227\\x00(\\x00\\x00\\x00 It was the wicked and wild wind\\x00\\x0228\\x00)\\x00\\x00\\x00 Blew down the doors to let me in\\x00\\x0229\\x001\\x00\\x00\\x00 Shattered windows and the sound of drums\\x00\\x0230\\x000\\x00\\x00\\x00 People couldn\\\'t believe what I\\\'d become\\x00\\x0231\\x00\\x01\\x00\\x00\\x00\\x00\\x0232\\x00\\x1d\\x00\\x00\\x00 Revolutionaries wait\\x00\\x0233\\x00&\\x00\\x00\\x00 For my head on a silver plate\\x00\\x0234\\x00)\\x00\\x00\\x00 Just a puppet on a lonely string\\x00\\x0235\\x00+\\x00\\x00\\x00 Oh who would ever want to be king?\\x00\\x0236\\x00\\x01\\x00\\x00\\x00\\x00\\x0237\\x00)\\x00\\x00\\x00 I hear Jerusalem bells a ringing\\x00\\x0238\\x00)\\x00\\x00\\x00 Roman Cavalry choirs are singing\\x00\\x0239\\x00*\\x00\\x00\\x00 Be my mirror, my sword and shield\\x00\\x0240\\x00+\\x00\\x00\\x00 My missionaries in a foreign field\\x00\\x0241\\x00\\x01\\x00\\x00\\x00\\x00\\x0242\\x00(\\x00\\x00\\x00 For some reason I can\\\'t explain\\x00\\x0243\\x00.\\x00\\x00\\x00 I know Saint Peter won\\\'t call my name\\x00\\x0244\\x00\\x1d\\x00\\x00\\x00 Never an honest word\\x00\\x0245\\x00,\\x00\\x00\\x00 But that was when I ruled the world\\x00\\x0246\\x00\\x01\\x00\\x00\\x00\\x00\\x0247\\x00)\\x00\\x00\\x00 I hear Jerusalem bells a ringing\\x00\\x0248\\x00)\\x00\\x00\\x00 Roman Cavalry choirs are singing\\x00\\x0249\\x00*\\x00\\x00\\x00 Be my mirror, my sword and shield\\x00\\x0250\\x00+\\x00\\x00\\x00 My missionaries in a foreign field\\x00\\x0251\\x00\\x01\\x00\\x00\\x00\\x00\\x0252\\x00(\\x00\\x00\\x00 For some reason I can\\\'t explain\\x00\\x0253\\x00.\\x00\\x00\\x00 I know Saint Peter won\\\'t call my name\\x00\\x0254\\x00\\x1d\\x00\\x00\\x00 Never an honest word\\x00\\x0255\\x00,\\x00\\x00\\x00 But that was when I ruled the world\\x00\\x00\\x00\'') diff --git a/bson/tests/test_object.py b/bson/tests/test_object.py index 2471e9c..db110fb 100644 --- a/bson/tests/test_object.py +++ b/bson/tests/test_object.py @@ -3,8 +3,8 @@ from bson import BSONCoding, dumps, loads, import_class - -class TestData(BSONCoding): +# named with underscore so as not to confuse pytest +class _TestData(BSONCoding): def __init__(self, *args): self.args = list(args) self.nested = None @@ -17,7 +17,7 @@ def bson_init(self, raw_values): self.nested = raw_values["nested"] def __eq__(self, other): - if not isinstance(other, TestData): + if not isinstance(other, _TestData): return NotImplemented if self.args != other.args: return False @@ -31,12 +31,12 @@ def __ne__(self, other): class TestObjectCoding(TestCase): def test_codec(self): - import_class(TestData) - data = TestData(u"Lorem ipsum dolor sit amet", + import_class(_TestData) + data = _TestData(u"Lorem ipsum dolor sit amet", "consectetur adipisicing elit", 42) - data2 = TestData(u"She's got both hands in her pockets", + data2 = _TestData(u"She's got both hands in her pockets", "and she won't look at you won't look at you eh", 66, 23.54, diff --git a/bson/tests/test_objectid.py b/bson/tests/test_objectid.py index 52eb62b..c664b0a 100644 --- a/bson/tests/test_objectid.py +++ b/bson/tests/test_objectid.py @@ -25,7 +25,6 @@ from bson.objectid import ObjectId, _fnv_1a_24 from bson.objectid import InvalidId -from bson.py3compat import PY3, _unicode from bson.tz_util import (FixedOffset, utc) @@ -467,7 +466,7 @@ def test_fnv_1a_24(self): def test_unicode(self): a = ObjectId() - self.assertEqual(a, ObjectId(_unicode(a))) + self.assertEqual(a, ObjectId(a)) self.assertEqual(ObjectId("123456789012123456789012"), ObjectId(u"123456789012123456789012")) self.assertRaises(InvalidId, ObjectId, u"hello") @@ -511,18 +510,19 @@ def test_pid(self): self.assertTrue(oid_generated_on_client(ObjectId())) def test_generation_time(self): - d1 = datetime.datetime.utcnow() + d1 = datetime.datetime.now(utc) d2 = ObjectId().generation_time self.assertEqual(utc, d2.tzinfo) - d2 = d2.replace(tzinfo=None) self.assertTrue(d2 - d1 < datetime.timedelta(seconds=2)) def test_from_datetime(self): if 'PyPy 1.8.0' in sys.version: # See https://bugs.pypy.org/issue1092 raise SkipTest("datetime.timedelta is broken in pypy 1.8.0") - d = datetime.datetime.utcnow() + # note: could use tz aware datetimes here? + d = datetime.datetime.now(utc).replace(tzinfo=None) + d = d - datetime.timedelta(microseconds=d.microsecond) oid = ObjectId.from_datetime(d) self.assertEqual(d, oid.generation_time.replace(tzinfo=None)) @@ -559,13 +559,9 @@ def test_pickle_backwards_compatability(self): b"object\np2\nNtp3\nRp4\n" b"S'M\\x9afV\\x13v\\xc0\\x0b\\x88\\x00\\x00\\x00'\np5\nb.") - if PY3: - # Have to load using 'latin-1' since these were pickled in python2.x. - oid_1_9 = pickle.loads(pickled_with_1_9, encoding='latin-1') - oid_1_10 = pickle.loads(pickled_with_1_10, encoding='latin-1') - else: - oid_1_9 = pickle.loads(pickled_with_1_9) - oid_1_10 = pickle.loads(pickled_with_1_10) + # Have to load using 'latin-1' since these were pickled in python2.x. + oid_1_9 = pickle.loads(pickled_with_1_9, encoding='latin-1') + oid_1_10 = pickle.loads(pickled_with_1_10, encoding='latin-1') self.assertEqual(oid_1_9, ObjectId("4d9a66561376c00b88000000")) self.assertEqual(oid_1_9, oid_1_10) diff --git a/bson/tests/test_random_tree.py b/bson/tests/test_random_tree.py index ca9832e..916fad5 100644 --- a/bson/tests/test_random_tree.py +++ b/bson/tests/test_random_tree.py @@ -3,9 +3,6 @@ from random import randint from unittest import TestCase -from six import text_type, PY3 -from six.moves import xrange - from bson import dumps, loads @@ -13,10 +10,10 @@ def populate(parent, howmany, max_children): if howmany > max_children: children = randint(2, max_children) distribution = [] - for _ in xrange(0, children - 1): + for _ in range(0, children - 1): distribution.append(int(howmany / children)) distribution.append(howmany - sum(distribution, 0)) - for i in xrange(0, children): + for i in range(0, children): steal_target = randint(0, children - 1) while steal_target == i: steal_target = randint(0, children -1) @@ -25,7 +22,7 @@ def populate(parent, howmany, max_children): distribution[i] += steal_count distribution[steal_target] -= steal_count - for i in xrange(0, children): + for i in range(0, children): make_dict = randint(0, 1) if make_dict: baby = {} @@ -34,8 +31,7 @@ def populate(parent, howmany, max_children): populate(baby, distribution[i], max_children) if isinstance(parent, dict): key = os.urandom(8) - key = "".join(chr(c) for c in hexlify(key)) \ - if PY3 else key.encode("hex") + key = "".join(chr(c) for c in hexlify(key)) parent[key] = baby else: parent.append(baby) @@ -44,17 +40,16 @@ def populate(parent, howmany, max_children): def populate_with_leaves(parent, howmany): - for _ in xrange(0, howmany): + for _ in range(0, howmany): leaf = os.urandom(4) - leaf = "".join(chr(c) for c in hexlify(leaf)) \ - if PY3 else leaf.encode("hex") - make_unicode = randint(0, 1) - if make_unicode: - leaf = text_type(leaf) + leaf = "".join(chr(c) for c in hexlify(leaf)) + # py3 -- all str are unicode ... + # make_unicode = randint(0, 1) + # if make_unicode: + # leaf = text_type(leaf) if isinstance(parent, dict): key = os.urandom(4) - key = "".join(chr(c) for c in hexlify(key)) \ - if PY3 else key.encode("hex") + key = "".join(chr(c) for c in hexlify(key)) parent[key] = leaf else: parent.append(leaf) @@ -62,7 +57,7 @@ def populate_with_leaves(parent, howmany): class TestRandomTree(TestCase): def test_random_tree(self): - for _ in xrange(0, 16): + for _ in range(0, 16): p = {} populate(p, 256, 4) sp = dumps(p)