Skip to content

Commit a13ac31

Browse files
authored
Add support for Python 3.11 and 3.12 (#217)
* Add support for Python 3.11 and test 3.12-dev * Remove redudant code for EOL Python versions * Upgrade Python syntax with pyupgrade --py37-plus * Add support for Python 3.12 * Upgrade Python syntax with pyupgrade --py37-plus --------- Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
1 parent 94e2557 commit a13ac31

25 files changed

Lines changed: 72 additions & 285 deletions

.github/workflows/integration.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,15 @@ jobs:
4040
max-parallel: 4
4141
matrix:
4242
os: [ubuntu-latest]
43-
python-version: [3.7, 3.8, 3.9, '3.10']
43+
python-version: [3.7, 3.8, 3.9, '3.10', 3.11, 3.12]
4444

4545
steps:
4646
- uses: actions/checkout@v3
4747

4848
- uses: actions/setup-python@v4
4949
with:
5050
python-version: ${{ matrix.python-version }}
51+
allow-prereleases: true
5152

5253
- name: Install tox
5354
run: |

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ Features
112112
- Feature-rich (e.g. get the five largest keys in a sorted dict: d.keys()[-5:])
113113
- Pragmatic design (e.g. SortedSet is a Python set with a SortedList index)
114114
- Developed on Python 3.10
115-
- Tested with CPython 3.7, 3.8, 3.9, 3.10 and PyPy3
115+
- Tested with CPython 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 and PyPy3
116116
- Tested on Linux, Mac OSX, and Windows
117117

118118
.. image:: https://github.com/grantjenks/python-sortedcontainers/workflows/integration/badge.svg

docs/conf.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- coding: utf-8 -*-
21
#
32
# Configuration file for the Sphinx documentation builder.
43
#

setup.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def run_tests(self):
3030
long_description=readme,
3131
author='Grant Jenks',
3232
author_email='contact@grantjenks.com',
33-
url='http://www.grantjenks.com/docs/sortedcontainers/',
33+
url='https://www.grantjenks.com/docs/sortedcontainers/',
3434
license='Apache 2.0',
3535
package_dir={'': 'src'},
3636
packages=['sortedcontainers'],
@@ -48,6 +48,8 @@ def run_tests(self):
4848
'Programming Language :: Python :: 3.8',
4949
'Programming Language :: Python :: 3.9',
5050
'Programming Language :: Python :: 3.10',
51+
'Programming Language :: Python :: 3.11',
52+
'Programming Language :: Python :: 3.12',
5153
'Programming Language :: Python :: Implementation :: CPython',
5254
'Programming Language :: Python :: Implementation :: PyPy',
5355
],

src/sortedcontainers/sorteddict.py

Lines changed: 7 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,16 @@
1616
1717
"""
1818

19-
import sys
2019
import warnings
2120

21+
from collections.abc import (
22+
ItemsView, KeysView, Mapping, ValuesView, Sequence
23+
)
2224
from itertools import chain
2325

2426
from .sortedlist import SortedList, recursive_repr
2527
from .sortedset import SortedSet
2628

27-
###############################################################################
28-
# BEGIN Python 2/3 Shims
29-
###############################################################################
30-
31-
try:
32-
from collections.abc import (
33-
ItemsView, KeysView, Mapping, ValuesView, Sequence
34-
)
35-
except ImportError:
36-
from collections import ItemsView, KeysView, Mapping, ValuesView, Sequence
37-
38-
###############################################################################
39-
# END Python 2/3 Shims
40-
###############################################################################
41-
4229

4330
class SortedDict(dict):
4431
"""Sorted dict is a sorted mutable mapping.
@@ -382,30 +369,7 @@ def values(self):
382369
"""
383370
return SortedValuesView(self)
384371

385-
386-
if sys.hexversion < 0x03000000:
387-
def __make_raise_attributeerror(original, alternate):
388-
# pylint: disable=no-self-argument
389-
message = (
390-
'SortedDict.{original}() is not implemented.'
391-
' Use SortedDict.{alternate}() instead.'
392-
).format(original=original, alternate=alternate)
393-
def method(self):
394-
# pylint: disable=missing-docstring,unused-argument
395-
raise AttributeError(message)
396-
method.__name__ = original # pylint: disable=non-str-assignment-to-dunder-name
397-
method.__doc__ = message
398-
return property(method)
399-
400-
iteritems = __make_raise_attributeerror('iteritems', 'items')
401-
iterkeys = __make_raise_attributeerror('iterkeys', 'keys')
402-
itervalues = __make_raise_attributeerror('itervalues', 'values')
403-
viewitems = __make_raise_attributeerror('viewitems', 'items')
404-
viewkeys = __make_raise_attributeerror('viewkeys', 'keys')
405-
viewvalues = __make_raise_attributeerror('viewvalues', 'values')
406-
407-
408-
class _NotGiven(object):
372+
class _NotGiven:
409373
# pylint: disable=too-few-public-methods
410374
def __repr__(self):
411375
return '<not-given>'
@@ -599,10 +563,10 @@ def __repr__(self):
599563
"""
600564
_key = self._key
601565
type_name = type(self).__name__
602-
key_arg = '' if _key is None else '{0!r}, '.format(_key)
603-
item_format = '{0!r}: {1!r}'.format
566+
key_arg = '' if _key is None else f'{_key!r}, '
567+
item_format = '{!r}: {!r}'.format
604568
items = ', '.join(item_format(key, self[key]) for key in self._list)
605-
return '{0}({1}{{{2}}})'.format(type_name, key_arg, items)
569+
return f'{type_name}({key_arg}{{{items}}})'
606570

