diff --git a/docs/conf.py b/docs/conf.py index 5bf4a1d..3a96e45 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,7 +31,7 @@ def get_version(version_file): requirements list of this package (otherwise it cannot be executed in a fresh Python environment). """ - with open(version_file, 'r') as fp: + with open(version_file) as fp: version_source = fp.read() globals = {} exec(version_source, globals) @@ -97,12 +97,12 @@ def get_version(version_file): os.environ['BUILDING_DOCS'] = '1' # General information about the project. -project = u'nocasedict' +project = 'nocasedict' #copyright = u'' -author = u"Andreas Maier" +author = "Andreas Maier" # The short description of the package. -_short_description = u"A case-insensitive ordered dictionary for Python" +_short_description = "A case-insensitive ordered dictionary for Python" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -582,7 +582,7 @@ def __init__(self, *args, **kwargs): self._logger = logging.getLogger(__name__) # requires Sphinx 1.6.1 self._log_prefix = "conf.py/AutoAutoSummary" self._excluded_classes = ['BaseException'] - super(AutoAutoSummary, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def _get_members(self, class_obj, member_type, include_in_public=None): """ @@ -690,7 +690,7 @@ def run(self): self._log_prefix, exc.__class__.__name__, exc) finally: - return super(AutoAutoSummary, self).run() + return super().run() def setup(app): diff --git a/nocasedict/__init__.py b/nocasedict/__init__.py index 0d9f436..c0ff654 100644 --- a/nocasedict/__init__.py +++ b/nocasedict/__init__.py @@ -2,7 +2,6 @@ nocasedict - A case-insensitive ordered dictionary for Python """ -from __future__ import absolute_import from ._version import __version__ # noqa: F401 from ._nocasedict import * # noqa: F403,F401 diff --git a/nocasedict/_hashable.py b/nocasedict/_hashable.py index 98bcf4a..b2677b0 100644 --- a/nocasedict/_hashable.py +++ b/nocasedict/_hashable.py @@ -2,7 +2,6 @@ This module provides class HashableMixin. """ -from __future__ import print_function, absolute_import from typing import Any from ._nocasedict import NocaseDict diff --git a/nocasedict/_keyableby.py b/nocasedict/_keyableby.py index 14567a0..6d5c65a 100644 --- a/nocasedict/_keyableby.py +++ b/nocasedict/_keyableby.py @@ -3,7 +3,6 @@ class NocaseDict. """ -from __future__ import print_function, absolute_import from typing import Type diff --git a/nocasedict/_nocasedict.py b/nocasedict/_nocasedict.py index f2c7a55..7663cc3 100644 --- a/nocasedict/_nocasedict.py +++ b/nocasedict/_nocasedict.py @@ -30,27 +30,16 @@ The only class exposed by this package is :class:`nocasedict.NocaseDict`. """ -from __future__ import print_function, absolute_import -import sys import os -import warnings -from collections import OrderedDict from collections.abc import MutableMapping, KeysView, ValuesView, ItemsView from typing import Any, AnyStr, NoReturn, Optional, Iterator, Tuple, Dict -from ._utils import _stacklevel_above_nocasedict - __all__ = ['NocaseDict'] -# Starting with Python 3.7, the standard dict is guaranteed to be ordered. -# Note: In CPython, that already happened in 3.6, but it was not guaranteed -# for all implementations. -# pylint: disable=invalid-name -if sys.version_info[0:2] >= (3, 7): - _ODICT_TYPE = dict -else: - _ODICT_TYPE = OrderedDict +# Note: Since the minimum version for nocasedict is Python 3.8, the standard +# dict is guaranteed to be ordered and the implementation uses dict when an +# ordered dict is needed. Key = Optional[AnyStr] @@ -77,6 +66,7 @@ def __len__(self): return len(self._dict) def __contains__(self, x): + # pylint: disable=invalid-name return x in iter(self) def __reversed__(self): @@ -87,7 +77,7 @@ def __repr__(self): class dict_keys(_DictView, KeysView): - # pylint: disable=too-few-public-methods + # pylint: disable=too-few-public-methods,invalid-name """ Dictionary values view. """ @@ -100,7 +90,7 @@ def __iter__(self): class dict_values(_DictView, ValuesView): - # pylint: disable=too-few-public-methods + # pylint: disable=too-few-public-methods,invalid-name """ Dictionary values view. """ @@ -113,7 +103,7 @@ def __iter__(self): class dict_items(_DictView, ItemsView): - # pylint: disable=too-few-public-methods + # pylint: disable=too-few-public-methods,invalid-name """ Dictionary items view. """ @@ -263,7 +253,7 @@ def __init__(self, *args, **kwargs) -> None: # The internal dictionary, with casefolded keys. An item in this dict # is the tuple (original key, value). - self._data: Dict[Key, Any] = _ODICT_TYPE() + self._data: Dict[Key, Any] = {} self.update(*args, **kwargs) @@ -597,23 +587,12 @@ def update(self, *args, **kwargs) -> None: if args: if len(args) > 1: raise TypeError( - "Expected at most 1 positional argument, got {n}". - format(n=len(args))) + f"Expected at most 1 positional argument, got {len(args)}") other = args[0] try: # Try mapping / dictionary for key in other.keys(): self[key] = other[key] - # pylint: disable=unidiomatic-typecheck - if type(other) is dict and _ODICT_TYPE is not dict and \ - len(other.keys()) > 1: - warnings.warn( - "Before Python 3.7, initializing or updating a " - "NocaseDict object from a dict object with more than " - "one item is not guaranteed to preserve the order of " - "its items", - UserWarning, - stacklevel=_stacklevel_above_nocasedict()) except AttributeError: # Expecting an iterable @@ -632,9 +611,8 @@ def update(self, *args, **kwargs) -> None: key, value = item except ValueError as exc: value_error = ValueError( - "Cannot unpack positional argument item #{i} " - "of type {t} into key, value: {exc}". - format(i=i, t=type(item), exc=exc)) + f"Cannot unpack positional argument item #{i} " + f"of type {type(item)} into key, value: {exc}") value_error.__cause__ = None # Suppress 'During..' # pylint: disable=raise-missing-from raise value_error @@ -642,13 +620,6 @@ def update(self, *args, **kwargs) -> None: for key, val in kwargs.items(): self[key] = val - if len(kwargs) > 1 and _ODICT_TYPE is not dict: - warnings.warn( - "Before Python 3.7, initializing or updating a NocaseDict " - "object from more than one keyword argument is not guaranteed " - "to preserve their order", - UserWarning, - stacklevel=_stacklevel_above_nocasedict()) def clear(self) -> None: """ @@ -723,8 +694,8 @@ def _raise_ordering_not_supported(self, other: Any, op: str) -> NoReturn: is not supported. """ raise TypeError( - "'{}' not supported between instances of '{}' and '{}'". - format(op, type(self), type(other))) + f"'{op}' not supported between instances of '{type(self)}' and " + f"'{type(other)}'") def __lt__(self, other: Any) -> NoReturn: self._raise_ordering_not_supported(other, '<') diff --git a/nocasedict/_utils.py b/nocasedict/_utils.py deleted file mode 100644 index eaaec51..0000000 --- a/nocasedict/_utils.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Utility functions of the nocasedict package. -""" - -import inspect - - -def _stacklevel_above_nocasedict() -> int: - """ - Return the stack level (with 1 = caller of this function) of the first - caller that is not defined in the _nocasedict module and that is not - a method of a class named 'NocaseDict' (case insensitively). The second - check skips user classes derived from nocasedict.NocaseDict. - - The returned stack level can be used directly by the caller of this - function as an argument for the stacklevel parameter of warnings.warn(). - """ - stacklevel = 2 # start with caller of our caller - frame = inspect.stack()[stacklevel][0] # stack() level is 0-based - while True: - if frame.f_globals.get('__name__', None) != '_nocasedict': - try: - class_name = frame.f_locals['self'].__class__.__name__.lower() - except KeyError: - class_name = None - if class_name != 'nocasedict': - break - stacklevel += 1 - if frame.f_back is None: - break - frame = frame.f_back - del frame - return stacklevel diff --git a/setup.py b/setup.py index 609fbe6..7a577d9 100755 --- a/setup.py +++ b/setup.py @@ -5,7 +5,6 @@ import sys import os -import io import re from typing import Optional @@ -21,7 +20,7 @@ def get_version(version_file): requirements list of this package (otherwise it cannot be executed in a fresh Python environment). """ - with io.open(version_file, 'r', encoding='utf-8') as fp: + with open(version_file, encoding='utf-8') as fp: version_source = fp.read() _globals = {} exec(version_source, _globals) # pylint: disable=exec-used @@ -34,7 +33,7 @@ def get_requirements(requirements_file): non-comment lines. The returned lines are without any trailing newline characters. """ - with io.open(requirements_file, 'r', encoding='utf-8') as fp: + with open(requirements_file, encoding='utf-8') as fp: lines = fp.readlines() reqs = [] for line in lines: @@ -48,7 +47,7 @@ def read_file(a_file): """ Read the specified file and return its content as one string. """ - with io.open(a_file, 'r', encoding='utf-8') as fp: + with open(a_file, encoding='utf-8') as fp: content = fp.read() return content diff --git a/tests/unittest/test_hashable.py b/tests/unittest/test_hashable.py index 7b82b51..9e11399 100644 --- a/tests/unittest/test_hashable.py +++ b/tests/unittest/test_hashable.py @@ -2,7 +2,6 @@ Test the HashableMixin mixin class. """ -from __future__ import absolute_import import os import pytest @@ -155,7 +154,7 @@ def __hash__(self): "Comparing unicode value with bytes value", dict( obj1=MyNocaseDict([('k1', b'v1')]), - obj2=MyNocaseDict([('k2', u'v2')]), + obj2=MyNocaseDict([('k2', 'v2')]), exp_obj_equal=False, ), None, None, True @@ -164,7 +163,7 @@ def __hash__(self): "Matching unicode key with string key", dict( obj1=MyNocaseDict([('k1', 'v1')]), - obj2=MyNocaseDict([(u'k2', 'v2')]), + obj2=MyNocaseDict([('k2', 'v2')]), exp_obj_equal=False, ), None, None, True diff --git a/tests/unittest/test_keyableby.py b/tests/unittest/test_keyableby.py index feae3ba..58746af 100644 --- a/tests/unittest/test_keyableby.py +++ b/tests/unittest/test_keyableby.py @@ -2,7 +2,6 @@ Test the KeyableByMixin() mixin function. """ -from __future__ import absolute_import import os import pytest diff --git a/tests/unittest/test_nocasedict.py b/tests/unittest/test_nocasedict.py index 16ad7d3..11907e2 100755 --- a/tests/unittest/test_nocasedict.py +++ b/tests/unittest/test_nocasedict.py @@ -2,7 +2,6 @@ Test the NocaseDict class. """ -from __future__ import absolute_import import sys import os @@ -2679,7 +2678,7 @@ def test_NocaseDict_copy(testcase, "Comparing unicode value with bytes value", dict( obj1=NocaseDict([('k1', b'v1')]), - obj2=NocaseDict([('k2', u'v2')]), + obj2=NocaseDict([('k2', 'v2')]), exp_obj_equal=False, ), None, None, True @@ -2688,7 +2687,7 @@ def test_NocaseDict_copy(testcase, "Matching unicode key with string key", dict( obj1=NocaseDict([('k1', 'v1')]), - obj2=NocaseDict([(u'k2', 'v2')]), + obj2=NocaseDict([('k2', 'v2')]), exp_obj_equal=False, ), None, None, True diff --git a/tests/utils/import_installed.py b/tests/utils/import_installed.py index b71f21f..e742edc 100644 --- a/tests/utils/import_installed.py +++ b/tests/utils/import_installed.py @@ -3,7 +3,6 @@ package. """ -from __future__ import absolute_import, print_function import sys import os @@ -79,11 +78,11 @@ def import_installed(module_name): if module_name not in sys.modules: module = __import__(module_name, level=0) # only absolute imports if test_installed == 'DEBUG': - print("Debug: {0} module newly loaded from: {1}". - format(module_name, module.__file__)) + print(f"Debug: {module_name} module newly loaded from: " + f"{module.__file__}") else: module = sys.modules[module_name] if test_installed == 'DEBUG': - print("Debug: {0} module was already loaded from: {1}". - format(module_name, module.__file__)) + print(f"Debug: {module_name} module was already loaded from: " + f"{module.__file__}") return module diff --git a/tests/utils/simplified_test_function.py b/tests/utils/simplified_test_function.py index b31ef80..340eb8d 100644 --- a/tests/utils/simplified_test_function.py +++ b/tests/utils/simplified_test_function.py @@ -2,7 +2,6 @@ simplified_test_function - Pytest extension for simplifying test functions. """ -from __future__ import absolute_import import functools import warnings @@ -174,7 +173,7 @@ def wrapper_func(desc, kwargs, exp_exc_types, exp_warn_types, condition): for w in rec_warnings: tup = (w.filename, w.lineno, w.category.__name__, str(w.message)) - line = "{t[0]}:{t[1]}: {t[2]}: {t[3]}".format(t=tup) + line = f"{tup[0]}:{tup[1]}: {tup[2]}: {tup[3]}" if line not in lines: lines.append(line) msg = "Unexpected warnings:\n{}".format(