607571

608572
def _check(self):

src/sortedcontainers/sortedlist.py

Lines changed: 24 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -14,72 +14,18 @@
1414
1515
"""
1616
# pylint: disable=too-many-lines
17-
from __future__ import print_function
1817

1918
import sys
2019
import traceback
2120

2221
from bisect import bisect_left, bisect_right, insort
22+
from collections.abc import Sequence, MutableSequence
23+
from functools import reduce
2324
from itertools import chain, repeat, starmap
2425
from math import log
2526
from operator import add, eq, ne, gt, ge, lt, le, iadd
2627
from textwrap import dedent
27-
28-
###############################################################################
29-
# BEGIN Python 2/3 Shims
30-
###############################################################################
31-
32-
try:
33-
from collections.abc import Sequence, MutableSequence
34-
except ImportError:
35-
from collections import Sequence, MutableSequence
36-
37-
from functools import wraps
38-
from sys import hexversion
39-
40-
if hexversion < 0x03000000:
41-
from itertools import imap as map # pylint: disable=redefined-builtin
42-
from itertools import izip as zip # pylint: disable=redefined-builtin
43-
try:
44-
from thread import get_ident
45-
except ImportError:
46-
from dummy_thread import get_ident
47-
else:
48-
from functools import reduce
49-
try:
50-
from _thread import get_ident
51-
except ImportError:
52-
from _dummy_thread import get_ident
53-
54-
55-
def recursive_repr(fillvalue='...'):
56-
"Decorator to make a repr function return fillvalue for a recursive call."
57-
# pylint: disable=missing-docstring
58-
# Copied from reprlib in Python 3
59-
# https://hg.python.org/cpython/file/3.6/Lib/reprlib.py
60-
61-
def decorating_function(user_function):
62-
repr_running = set()
63-
64-
@wraps(user_function)
65-
def wrapper(self):
66-
key = id(self), get_ident()
67-
if key in repr_running:
68-
return fillvalue
69-
repr_running.add(key)
70-
try:
71-
result = user_function(self)
72-
finally:
73-
repr_running.discard(key)
74-
return result
75-
76-
return wrapper
77-
78-
return decorating_function
79-
80-
###############################################################################
81-
# END Python 2/3 Shims
82-
###############################################################################
28+
from reprlib import recursive_repr
8329

8430

8531
class SortedList(MutableSequence):
@@ -446,20 +392,20 @@ def remove(self, value):
446392
_maxes = self._maxes
447393

448394
if not _maxes:
449-
raise ValueError('{0!r} not in list'.format(value))
395+
raise ValueError(f'{value!r} not in list')
450396

451397
pos = bisect_left(_maxes, value)
452398

453399
if pos == len(_maxes):
454-
raise ValueError('{0!r} not in list'.format(value))
400+
raise ValueError(f'{value!r} not in list')
455401

456402
_lists = self._lists
457403
idx = bisect_left(_lists[pos], value)
458404

459405
if _lists[pos][idx] == value:
460406
self._delete(pos, idx)
461407
else:
462-
raise ValueError('{0!r} not in list'.format(value))
408+
raise ValueError(f'{value!r} not in list')
463409

464410

465411
def _delete(self, pos, idx):
@@ -1407,7 +1353,7 @@ def index(self, value, start=None, stop=None):
14071353
_len = self._len
14081354

14091355
if not _len:
1410-
raise ValueError('{0!r} is not in list'.format(value))
1356+
raise ValueError(f'{value!r} is not in list')
14111357

14121358
if start is None:
14131359
start = 0
@@ -1424,19 +1370,19 @@ def index(self, value, start=None, stop=None):
14241370
stop = _len
14251371

14261372
if stop <= start:
1427-
raise ValueError('{0!r} is not in list'.format(value))
1373+
raise ValueError(f'{value!r} is not in list')
14281374

14291375
_maxes = self._maxes
14301376
pos_left = bisect_left(_maxes, value)
14311377

14321378
if pos_left == len(_maxes):
1433-
raise ValueError('{0!r} is not in list'.format(value))
1379+
raise ValueError(f'{value!r} is not in list')
14341380

14351381
_lists = self._lists
14361382
idx_left = bisect_left(_lists[pos_left], value)
14371383

14381384
if _lists[pos_left][idx_left] != value:
1439-
raise ValueError('{0!r} is not in list'.format(value))
1385+
raise ValueError(f'{value!r} is not in list')
14401386

14411387
stop -= 1
14421388
left = self._loc(pos_left, idx_left)
@@ -1450,7 +1396,7 @@ def index(self, value, start=None, stop=None):
14501396
if start <= right:
14511397
return start
14521398

1453-
raise ValueError('{0!r} is not in list'.format(value))
1399+
raise ValueError(f'{value!r} is not in list')
14541400

14551401

14561402
def __add__(self, other):
@@ -1566,7 +1512,7 @@ def comparer(self, other):
15661512
return seq_op(self_len, len_other)
15671513

15681514
seq_op_name = seq_op.__name__
1569-
comparer.__name__ = '__{0}__'.format(seq_op_name)
1515+
comparer.__name__ = f'__{seq_op_name}__'
15701516
doc_str = """Return true if and only if sorted list is {0} `other`.
15711517
15721518
``sl.__{1}__(other)`` <==> ``sl {2} other``
@@ -1606,7 +1552,7 @@ def __repr__(self):
16061552
:return: string representation
16071553
16081554
"""
1609-
return '{0}({1!r})'.format(type(self).__name__, list(self))
1555+
return f'{type(self).__name__}({list(self)!r})'
16101556

16111557

16121558
def _check(self):
@@ -2022,13 +1968,13 @@ def remove(self, value):
20221968
_maxes = self._maxes
20231969

20241970
if not _maxes:
2025-
raise ValueError('{0!r} not in list'.format(value))
1971+
raise ValueError(f'{value!r} not in list')
20261972

20271973
key = self._key(value)
20281974
pos = bisect_left(_maxes, key)
20291975

20301976
if pos == len(_maxes):
2031-
raise ValueError('{0!r} not in list'.format(value))
1977+
raise ValueError(f'{value!r} not in list')
20321978

20331979
_lists = self._lists
20341980
_keys = self._keys
@@ -2038,15 +1984,15 @@ def remove(self, value):
20381984

20391985
while True:
20401986
if _keys[pos][idx] != key:
2041-
raise ValueError('{0!r} not in list'.format(value))
1987+
raise ValueError(f'{value!r} not in list')
20421988
if _lists[pos][idx] == value:
20431989
self._delete(pos, idx)
20441990
return
20451991
idx += 1
20461992
if idx == len_sublist:
20471993
pos += 1
20481994
if pos == len_keys:
2049-
raise ValueError('{0!r} not in list'.format(value))
1995+
raise ValueError(f'{value!r} not in list')
20501996
len_sublist = len(_keys[pos])
20511997
idx = 0
20521998

@@ -2443,7 +2389,7 @@ def index(self, value, start=None, stop=None):
24432389
_len = self._len
24442390

24452391
if not _len:
2446-
raise ValueError('{0!r} is not in list'.format(value))
2392+
raise ValueError(f'{value!r} is not in list')
24472393

24482394
if start is None:
24492395
start = 0
@@ -2460,14 +2406,14 @@ def index(self, value, start=None, stop=None):
24602406
stop = _len
24612407

24622408
if stop <= start:
2463-
raise ValueError('{0!r} is not in list'.format(value))
2409+
raise ValueError(f'{value!r} is not in list')
24642410

24652411
_maxes = self._maxes
24662412
key = self._key(value)
24672413
pos = bisect_left(_maxes, key)
24682414

24692415
if pos == len(_maxes):
2470-
raise ValueError('{0!r} is not in list'.format(value))
2416+
raise ValueError(f'{value!r} is not in list')
24712417

24722418
stop -= 1
24732419
_lists = self._lists
@@ -2478,7 +2424,7 @@ def index(self, value, start=None, stop=None):
24782424

24792425
while True:
24802426
if _keys[pos][idx] != key:
2481-
raise ValueError('{0!r} is not in list'.format(value))
2427+
raise ValueError(f'{value!r} is not in list')
24822428
if _lists[pos][idx] == value:
24832429
loc = self._loc(pos, idx)
24842430
if start <= loc <= stop:
@@ -2489,11 +2435,11 @@ def index(self, value, start=None, stop=None):
24892435
if idx == len_sublist:
24902436
pos += 1
24912437
if pos == len_keys:
2492-
raise ValueError('{0!r} is not in list'.format(value))
2438+
raise ValueError(f'{value!r} is not in list')
24932439
len_sublist = len(_keys[pos])
24942440
idx = 0
24952441

2496-
raise ValueError('{0!r} is not in list'.format(value))
2442+
raise ValueError(f'{value!r} is not in list')
24972443

24982444

24992445
def __add__(self, other):
@@ -2557,7 +2503,7 @@ def __repr__(self):
25572503
25582504
"""
25592505
type_name = type(self).__name__
2560-
return '{0}({1!r}, key={2!r})'.format(type_name, list(self), self._key)
2506+
return f'{type_name}({list(self)!r}, key={self._key!r})'
25612507

25622508

25632509
def _check(self):

0 commit comments

Comments
 (0)