diff --git a/.travis.yml b/.travis.yml index b31b114c..18014285 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,15 +5,11 @@ python: - 3.8 - 3.7 - 3.6 - - 3.5 - - 2.7 env: - JEDI_TEST_ENVIRONMENT=38 - JEDI_TEST_ENVIRONMENT=37 - JEDI_TEST_ENVIRONMENT=36 - - JEDI_TEST_ENVIRONMENT=35 - - JEDI_TEST_ENVIRONMENT=27 - JEDI_TEST_ENVIRONMENT=interpreter matrix: diff --git a/README.rst b/README.rst index 2541797e..1dbe7ed7 100644 --- a/README.rst +++ b/README.rst @@ -98,7 +98,7 @@ Features and Limitations Jedi's features are listed here: `Features `_. -You can run Jedi on CPython 2.7 or 3.5+ but it should also +You can run Jedi on Python 3.6+ but it should also understand code that is older than those versions. Additionally you should be able to use `Virtualenvs `_ very well. diff --git a/appveyor.yml b/appveyor.yml index a68fa11e..007ad915 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,12 +6,6 @@ environment: - TOXENV: py37 PYTHON_PATH: C:\Python37 JEDI_TEST_ENVIRONMENT: 36 - - TOXENV: py37 - PYTHON_PATH: C:\Python37 - JEDI_TEST_ENVIRONMENT: 35 - - TOXENV: py37 - PYTHON_PATH: C:\Python37 - JEDI_TEST_ENVIRONMENT: 27 - TOXENV: py36 PYTHON_PATH: C:\Python36 @@ -19,38 +13,6 @@ environment: - TOXENV: py36 PYTHON_PATH: C:\Python36 JEDI_TEST_ENVIRONMENT: 36 - - TOXENV: py36 - PYTHON_PATH: C:\Python36 - JEDI_TEST_ENVIRONMENT: 35 - - TOXENV: py36 - PYTHON_PATH: C:\Python36 - JEDI_TEST_ENVIRONMENT: 27 - - - TOXENV: py35 - PYTHON_PATH: C:\Python35 - JEDI_TEST_ENVIRONMENT: 37 - - TOXENV: py35 - PYTHON_PATH: C:\Python35 - JEDI_TEST_ENVIRONMENT: 36 - - TOXENV: py35 - PYTHON_PATH: C:\Python35 - JEDI_TEST_ENVIRONMENT: 35 - - TOXENV: py35 - PYTHON_PATH: C:\Python35 - JEDI_TEST_ENVIRONMENT: 27 - - - TOXENV: py27 - PYTHON_PATH: C:\Python27 - JEDI_TEST_ENVIRONMENT: 37 - - TOXENV: py27 - PYTHON_PATH: C:\Python27 - JEDI_TEST_ENVIRONMENT: 36 - - TOXENV: py27 - PYTHON_PATH: C:\Python27 - JEDI_TEST_ENVIRONMENT: 35 - - TOXENV: py27 - PYTHON_PATH: C:\Python27 - JEDI_TEST_ENVIRONMENT: 27 install: - git submodule update --init --recursive - set PATH=%PYTHON_PATH%;%PYTHON_PATH%\Scripts;%PATH% diff --git a/conftest.py b/conftest.py index 08c5e81a..d4aaae42 100644 --- a/conftest.py +++ b/conftest.py @@ -8,7 +8,6 @@ import pytest import jedi from jedi.api.environment import get_system_environment, InterpreterEnvironment -from jedi._compatibility import py_version from test.helpers import test_dir collect_ignore = [ @@ -18,9 +17,6 @@ collect_ignore = [ 'build/', 'test/examples', ] -if sys.version_info < (3, 6): - # Python 2 not supported syntax - collect_ignore.append('test/test_inference/test_mixed.py') # The following hooks (pytest_configure, pytest_unconfigure) are used @@ -45,7 +41,7 @@ def pytest_addoption(parser): help="Warnings are treated as errors.") parser.addoption("--env", action='store', - help="Execute the tests in that environment (e.g. 35 for python3.5).") + help="Execute the tests in that environment (e.g. 39 for python3.9).") parser.addoption("--interpreter-env", "-I", action='store_true', help="Don't use subprocesses to guarantee having safe " "code execution. Useful for debugging.") @@ -97,7 +93,8 @@ def clean_jedi_cache(request): def environment(request): version = request.config.option.env if version is None: - version = os.environ.get('JEDI_TEST_ENVIRONMENT', str(py_version)) + v = str(sys.version_info[0]) + str(sys.version_info[1]) + version = os.environ.get('JEDI_TEST_ENVIRONMENT', v) if request.config.option.interpreter_env or version == 'interpreter': return InterpreterEnvironment() @@ -136,17 +133,6 @@ def goto_or_help_or_infer(request, Script): return lambda code, *args, **kwargs: getattr(Script(code), request.param)(*args, **kwargs) -@pytest.fixture(scope='session') -def has_typing(environment): - if environment.version_info >= (3, 5, 0): - # This if is just needed to avoid that tests ever skip way more than - # they should for all Python versions. - return True - - script = jedi.Script('import typing', environment=environment) - return bool(script.infer()) - - @pytest.fixture(scope='session') def has_django(environment): script = jedi.Script('import django', environment=environment) @@ -158,14 +144,6 @@ def jedi_path(): return os.path.dirname(__file__) -@pytest.fixture() -def skip_python2(environment): - if environment.version_info.major == 2: - # This if is just needed to avoid that tests ever skip way more than - # they should for all Python versions. - pytest.skip() - - @pytest.fixture() def skip_pre_python38(environment): if environment.version_info < (3, 8): @@ -180,19 +158,3 @@ def skip_pre_python37(environment): # This if is just needed to avoid that tests ever skip way more than # they should for all Python versions. pytest.skip() - - -@pytest.fixture() -def skip_pre_python35(environment): - if environment.version_info < (3, 5): - # This if is just needed to avoid that tests ever skip way more than - # they should for all Python versions. - pytest.skip() - - -@pytest.fixture() -def skip_pre_python36(environment): - if environment.version_info < (3, 6): - # This if is just needed to avoid that tests ever skip way more than - # they should for all Python versions. - pytest.skip() diff --git a/docs/conf.py b/docs/conf.py index 57015f3f..057a2360 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -# # Jedi documentation build configuration file, created by # sphinx-quickstart on Wed Dec 26 00:11:34 2012. # @@ -43,8 +41,8 @@ source_encoding = 'utf-8' master_doc = 'index' # General information about the project. -project = u'Jedi' -copyright = u'jedi contributors' +project = 'Jedi' +copyright = 'jedi contributors' import jedi from jedi.utils import version_info @@ -205,8 +203,8 @@ latex_elements = { # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'Jedi.tex', u'Jedi Documentation', - u'Jedi contributors', 'manual'), + ('index', 'Jedi.tex', 'Jedi Documentation', + 'Jedi contributors', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -235,8 +233,8 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'jedi', u'Jedi Documentation', - [u'Jedi contributors'], 1) + ('index', 'jedi', 'Jedi Documentation', + ['Jedi contributors'], 1) ] # If true, show URL addresses after external links. @@ -249,8 +247,8 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'Jedi', u'Jedi Documentation', - u'Jedi contributors', 'Jedi', 'Awesome Python autocompletion library.', + ('index', 'Jedi', 'Jedi Documentation', + 'Jedi contributors', 'Jedi', 'Awesome Python autocompletion library.', 'Miscellaneous'), ] diff --git a/docs/docs/api.rst b/docs/docs/api.rst index 3171ab32..33346d7d 100644 --- a/docs/docs/api.rst +++ b/docs/docs/api.rst @@ -107,7 +107,7 @@ Completions >>> code = '''import json; json.l''' >>> script = jedi.Script(code, path='example.py') >>> script - > + > >>> completions = script.complete(1, 19) >>> completions [, ] diff --git a/docs/docs/features.rst b/docs/docs/features.rst index c77418db..503b67db 100644 --- a/docs/docs/features.rst +++ b/docs/docs/features.rst @@ -16,7 +16,7 @@ Jedi's main API calls and features are: Basic Features -------------- -- Python 2.7 and 3.5+ support +- Python 3.6+ support - Ignores syntax errors and wrong indentation - Can deal with complex module / function / class structures - Great ``virtualenv``/``venv`` support diff --git a/docs/docs/installation.rst b/docs/docs/installation.rst index 46e5ba90..d8df120c 100644 --- a/docs/docs/installation.rst +++ b/docs/docs/installation.rst @@ -50,14 +50,6 @@ Arch Linux You can install |jedi| directly from official Arch Linux packages: - `python-jedi `__ - (Python 3) -- `python2-jedi `__ - (Python 2) - -The specified Python version just refers to the *runtime environment* for -|jedi|. Use the Python 2 version if you're running vim (or whatever editor you -use) under Python 2. Otherwise, use the Python 3 version. But whatever version -you choose, both are able to complete both Python 2 and 3 *code*. (There is also a packaged version of the vim plugin available: `vim-jedi at Arch Linux `__.) diff --git a/jedi/_compatibility.py b/jedi/_compatibility.py index 13c1975c..6bb3eeaa 100644 --- a/jedi/_compatibility.py +++ b/jedi/_compatibility.py @@ -1,292 +1,13 @@ """ -To ensure compatibility from Python ``2.7`` - ``3.x``, a module has been -created. Clearly there is huge need to use conforming syntax. +This module is here to ensure compatibility of Windows/Linux/MacOS and +different Python versions. """ -from __future__ import print_function -import atexit import errno -import functools import sys -import os -import re -import pkgutil -import warnings -import subprocess -import weakref -try: - import importlib -except ImportError: - pass -from zipimport import zipimporter - -from jedi.file_io import KnownContentFileIO, ZipFileIO - -is_py3 = sys.version_info[0] >= 3 -is_py35 = is_py3 and sys.version_info[1] >= 5 -py_version = int(str(sys.version_info[0]) + str(sys.version_info[1])) +import pickle -if sys.version_info[:2] < (3, 5): - """ - A super-minimal shim around listdir that behave like - scandir for the information we need. - """ - class _DirEntry: - - def __init__(self, name, basepath): - self.name = name - self.basepath = basepath - - def is_dir(self): - path_for_name = os.path.join(self.basepath, self.name) - return os.path.isdir(path_for_name) - - def scandir(dir): - return [_DirEntry(name, dir) for name in os.listdir(dir)] -else: - from os import scandir - - -class DummyFile(object): - def __init__(self, loader, string): - self.loader = loader - self.string = string - - def read(self): - return self.loader.get_source(self.string) - - def close(self): - del self.loader - - -def find_module_py34(string, path=None, full_name=None, is_global_search=True): - spec = None - loader = None - - for finder in sys.meta_path: - if is_global_search and finder != importlib.machinery.PathFinder: - p = None - else: - p = path - try: - find_spec = finder.find_spec - except AttributeError: - # These are old-school clases that still have a different API, just - # ignore those. - continue - - spec = find_spec(string, p) - if spec is not None: - loader = spec.loader - if loader is None and not spec.has_location: - # This is a namespace package. - full_name = string if not path else full_name - implicit_ns_info = ImplicitNSInfo(full_name, spec.submodule_search_locations._path) - return implicit_ns_info, True - break - - return find_module_py33(string, path, loader) - - -def find_module_py33(string, path=None, loader=None, full_name=None, is_global_search=True): - loader = loader or importlib.machinery.PathFinder.find_module(string, path) - - if loader is None and path is None: # Fallback to find builtins - try: - with warnings.catch_warnings(record=True): - # Mute "DeprecationWarning: Use importlib.util.find_spec() - # instead." While we should replace that in the future, it's - # probably good to wait until we deprecate Python 3.3, since - # it was added in Python 3.4 and find_loader hasn't been - # removed in 3.6. - loader = importlib.find_loader(string) - except ValueError as e: - # See #491. Importlib might raise a ValueError, to avoid this, we - # just raise an ImportError to fix the issue. - raise ImportError("Originally " + repr(e)) - - if loader is None: - raise ImportError("Couldn't find a loader for {}".format(string)) - - return _from_loader(loader, string) - - -def _from_loader(loader, string): - try: - is_package_method = loader.is_package - except AttributeError: - is_package = False - else: - is_package = is_package_method(string) - try: - get_filename = loader.get_filename - except AttributeError: - return None, is_package - else: - module_path = cast_path(get_filename(string)) - - # To avoid unicode and read bytes, "overwrite" loader.get_source if - # possible. - try: - f = type(loader).get_source - except AttributeError: - raise ImportError("get_source was not defined on loader") - - if is_py3 and f is not importlib.machinery.SourceFileLoader.get_source: - # Unfortunately we are reading unicode here, not bytes. - # It seems hard to get bytes, because the zip importer - # logic just unpacks the zip file and returns a file descriptor - # that we cannot as easily access. Therefore we just read it as - # a string in the cases where get_source was overwritten. - code = loader.get_source(string) - else: - code = _get_source(loader, string) - - if code is None: - return None, is_package - if isinstance(loader, zipimporter): - return ZipFileIO(module_path, code, cast_path(loader.archive)), is_package - - return KnownContentFileIO(module_path, code), is_package - - -def _get_source(loader, fullname): - """ - This method is here as a replacement for SourceLoader.get_source. That - method returns unicode, but we prefer bytes. - """ - path = loader.get_filename(fullname) - try: - return loader.get_data(path) - except OSError: - raise ImportError('source not available through get_data()', - name=fullname) - - -def find_module_pre_py3(string, path=None, full_name=None, is_global_search=True): - # This import is here, because in other places it will raise a - # DeprecationWarning. - import imp - try: - module_file, module_path, description = imp.find_module(string, path) - module_type = description[2] - is_package = module_type is imp.PKG_DIRECTORY - if is_package: - # In Python 2 directory package imports are returned as folder - # paths, not __init__.py paths. - p = os.path.join(module_path, '__init__.py') - try: - module_file = open(p) - module_path = p - except FileNotFoundError: - pass - elif module_type != imp.PY_SOURCE: - if module_file is not None: - module_file.close() - module_file = None - - if module_file is None: - return None, is_package - - with module_file: - code = module_file.read() - return KnownContentFileIO(cast_path(module_path), code), is_package - except ImportError: - pass - - if path is None: - path = sys.path - for item in path: - loader = pkgutil.get_importer(item) - if loader: - loader = loader.find_module(string) - if loader is not None: - return _from_loader(loader, string) - raise ImportError("No module named {}".format(string)) - - -find_module = find_module_py34 if is_py3 else find_module_pre_py3 -find_module.__doc__ = """ -Provides information about a module. - -This function isolates the differences in importing libraries introduced with -python 3.3 on; it gets a module name and optionally a path. It will return a -tuple containin an open file for the module (if not builtin), the filename -or the name of the module if it is a builtin one and a boolean indicating -if the module is contained in a package. -""" - - -class ImplicitNSInfo(object): - """Stores information returned from an implicit namespace spec""" - def __init__(self, name, paths): - self.name = name - self.paths = paths - - -if is_py3: - all_suffixes = importlib.machinery.all_suffixes -else: - def all_suffixes(): - # Is deprecated and raises a warning in Python 3.6. - import imp - return [suffix for suffix, _, _ in imp.get_suffixes()] - - -# unicode function -try: - unicode = unicode -except NameError: - unicode = str - - -# re-raise function -if is_py3: - def reraise(exception, traceback): - raise exception.with_traceback(traceback) -else: - eval(compile(""" -def reraise(exception, traceback): - raise exception, None, traceback -""", 'blub', 'exec')) - -reraise.__doc__ = """ -Re-raise `exception` with a `traceback` object. - -Usage:: - - reraise(Exception, sys.exc_info()[2]) - -""" - - -def use_metaclass(meta, *bases): - """ Create a class with a metaclass. """ - if not bases: - bases = (object,) - return meta("Py2CompatibilityMetaClass", bases, {}) - - -try: - encoding = sys.stdout.encoding - if encoding is None: - encoding = 'utf-8' -except AttributeError: - encoding = 'ascii' - - -def u(string, errors='strict'): - """Cast to unicode DAMMIT! - Written because Python2 repr always implicitly casts to a string, so we - have to cast back to a unicode (and we now that we always deal with valid - unicode, because we check that in the beginning). - """ - if isinstance(string, bytes): - return unicode(string, encoding='UTF-8', errors=errors) - return string - - -def cast_path(obj): +def cast_path(string): """ Take a bytes or str path and cast it to unicode. @@ -297,103 +18,13 @@ def cast_path(obj): Since this just really complicates everything and Python 2.7 will be EOL soon anyway, just go with always strings. """ - return u(obj, errors='replace') - - -def force_unicode(obj): - # Intentionally don't mix those two up, because those two code paths might - # be different in the future (maybe windows?). - return cast_path(obj) - - -try: - import builtins # module name in python 3 -except ImportError: - import __builtin__ as builtins # noqa: F401 - - -import ast # noqa: F401 - - -def literal_eval(string): - return ast.literal_eval(string) - - -try: - from itertools import zip_longest -except ImportError: - from itertools import izip_longest as zip_longest # Python 2 # noqa: F401 - -try: - FileNotFoundError = FileNotFoundError -except NameError: - FileNotFoundError = IOError - -try: - IsADirectoryError = IsADirectoryError -except NameError: - IsADirectoryError = IOError - -try: - PermissionError = PermissionError -except NameError: - PermissionError = IOError - -try: - NotADirectoryError = NotADirectoryError -except NameError: - class NotADirectoryError(Exception): - # Don't implement this for Python 2 anymore. - pass - - -def no_unicode_pprint(dct): - """ - Python 2/3 dict __repr__ may be different, because of unicode differens - (with or without a `u` prefix). Normally in doctests we could use `pprint` - to sort dicts and check for equality, but here we have to write a separate - function to do that. - """ - import pprint - s = pprint.pformat(dct) - print(re.sub("u'", "'", s)) - - -def utf8_repr(func): - """ - ``__repr__`` methods in Python 2 don't allow unicode objects to be - returned. Therefore cast them to utf-8 bytes in this decorator. - """ - def wrapper(self): - result = func(self) - if isinstance(result, unicode): - return result.encode('utf-8') - else: - return result - - if is_py3: - return func - else: - return wrapper - - -if is_py3: - import queue -else: - import Queue as queue # noqa: F401 - -try: - # Attempt to load the C implementation of pickle on Python 2 as it is way - # faster. - import cPickle as pickle -except ImportError: - import pickle + if isinstance(string, bytes): + return str(string, encoding='UTF-8', errors='replace') + return str(string) def pickle_load(file): try: - if is_py3: - return pickle.load(file, encoding='bytes') return pickle.load(file) # Python on Windows don't throw EOF errors for pipes. So reraise them with # the correct type, which is caught upwards. @@ -403,24 +34,8 @@ def pickle_load(file): raise -def _python2_dct_keys_to_unicode(data): - """ - Python 2 stores object __dict__ entries as bytes, not unicode, correct it - here. Python 2 can deal with both, Python 3 expects unicode. - """ - if isinstance(data, tuple): - return tuple(_python2_dct_keys_to_unicode(x) for x in data) - elif isinstance(data, list): - return list(_python2_dct_keys_to_unicode(x) for x in data) - elif hasattr(data, '__dict__') and type(data.__dict__) == dict: - data.__dict__ = {unicode(k): v for k, v in data.__dict__.items()} - return data - - def pickle_dump(data, file, protocol): try: - if not is_py3: - data = _python2_dct_keys_to_unicode(data) pickle.dump(data, file, protocol) # On Python 3.3 flush throws sometimes an error even though the writing # operation should be completed. @@ -431,201 +46,3 @@ def pickle_dump(data, file, protocol): if sys.platform == 'win32': raise IOError(errno.EPIPE, "Broken pipe") raise - - -# Determine the highest protocol version compatible for a given list of Python -# versions. -def highest_pickle_protocol(python_versions): - protocol = 4 - for version in python_versions: - if version[0] == 2: - # The minimum protocol version for the versions of Python that we - # support (2.7 and 3.3+) is 2. - return 2 - if version[1] < 4: - protocol = 3 - return protocol - - -try: - from inspect import Parameter -except ImportError: - class Parameter(object): - POSITIONAL_ONLY = object() - POSITIONAL_OR_KEYWORD = object() - VAR_POSITIONAL = object() - KEYWORD_ONLY = object() - VAR_KEYWORD = object() - - -class GeneralizedPopen(subprocess.Popen): - def __init__(self, *args, **kwargs): - if os.name == 'nt': - try: - # Was introduced in Python 3.7. - CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW - except AttributeError: - CREATE_NO_WINDOW = 0x08000000 - kwargs['creationflags'] = CREATE_NO_WINDOW - # The child process doesn't need file descriptors except 0, 1, 2. - # This is unix only. - kwargs['close_fds'] = 'posix' in sys.builtin_module_names - super(GeneralizedPopen, self).__init__(*args, **kwargs) - - -# shutil.which is not available on Python 2.7. -def which(cmd, mode=os.F_OK | os.X_OK, path=None): - """Given a command, mode, and a PATH string, return the path which - conforms to the given mode on the PATH, or None if there is no such - file. - - `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result - of os.environ.get("PATH"), or can be overridden with a custom search - path. - - """ - # Check that a given file can be accessed with the correct mode. - # Additionally check that `file` is not a directory, as on Windows - # directories pass the os.access check. - def _access_check(fn, mode): - return (os.path.exists(fn) and os.access(fn, mode) - and not os.path.isdir(fn)) - - # If we're given a path with a directory part, look it up directly rather - # than referring to PATH directories. This includes checking relative to the - # current directory, e.g. ./script - if os.path.dirname(cmd): - if _access_check(cmd, mode): - return cmd - return None - - if path is None: - path = os.environ.get("PATH", os.defpath) - if not path: - return None - path = path.split(os.pathsep) - - if sys.platform == "win32": - # The current directory takes precedence on Windows. - if os.curdir not in path: - path.insert(0, os.curdir) - - # PATHEXT is necessary to check on Windows. - pathext = os.environ.get("PATHEXT", "").split(os.pathsep) - # See if the given file matches any of the expected path extensions. - # This will allow us to short circuit when given "python.exe". - # If it does match, only test that one, otherwise we have to try - # others. - if any(cmd.lower().endswith(ext.lower()) for ext in pathext): - files = [cmd] - else: - files = [cmd + ext for ext in pathext] - else: - # On other platforms you don't have things like PATHEXT to tell you - # what file suffixes are executable, so just pass on cmd as-is. - files = [cmd] - - seen = set() - for dir in path: - normdir = os.path.normcase(dir) - if normdir not in seen: - seen.add(normdir) - for thefile in files: - name = os.path.join(dir, thefile) - if _access_check(name, mode): - return name - return None - - -if not is_py3: - # Simplified backport of Python 3 weakref.finalize: - # https://github.com/python/cpython/blob/ded4737989316653469763230036b04513cb62b3/Lib/weakref.py#L502-L662 - class finalize(object): - """Class for finalization of weakrefable objects. - - finalize(obj, func, *args, **kwargs) returns a callable finalizer - object which will be called when obj is garbage collected. The - first time the finalizer is called it evaluates func(*arg, **kwargs) - and returns the result. After this the finalizer is dead, and - calling it just returns None. - - When the program exits any remaining finalizers will be run. - """ - - # Finalizer objects don't have any state of their own. - # This ensures that they cannot be part of a ref-cycle. - __slots__ = () - _registry = {} - - def __init__(self, obj, func, *args, **kwargs): - info = functools.partial(func, *args, **kwargs) - info.weakref = weakref.ref(obj, self) - self._registry[self] = info - - # To me it's an absolute mystery why in Python 2 we need _=None. It - # makes really no sense since it's never really called. Then again it - # might be called by Python 2.7 itself, but weakref.finalize is not - # documented in Python 2 and therefore shouldn't be randomly called. - # We never call this stuff with a parameter and therefore this - # parameter should not be needed. But it is. ~dave - def __call__(self, _=None): - """Return func(*args, **kwargs) if alive.""" - info = self._registry.pop(self, None) - if info: - return info() - - @classmethod - def _exitfunc(cls): - if not cls._registry: - return - for finalizer in list(cls._registry): - try: - finalizer() - except Exception: - sys.excepthook(*sys.exc_info()) - assert finalizer not in cls._registry - - atexit.register(finalize._exitfunc) - weakref.finalize = finalize - - -if is_py3 and sys.version_info[1] > 5: - from inspect import unwrap -else: - # Only Python >=3.6 does properly limit the amount of unwraps. This is very - # relevant in the case of unittest.mock.patch. - # Below is the implementation of Python 3.7. - def unwrap(func, stop=None): - """Get the object wrapped by *func*. - - Follows the chain of :attr:`__wrapped__` attributes returning the last - object in the chain. - - *stop* is an optional callback accepting an object in the wrapper chain - as its sole argument that allows the unwrapping to be terminated early if - the callback returns a true value. If the callback never returns a true - value, the last object in the chain is returned as usual. For example, - :func:`signature` uses this to stop unwrapping if any object in the - chain has a ``__signature__`` attribute defined. - - :exc:`ValueError` is raised if a cycle is encountered. - - """ - if stop is None: - def _is_wrapper(f): - return hasattr(f, '__wrapped__') - else: - def _is_wrapper(f): - return hasattr(f, '__wrapped__') and not stop(f) - f = func # remember the original func for error reporting - # Memoise by id to tolerate non-hashable objects, but store objects to - # ensure they aren't destroyed, which would allow their IDs to be reused. - memo = {id(f): f} - recursion_limit = sys.getrecursionlimit() - while _is_wrapper(func): - func = func.__wrapped__ - id_func = id(func) - if (id_func in memo) or (len(memo) >= recursion_limit): - raise ValueError('wrapper loop when unwrapping {!r}'.format(f)) - memo[id_func] = func - return func diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index ab069c98..2be38b8f 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -7,15 +7,14 @@ Alternatively, if you don't need a custom function and are happy with printing debug messages to stdout, simply call :func:`set_debug_function` without arguments. """ -import os import sys import warnings -from functools import wraps +from pathlib import Path import parso from parso.python import tree -from jedi._compatibility import force_unicode, cast_path, is_py3 +from jedi._compatibility import cast_path from jedi.parser_utils import get_executable_nodes from jedi import debug from jedi import settings @@ -51,18 +50,6 @@ from jedi.inference.utils import to_list sys.setrecursionlimit(3000) -def _no_python2_support(func): - # TODO remove when removing Python 2/3.5 - @wraps(func) - def wrapper(self, *args, **kwargs): - if self._inference_state.grammar.version_info < (3, 6) or sys.version_info < (3, 6): - raise NotImplementedError( - "No support for refactorings/search on Python 2/3.5" - ) - return func(self, *args, **kwargs) - return wrapper - - class Script(object): """ A Script is the base for completions, goto or whatever you want to do with @@ -109,10 +96,7 @@ class Script(object): :type column: int :param path: The path of the file in the file system, or ``''`` if it hasn't been saved yet. - :type path: str or None - :param encoding: Deprecated, cast to unicode yourself. The encoding of - ``code``, if it is not a ``unicode`` object (default ``'utf-8'``). - :type encoding: str + :type path: str or pathlib.Path or None :param sys_path: Deprecated, use the project parameter. :type sys_path: typing.List[str] :param Environment environment: Provide a predefined :ref:`Environment ` @@ -122,21 +106,14 @@ class Script(object): also ways to modify the sys path and other things. """ def __init__(self, code=None, line=None, column=None, path=None, - encoding=None, sys_path=None, environment=None, - project=None, source=None): + sys_path=None, environment=None, project=None, source=None): self._orig_path = path # An empty path (also empty string) should always result in no path. - self.path = os.path.abspath(path) if path else None + if isinstance(path, str): + path = Path(path) + + self.path = path.absolute() if path else None - if encoding is None: - encoding = 'utf-8' - else: - warnings.warn( - "Deprecated since version 0.17.0. You should cast to valid " - "unicode yourself, especially if you are not using utf-8.", - DeprecationWarning, - stacklevel=2 - ) if line is not None: warnings.warn( "Providing the line is now done in the functions themselves " @@ -163,14 +140,9 @@ class Script(object): with open(path, 'rb') as f: code = f.read() - if sys_path is not None and not is_py3: - sys_path = list(map(force_unicode, sys_path)) - if project is None: # Load the Python grammar of the current interpreter. - project = get_default_project( - os.path.dirname(self.path) if path else None - ) + project = get_default_project(None if self.path is None else self.path.parent) # TODO deprecate and remove sys_path from the Script API. if sys_path is not None: project._sys_path = sys_path @@ -188,8 +160,7 @@ class Script(object): self._module_node, code = self._inference_state.parse_and_get_code( code=code, path=self.path, - encoding=encoding, - use_latest_grammar=path and path.endswith('.pyi'), + use_latest_grammar=path and path.suffix == 'pyi', cache=False, # No disk cache, because the current script often changes. diff_cache=settings.fast_parser, cache_path=settings.cache_directory, @@ -221,7 +192,7 @@ class Script(object): file_io = None else: file_io = KnownContentFileIO(cast_path(self.path), self._code) - if self.path is not None and self.path.endswith('.pyi'): + if self.path is not None and self.path.suffix == '.pyi': # We are in a stub file. Try to load the stub properly. stub_module = load_proper_stub_module( self._inference_state, @@ -242,7 +213,7 @@ class Script(object): code_lines=self._code_lines, is_package=is_package, ) - if names[0] not in ('builtins', '__builtin__', 'typing'): + if names[0] not in ('builtins', 'typing'): # These modules are essential for Jedi, so don't overwrite them. self._inference_state.module_cache.add(names, ValueSet([module])) return module @@ -258,7 +229,7 @@ class Script(object): ) @validate_line_column - def complete(self, line=None, column=None, **kwargs): + def complete(self, line=None, column=None, *, fuzzy=False): """ Completes objects under the cursor. @@ -272,9 +243,6 @@ class Script(object): before magic methods and name mangled names that start with ``__``. :rtype: list of :class:`.Completion` """ - return self._complete(line, column, **kwargs) - - def _complete(self, line, column, fuzzy=False): # Python 2... with debug.increase_indent_cm('complete'): completion = Completion( self._inference_state, self._get_module_context(), self._code_lines, @@ -291,7 +259,7 @@ class Script(object): return self.complete(*self._pos, fuzzy=fuzzy) @validate_line_column - def infer(self, line=None, column=None, **kwargs): + def infer(self, line=None, column=None, *, only_stubs=False, prefer_stubs=False): """ Return the definitions of under the cursor. It is basically a wrapper around Jedi's type inference. @@ -307,18 +275,6 @@ class Script(object): :param prefer_stubs: Prefer stubs to Python objects for this method. :rtype: list of :class:`.Name` """ - with debug.increase_indent_cm('infer'): - return self._infer(line, column, **kwargs) - - def goto_definitions(self, **kwargs): - warnings.warn( - "Deprecated since version 0.16.0. Use Script(...).infer instead.", - DeprecationWarning, - stacklevel=2 - ) - return self.infer(*self._pos, **kwargs) - - def _infer(self, line, column, only_stubs=False, prefer_stubs=False): pos = line, column leaf = self._module_node.get_name_of_position(pos) if leaf is None: @@ -341,6 +297,14 @@ class Script(object): # the API. return helpers.sorted_definitions(set(defs)) + def goto_definitions(self, **kwargs): + warnings.warn( + "Deprecated since version 0.16.0. Use Script(...).infer instead.", + DeprecationWarning, + stacklevel=2 + ) + return self.infer(*self._pos, **kwargs) + def goto_assignments(self, follow_imports=False, follow_builtin_imports=False, **kwargs): warnings.warn( "Deprecated since version 0.16.0. Use Script(...).goto instead.", @@ -353,7 +317,8 @@ class Script(object): **kwargs) @validate_line_column - def goto(self, line=None, column=None, **kwargs): + def goto(self, line=None, column=None, *, follow_imports=False, follow_builtin_imports=False, + only_stubs=False, prefer_stubs=False): """ Goes to the name that defined the object under the cursor. Optionally you can follow imports. @@ -367,11 +332,6 @@ class Script(object): :param prefer_stubs: Prefer stubs to Python objects for this method. :rtype: list of :class:`.Name` """ - with debug.increase_indent_cm('goto'): - return self._goto(line, column, **kwargs) - - def _goto(self, line, column, follow_imports=False, follow_builtin_imports=False, - only_stubs=False, prefer_stubs=False): tree_name = self._module_node.get_name_of_position((line, column)) if tree_name is None: # Without a name we really just want to jump to the result e.g. @@ -407,8 +367,7 @@ class Script(object): # Avoid duplicates return list(set(helpers.sorted_definitions(defs))) - @_no_python2_support - def search(self, string, **kwargs): + def search(self, string, *, all_scopes=False): """ Searches a name in the current file. For a description of how the search string should look like, please have a look at @@ -419,9 +378,6 @@ class Script(object): functions and classes. :yields: :class:`.Name` """ - return self._search(string, **kwargs) # Python 2 ... - - def _search(self, string, all_scopes=False): return self._search_func(string, all_scopes=all_scopes) @to_list @@ -685,8 +641,7 @@ class Script(object): ] return sorted(defs, key=lambda x: x.start_pos) - @_no_python2_support - def rename(self, line=None, column=None, **kwargs): + def rename(self, line=None, column=None, *, new_name): """ Renames all references of the variable under the cursor. @@ -695,14 +650,11 @@ class Script(object): :raises: :exc:`.RefactoringError` :rtype: :class:`.Refactoring` """ - return self._rename(line, column, **kwargs) - - def _rename(self, line, column, new_name): # Python 2... definitions = self.get_references(line, column, include_builtins=False) return refactoring.rename(self._inference_state, definitions, new_name) - @_no_python2_support - def extract_variable(self, line, column, **kwargs): + @validate_line_column + def extract_variable(self, line, column, *, new_name, until_line=None, until_column=None): """ Moves an expression to a new statemenet. @@ -727,10 +679,6 @@ class Script(object): :raises: :exc:`.RefactoringError` :rtype: :class:`.Refactoring` """ - return self._extract_variable(line, column, **kwargs) # Python 2... - - @validate_line_column - def _extract_variable(self, line, column, new_name, until_line=None, until_column=None): if until_line is None and until_column is None: until_pos = None else: @@ -744,8 +692,8 @@ class Script(object): new_name, (line, column), until_pos ) - @_no_python2_support - def extract_function(self, line, column, **kwargs): + @validate_line_column + def extract_function(self, line, column, *, new_name, until_line=None, until_column=None): """ Moves an expression to a new function. @@ -778,10 +726,6 @@ class Script(object): :raises: :exc:`.RefactoringError` :rtype: :class:`.Refactoring` """ - return self._extract_function(line, column, **kwargs) # Python 2... - - @validate_line_column - def _extract_function(self, line, column, new_name, until_line=None, until_column=None): if until_line is None and until_column is None: until_pos = None else: @@ -795,7 +739,6 @@ class Script(object): new_name, (line, column), until_pos ) - @_no_python2_support def inline(self, line=None, column=None): """ Inlines a variable under the cursor. This is basically the opposite of @@ -855,8 +798,8 @@ class Interpreter(Script): if not isinstance(environment, InterpreterEnvironment): raise TypeError("The environment needs to be an InterpreterEnvironment subclass.") - super(Interpreter, self).__init__(code, environment=environment, - project=Project(os.getcwd()), **kwds) + super().__init__(code, environment=environment, + project=Project(Path.cwd()), **kwds) self.namespaces = namespaces self._inference_state.allow_descriptor_getattr = self._allow_descriptor_getattr_default @@ -864,7 +807,7 @@ class Interpreter(Script): def _get_module_context(self): tree_module_value = ModuleValue( self._inference_state, self._module_node, - file_io=KnownContentFileIO(self.path, self._code), + file_io=KnownContentFileIO(str(self.path), self._code), string_names=('__main__',), code_lines=self._code_lines, ) @@ -874,7 +817,7 @@ class Interpreter(Script): ) -def names(source=None, path=None, encoding='utf-8', all_scopes=False, +def names(source=None, path=None, all_scopes=False, definitions=True, references=False, environment=None): warnings.warn( "Deprecated since version 0.16.0. Use Script(...).get_names instead.", @@ -882,7 +825,7 @@ def names(source=None, path=None, encoding='utf-8', all_scopes=False, stacklevel=2 ) - return Script(source, path=path, encoding=encoding).get_names( + return Script(source, path=path).get_names( all_scopes=all_scopes, definitions=definitions, references=references, @@ -899,7 +842,7 @@ def preload_module(*modules): """ for m in modules: s = "import %s as x; x." % m - Script(s, path=None).complete(1, len(s)) + Script(s).complete(1, len(s)) def set_debug_function(func_cb=debug.print_to_stdout, warnings=True, diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 4e0d2635..c55d7b68 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -14,8 +14,8 @@ These classes are the much biggest part of the API, because they contain the interesting information about all operations. """ import re -import sys import warnings +from typing import Optional from parso.python.tree import search_ancestor @@ -71,7 +71,6 @@ class BaseName(object): '_collections': 'collections', '_socket': 'socket', '_sqlite3': 'sqlite3', - '__builtin__': 'builtins', } _tuple_mapping = dict((tuple(k.split('.')), v) for (k, v) in { @@ -94,9 +93,9 @@ class BaseName(object): return self._name.get_root_context() @property - def module_path(self): + def module_path(self) -> Optional[str]: """ - Shows the file path of a module. e.g. ``/usr/lib/python2.7/os.py`` + Shows the file path of a module. e.g. ``/usr/lib/python3.9/os.py`` :rtype: str or None """ @@ -104,7 +103,9 @@ class BaseName(object): if module.is_stub() or not module.is_compiled(): # Compiled modules should not return a module path even if they # have one. - return self._get_module_context().py__file__() + path = self._get_module_context().py__file__() + if path is not None: + return path return None @@ -129,7 +130,6 @@ class BaseName(object): to Jedi, :meth:`jedi.Script.infer` should return a list of definition for ``sys``, ``f``, ``C`` and ``x``. - >>> from jedi._compatibility import no_unicode_pprint >>> from jedi import Script >>> source = ''' ... import keyword @@ -155,7 +155,7 @@ class BaseName(object): so that it is easy to relate the result to the source code. >>> defs = sorted(defs, key=lambda d: d.line) - >>> no_unicode_pprint(defs) # doctest: +NORMALIZE_WHITESPACE + >>> print(defs) # doctest: +NORMALIZE_WHITESPACE [, , , @@ -163,7 +163,7 @@ class BaseName(object): Finally, here is what you can get from :attr:`type`: - >>> defs = [str(d.type) for d in defs] # It's unicode and in Py2 has u before it. + >>> defs = [d.type for d in defs] >>> defs[0] 'module' >>> defs[1] @@ -324,7 +324,6 @@ class BaseName(object): Example: - >>> from jedi._compatibility import no_unicode_pprint >>> from jedi import Script >>> source = ''' ... def f(): @@ -337,10 +336,10 @@ class BaseName(object): >>> script = Script(source) # line is maximum by default >>> defs = script.infer(column=3) >>> defs = sorted(defs, key=lambda d: d.line) - >>> no_unicode_pprint(defs) # doctest: +NORMALIZE_WHITESPACE + >>> print(defs) # doctest: +NORMALIZE_WHITESPACE [, ] - >>> str(defs[0].description) # strip literals in python2 + >>> str(defs[0].description) 'def f' >>> str(defs[1].description) 'class C' @@ -424,7 +423,10 @@ class BaseName(object): return False return tree_name.is_definition() and tree_name.parent.type == 'trailer' - def goto(self, **kwargs): + @debug.increase_indent_cm('goto on name') + def goto(self, *, follow_imports=False, follow_builtin_imports=False, + only_stubs=False, prefer_stubs=False): + """ Like :meth:`.Script.goto` (also supports the same params), but does it for the current name. This is typically useful if you are using @@ -437,20 +439,6 @@ class BaseName(object): :param prefer_stubs: Prefer stubs to Python objects for this goto call. :rtype: list of :class:`Name` """ - with debug.increase_indent_cm('goto for %s' % self._name): - return self._goto(**kwargs) - - def goto_assignments(self, **kwargs): # Python 2... - warnings.warn( - "Deprecated since version 0.16.0. Use .goto.", - DeprecationWarning, - stacklevel=2 - ) - return self.goto(**kwargs) - - def _goto(self, follow_imports=False, follow_builtin_imports=False, - only_stubs=False, prefer_stubs=False): - if not self._name.is_value_name: return [] @@ -465,7 +453,16 @@ class BaseName(object): return [self if n == self._name else Name(self._inference_state, n) for n in names] - def infer(self, **kwargs): # Python 2... + def goto_assignments(self, **kwargs): + warnings.warn( + "Deprecated since version 0.16.0. Use .goto.", + DeprecationWarning, + stacklevel=2 + ) + return self.goto(**kwargs) + + @debug.increase_indent_cm('infer on name') + def infer(self, *, only_stubs=False, prefer_stubs=False): """ Like :meth:`.Script.infer`, it can be useful to understand which type the current name has. @@ -482,10 +479,6 @@ class BaseName(object): inference call. :rtype: list of :class:`Name` """ - with debug.increase_indent_cm('infer for %s' % self._name): - return self._infer(**kwargs) - - def _infer(self, only_stubs=False, prefer_stubs=False): assert not (only_stubs and prefer_stubs) if not self._name.is_value_name: @@ -645,7 +638,7 @@ class Completion(BaseName): """ def __init__(self, inference_state, name, stack, like_name_length, is_fuzzy, cached_name=None): - super(Completion, self).__init__(inference_state, name) + super().__init__(inference_state, name) self._like_name_length = like_name_length self._stack = stack @@ -716,7 +709,7 @@ class Completion(BaseName): # wouldn't load like > 100 Python modules anymore. fast = False - return super(Completion, self).docstring(raw=raw, fast=fast) + return super().docstring(raw=raw, fast=fast) def _get_docstring(self): if self._cached_name is not None: @@ -725,7 +718,7 @@ class Completion(BaseName): self._name.get_public_name(), lambda: self._get_cache() ) - return super(Completion, self)._get_docstring() + return super()._get_docstring() def _get_docstring_signature(self): if self._cached_name is not None: @@ -734,13 +727,13 @@ class Completion(BaseName): self._name.get_public_name(), lambda: self._get_cache() ) - return super(Completion, self)._get_docstring_signature() + return super()._get_docstring_signature() def _get_cache(self): return ( - super(Completion, self).type, - super(Completion, self)._get_docstring_signature(), - super(Completion, self)._get_docstring(), + super().type, + super()._get_docstring_signature(), + super()._get_docstring(), ) @property @@ -756,7 +749,7 @@ class Completion(BaseName): lambda: self._get_cache() ) - return super(Completion, self).type + return super().type def __repr__(self): return '<%s: %s>' % (type(self).__name__, self._name.get_public_name()) @@ -768,7 +761,7 @@ class Name(BaseName): :meth:`.Script.goto` or :meth:`.Script.infer`. """ def __init__(self, inference_state, definition): - super(Name, self).__init__(inference_state, definition) + super().__init__(inference_state, definition) @property def desc_with_module(self): @@ -821,7 +814,7 @@ class BaseSignature(Name): calls. """ def __init__(self, inference_state, signature): - super(BaseSignature, self).__init__(inference_state, signature.name) + super().__init__(inference_state, signature.name) self._signature = signature @property @@ -851,7 +844,7 @@ class Signature(BaseSignature): :meth:`.Script.get_signatures`. """ def __init__(self, inference_state, signature, call_details): - super(Signature, self).__init__(inference_state, signature) + super().__init__(inference_state, signature) self._call_details = call_details self._signature = signature @@ -918,8 +911,4 @@ class ParamName(Name): :rtype: :py:attr:`inspect.Parameter.kind` """ - if sys.version_info < (3, 5): - raise NotImplementedError( - 'Python 2 is end-of-life, the new feature is not available for it' - ) return self._name.get_kind() diff --git a/jedi/api/completion.py b/jedi/api/completion.py index 90b03541..f8647248 100644 --- a/jedi/api/completion.py +++ b/jedi/api/completion.py @@ -1,12 +1,12 @@ import re from textwrap import dedent +from inspect import Parameter from parso.python.token import PythonTokenTypes from parso.python import tree from parso.tree import search_ancestor, Leaf from parso import split_lines -from jedi._compatibility import Parameter from jedi import debug from jedi import settings from jedi.api import classes @@ -34,9 +34,7 @@ def _get_signature_param_names(signatures, positional_count, used_kwargs): # Add named params for call_sig in signatures: for i, p in enumerate(call_sig.params): - # Allow protected access, because it's a public API. - # TODO reconsider with Python 2 drop - kind = p._name.get_kind() + kind = p.kind if i < positional_count and kind == Parameter.POSITIONAL_OR_KEYWORD: continue if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) \ @@ -51,8 +49,7 @@ def _must_be_kwarg(signatures, positional_count, used_kwargs): must_be_kwarg = True for signature in signatures: for i, p in enumerate(signature.params): - # TODO reconsider with Python 2 drop - kind = p._name.get_kind() + kind = p.kind if kind is Parameter.VAR_POSITIONAL: # In case there were not already kwargs, the next param can # always be a normal argument. @@ -579,8 +576,8 @@ def _complete_getattr(user_context, instance): will write it like this anyway and the other ones, well they are just out of luck I guess :) ~dave. """ - names = (instance.get_function_slot_names(u'__getattr__') - or instance.get_function_slot_names(u'__getattribute__')) + names = (instance.get_function_slot_names('__getattr__') + or instance.get_function_slot_names('__getattribute__')) functions = ValueSet.from_sets( name.infer() for name in names diff --git a/jedi/api/environment.py b/jedi/api/environment.py index 89e47163..1747db7b 100644 --- a/jedi/api/environment.py +++ b/jedi/api/environment.py @@ -7,8 +7,8 @@ import sys import hashlib import filecmp from collections import namedtuple +from shutil import which -from jedi._compatibility import highest_pickle_protocol, which from jedi.cache import memoize_method, time_cache from jedi.inference.compiled.subprocess import CompiledSubprocess, \ InferenceStateSameProcess, InferenceStateSubprocess @@ -17,7 +17,7 @@ import parso _VersionInfo = namedtuple('VersionInfo', 'major minor micro') -_SUPPORTED_PYTHONS = ['3.8', '3.7', '3.6', '3.5', '2.7'] +_SUPPORTED_PYTHONS = ['3.9', '3.8', '3.7', '3.6'] _SAFE_PATHS = ['/usr/bin', '/usr/local/bin'] _CONDA_VAR = 'CONDA_PREFIX' _CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor) @@ -96,16 +96,6 @@ class Environment(_BaseEnvironment): Like :data:`sys.version_info`: a tuple to show the current Environment's Python version. """ - - # py2 sends bytes via pickle apparently?! - if self.version_info.major == 2: - self.executable = self.executable.decode() - self.path = self.path.decode() - - # Adjust pickle protocol according to host and client version. - self._subprocess._pickle_protocol = highest_pickle_protocol([ - sys.version_info, self.version_info]) - return self._subprocess def __repr__(self): @@ -267,7 +257,7 @@ def _get_cached_default_environment(): return InterpreterEnvironment() -def find_virtualenvs(paths=None, **kwargs): +def find_virtualenvs(paths=None, *, safe=True, use_environment_vars=True): """ :param paths: A list of paths in your file system to be scanned for Virtualenvs. It will search in these paths and potentially execute the @@ -284,47 +274,44 @@ def find_virtualenvs(paths=None, **kwargs): :yields: :class:`.Environment` """ - def py27_comp(paths=None, safe=True, use_environment_vars=True): - if paths is None: - paths = [] + if paths is None: + paths = [] - _used_paths = set() + _used_paths = set() - if use_environment_vars: - # Using this variable should be safe, because attackers might be - # able to drop files (via git) but not environment variables. - virtual_env = _get_virtual_env_from_var() - if virtual_env is not None: - yield virtual_env - _used_paths.add(virtual_env.path) + if use_environment_vars: + # Using this variable should be safe, because attackers might be + # able to drop files (via git) but not environment variables. + virtual_env = _get_virtual_env_from_var() + if virtual_env is not None: + yield virtual_env + _used_paths.add(virtual_env.path) - conda_env = _get_virtual_env_from_var(_CONDA_VAR) - if conda_env is not None: - yield conda_env - _used_paths.add(conda_env.path) + conda_env = _get_virtual_env_from_var(_CONDA_VAR) + if conda_env is not None: + yield conda_env + _used_paths.add(conda_env.path) - for directory in paths: - if not os.path.isdir(directory): + for directory in paths: + if not os.path.isdir(directory): + continue + + directory = os.path.abspath(directory) + for path in os.listdir(directory): + path = os.path.join(directory, path) + if path in _used_paths: + # A path shouldn't be inferred twice. continue + _used_paths.add(path) - directory = os.path.abspath(directory) - for path in os.listdir(directory): - path = os.path.join(directory, path) - if path in _used_paths: - # A path shouldn't be inferred twice. - continue - _used_paths.add(path) - - try: - executable = _get_executable_path(path, safe=safe) - yield Environment(executable) - except InvalidPythonEnvironment: - pass - - return py27_comp(paths, **kwargs) + try: + executable = _get_executable_path(path, safe=safe) + yield Environment(executable) + except InvalidPythonEnvironment: + pass -def find_system_environments(**kwargs): +def find_system_environments(*, env_vars={}): """ Ignores virtualenvs and returns the Python versions that were installed on your system. This might return nothing, if you're running Python e.g. from @@ -336,14 +323,14 @@ def find_system_environments(**kwargs): """ for version_string in _SUPPORTED_PYTHONS: try: - yield get_system_environment(version_string, **kwargs) + yield get_system_environment(version_string, env_vars=env_vars) except InvalidPythonEnvironment: pass # TODO: this function should probably return a list of environments since # multiple Python installations can be found on a system for the same version. -def get_system_environment(version, **kwargs): +def get_system_environment(version, *, env_vars={}): """ Return the first Python environment found for a string of the form 'X.Y' where X and Y are the major and minor versions of Python. @@ -360,26 +347,20 @@ def get_system_environment(version, **kwargs): if os.name == 'nt': for exe in _get_executables_from_windows_registry(version): try: - return Environment(exe, **kwargs) + return Environment(exe, env_vars=env_vars) except InvalidPythonEnvironment: pass raise InvalidPythonEnvironment("Cannot find executable python%s." % version) -def create_environment(path, safe=True, **kwargs): +def create_environment(path, *, safe=True, env_vars=None): """ Make it possible to manually create an Environment object by specifying a Virtualenv path or an executable path and optional environment variables. :raises: :exc:`.InvalidPythonEnvironment` :returns: :class:`.Environment` - - TODO: make env_vars a kwarg when Python 2 is dropped. For now, preserve API """ - return _create_environment(path, safe, **kwargs) - - -def _create_environment(path, safe=True, env_vars=None): if os.path.isfile(path): _assert_safe(path, safe) return Environment(path, env_vars=env_vars) @@ -403,11 +384,7 @@ def _get_executable_path(path, safe=True): def _get_executables_from_windows_registry(version): - # The winreg module is named _winreg on Python 2. - try: - import winreg - except ImportError: - import _winreg as winreg + import winreg # TODO: support Python Anaconda. sub_keys = [ diff --git a/jedi/api/file_name.py b/jedi/api/file_name.py index 4d472453..277f3220 100644 --- a/jedi/api/file_name.py +++ b/jedi/api/file_name.py @@ -1,6 +1,5 @@ import os -from jedi._compatibility import FileNotFoundError, force_unicode, scandir from jedi.api import classes from jedi.api.strings import StringName, get_quote_ending from jedi.api.helpers import match @@ -8,7 +7,7 @@ from jedi.inference.helpers import get_str_or_none class PathName(StringName): - api_type = u'path' + api_type = 'path' def complete_file_name(inference_state, module_context, start_leaf, quote, string, @@ -38,7 +37,7 @@ def complete_file_name(inference_state, module_context, start_leaf, quote, strin string = to_be_added + string base_path = os.path.join(inference_state.project.path, string) try: - listed = sorted(scandir(base_path), key=lambda e: e.name) + listed = sorted(os.scandir(base_path), key=lambda e: e.name) # OSError: [Errno 36] File name too long: '...' except (FileNotFoundError, OSError): return @@ -94,7 +93,7 @@ def _add_strings(context, nodes, add_slash=False): return None if not first and add_slash: string += os.path.sep - string += force_unicode(s) + string += s first = False return string diff --git a/jedi/api/helpers.py b/jedi/api/helpers.py index 0463b893..bcd59922 100644 --- a/jedi/api/helpers.py +++ b/jedi/api/helpers.py @@ -6,11 +6,11 @@ from collections import namedtuple from textwrap import dedent from itertools import chain from functools import wraps +from inspect import Parameter from parso.python.parser import Parser from parso.python import tree -from jedi._compatibility import u, Parameter from jedi.inference.base_value import NO_VALUES from jedi.inference.syntax_tree import infer_atom from jedi.inference.helpers import infer_call_of_leaf @@ -44,7 +44,10 @@ def match(string, like_name, fuzzy=False): def sorted_definitions(defs): # Note: `or ''` below is required because `module_path` could be - return sorted(defs, key=lambda x: (x.module_path or '', x.line or 0, x.column or 0, x.name)) + return sorted(defs, key=lambda x: (str(x.module_path or ''), + x.line or 0, + x.column or 0, + x.name)) def get_on_completion_name(module_node, lines, position): @@ -84,18 +87,18 @@ def _get_code_for_stack(code_lines, leaf, position): # If we're not on a comment simply get the previous leaf and proceed. leaf = leaf.get_previous_leaf() if leaf is None: - return u('') # At the beginning of the file. + return '' # At the beginning of the file. is_after_newline = leaf.type == 'newline' while leaf.type == 'newline': leaf = leaf.get_previous_leaf() if leaf is None: - return u('') + return '' if leaf.type == 'error_leaf' or leaf.type == 'string': if leaf.start_pos[0] < position[0]: # On a different line, we just begin anew. - return u('') + return '' # Error leafs cannot be parsed, completion in strings is also # impossible. @@ -111,7 +114,7 @@ def _get_code_for_stack(code_lines, leaf, position): if user_stmt.start_pos[1] > position[1]: # This means that it's actually a dedent and that means that we # start without value (part of a suite). - return u('') + return '' # This is basically getting the relevant lines. return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position) @@ -294,8 +297,7 @@ def _iter_arguments(nodes, position): # Returns Generator[Tuple[star_count, Optional[key_start: str], had_equal]] nodes_before = [c for c in nodes if c.start_pos < position] if nodes_before[-1].type == 'arglist': - for x in _iter_arguments(nodes_before[-1].children, position): - yield x # Python 2 :( + yield from _iter_arguments(nodes_before[-1].children, position) return previous_node_yielded = False @@ -320,7 +322,7 @@ def _iter_arguments(nodes, position): else: yield 0, None, False stars_seen = 0 - elif node.type in ('testlist', 'testlist_star_expr'): # testlist is Python 2 + elif node.type == 'testlist_star_expr': for n in node.children[::2]: if n.type == 'star_expr': stars_seen = 1 diff --git a/jedi/api/interpreter.py b/jedi/api/interpreter.py index 541840a1..8b681737 100644 --- a/jedi/api/interpreter.py +++ b/jedi/api/interpreter.py @@ -47,7 +47,7 @@ class MixedParserTreeFilter(ParserTreeFilter): class MixedModuleContext(ModuleContext): def __init__(self, tree_module_value, namespaces): - super(MixedModuleContext, self).__init__(tree_module_value) + super().__init__(tree_module_value) self.mixed_values = [ self._get_mixed_object( _create(self.inference_state, NamespaceObject(n)) diff --git a/jedi/api/keywords.py b/jedi/api/keywords.py index dd57bb85..c016dadf 100644 --- a/jedi/api/keywords.py +++ b/jedi/api/keywords.py @@ -1,22 +1,13 @@ import pydoc +from contextlib import suppress -from jedi.inference.utils import ignored from jedi.inference.names import AbstractArbitraryName -try: - from pydoc_data import topics as pydoc_topics -except ImportError: - # Python 2 - try: - import pydoc_topics - except ImportError: - # This is for Python 3 embeddable version, which dont have - # pydoc_data module in its file python3x.zip. - pydoc_topics = None +from pydoc_data import topics as pydoc_topics class KeywordName(AbstractArbitraryName): - api_type = u'keyword' + api_type = 'keyword' def py__doc__(self): return imitate_pydoc(self.string_name) @@ -30,11 +21,8 @@ def imitate_pydoc(string): if pydoc_topics is None: return '' - # str needed because of possible unicode stuff in py2k (pydoc doesn't work - # with unicode strings) - string = str(string) h = pydoc.help - with ignored(KeyError): + with suppress(KeyError): # try to access symbols string = h.symbols[string] string, _, related = string.partition(' ') diff --git a/jedi/api/project.py b/jedi/api/project.py index d506aae7..ae00c38b 100644 --- a/jedi/api/project.py +++ b/jedi/api/project.py @@ -7,26 +7,21 @@ flexibility to define sys paths and Python interpreters for a project, Projects can be saved to disk and loaded again, to allow project definitions to be used across repositories. """ -import os -import errno import json -import sys +from pathlib import Path +from itertools import chain -from jedi._compatibility import FileNotFoundError, PermissionError, \ - IsADirectoryError, NotADirectoryError from jedi import debug from jedi.api.environment import get_cached_default_environment, create_environment from jedi.api.exceptions import WrongVersion from jedi.api.completion import search_in_module from jedi.api.helpers import split_search_string, get_module_names -from jedi._compatibility import force_unicode from jedi.inference.imports import load_module_from_path, \ load_namespace_from_path, iter_module_names from jedi.inference.sys_path import discover_buildout_paths from jedi.inference.cache import inference_state_as_method_param_cache from jedi.inference.references import recurse_find_python_folders_and_files, search_in_file_ios from jedi.file_io import FolderIO -from jedi.common import traverse_parents _CONFIG_FOLDER = '.jedi' _CONTAINS_POTENTIAL_PROJECT = \ @@ -61,10 +56,6 @@ def _remove_duplicates_from_path(path): yield p -def _force_unicode_list(lst): - return list(map(force_unicode, lst)) - - class Project(object): """ Projects are a simple way to manage Python folders and define how Jedi does @@ -75,11 +66,11 @@ class Project(object): @staticmethod def _get_config_folder_path(base_path): - return os.path.join(base_path, _CONFIG_FOLDER) + return base_path.joinpath(_CONFIG_FOLDER) @staticmethod def _get_json_path(base_path): - return os.path.join(Project._get_config_folder_path(base_path), 'project.json') + return Project._get_config_folder_path(base_path).joinpath('project.json') @classmethod def load(cls, path): @@ -89,6 +80,8 @@ class Project(object): :param path: The path of the directory you want to use as a project. """ + if isinstance(path, str): + path = Path(path) with open(cls._get_json_path(path)) as f: version, data = json.load(f) @@ -107,13 +100,9 @@ class Project(object): data.pop('_environment', None) data.pop('_django', None) # TODO make django setting public? data = {k.lstrip('_'): v for k, v in data.items()} + data['path'] = str(data['path']) - # TODO when dropping Python 2 use pathlib.Path.mkdir(parents=True, exist_ok=True) - try: - os.makedirs(self._get_config_folder_path(self._path)) - except OSError as e: - if e.errno != errno.EEXIST: - raise + self._get_config_folder_path(self._path).mkdir(parents=True, exist_ok=True) with open(self._get_json_path(self._path), 'w') as f: return json.dump((_SERIALIZER_VERSION, data), f) @@ -138,14 +127,20 @@ class Project(object): """ def py2_comp(path, environment_path=None, load_unsafe_extensions=False, sys_path=None, added_sys_path=(), smart_sys_path=True): - self._path = os.path.abspath(path) + if isinstance(path, str): + path = Path(path).absolute() + self._path = path self._environment_path = environment_path + if sys_path is not None: + # Remap potential pathlib.Path entries + sys_path = list(map(str, sys_path)) self._sys_path = sys_path self._smart_sys_path = smart_sys_path self._load_unsafe_extensions = load_unsafe_extensions self._django = False - self.added_sys_path = list(added_sys_path) + # Remap potential pathlib.Path entries + self.added_sys_path = list(map(str, added_sys_path)) """The sys path that is going to be added at the end of the """ py2_comp(path, **kwargs) @@ -182,23 +177,27 @@ class Project(object): sys_path = list(self._sys_path) if self._smart_sys_path: - prefixed.append(self._path) + prefixed.append(str(self._path)) if inference_state.script_path is not None: - suffixed += discover_buildout_paths(inference_state, inference_state.script_path) + suffixed += map(str, discover_buildout_paths( + inference_state, + inference_state.script_path + )) if add_parent_paths: # Collect directories in upward search by: # 1. Skipping directories with __init__.py # 2. Stopping immediately when above self._path traversed = [] - for parent_path in traverse_parents(inference_state.script_path): - if parent_path == self._path or not parent_path.startswith(self._path): + for parent_path in inference_state.script_path.parents: + if parent_path == self._path \ + or self._path not in parent_path.parents: break if not add_init_paths \ - and os.path.isfile(os.path.join(parent_path, "__init__.py")): + and parent_path.joinpath("__init__.py").is_file(): continue - traversed.append(parent_path) + traversed.append(str(parent_path)) # AFAIK some libraries have imports like `foo.foo.bar`, which # leads to the conclusion to by default prefer longer paths @@ -206,10 +205,10 @@ class Project(object): suffixed += reversed(traversed) if self._django: - prefixed.append(self._path) + prefixed.append(str(self._path)) path = prefixed + sys_path + suffixed - return list(_force_unicode_list(_remove_duplicates_from_path(path))) + return list(_remove_duplicates_from_path(path)) def get_environment(self): if self._environment is None: @@ -219,7 +218,7 @@ class Project(object): self._environment = get_cached_default_environment() return self._environment - def search(self, string, **kwargs): + def search(self, string, *, all_scopes=False): """ Searches a name in the whole project. If the project is very big, at some point Jedi will stop searching. However it's also very much @@ -240,7 +239,7 @@ class Project(object): functions and classes. :yields: :class:`.Name` """ - return self._search(string, **kwargs) + return self._search_func(string, all_scopes=all_scopes) def complete_search(self, string, **kwargs): """ @@ -254,9 +253,6 @@ class Project(object): """ return self._search_func(string, complete=True, **kwargs) - def _search(self, string, all_scopes=False): # Python 2.. - return self._search_func(string, all_scopes=all_scopes) - @_try_to_skip_duplicates def _search_func(self, string, complete=False, all_scopes=False): # Using a Script is they easiest way to get an empty module context. @@ -265,16 +261,12 @@ class Project(object): inference_state = s._inference_state empty_module_context = s._get_module_context() - if inference_state.grammar.version_info < (3, 6) or sys.version_info < (3, 6): - raise NotImplementedError( - "No support for refactorings/search on Python 2/3.5" - ) debug.dbg('Search for string %s, complete=%s', string, complete) wanted_type, wanted_names = split_search_string(string) name = wanted_names[0] stub_folder_name = name + '-stubs' - ios = recurse_find_python_folders_and_files(FolderIO(self._path)) + ios = recurse_find_python_folders_and_files(FolderIO(str(self._path))) file_ios = [] # 1. Search for modules in the current project @@ -295,14 +287,13 @@ class Project(object): continue else: file_ios.append(file_io) - file_name = os.path.basename(file_io.path) - if file_name in (name + '.py', name + '.pyi'): + if Path(file_io.path).name in (name + '.py', name + '.pyi'): m = load_module_from_path(inference_state, file_io).as_context() else: continue debug.dbg('Search of a specific module %s', m) - for x in search_in_module( + yield from search_in_module( inference_state, m, names=[m.name], @@ -311,15 +302,14 @@ class Project(object): complete=complete, convert=True, ignore_imports=True, - ): - yield x # Python 2... + ) # 2. Search for identifiers in the project. for module_context in search_in_file_ios(inference_state, file_ios, name): names = get_module_names(module_context.tree_node, all_scopes=all_scopes) names = [module_context.create_name(n) for n in names] names = _remove_imports(names) - for x in search_in_module( + yield from search_in_module( inference_state, module_context, names=names, @@ -327,18 +317,17 @@ class Project(object): wanted_names=wanted_names, complete=complete, ignore_imports=True, - ): - yield x # Python 2... + ) # 3. Search for modules on sys.path sys_path = [ p for p in self._get_sys_path(inference_state) # Exclude folders that are handled by recursing of the Python # folders. - if not p.startswith(self._path) + if not p.startswith(str(self._path)) ] names = list(iter_module_names(inference_state, empty_module_context, sys_path)) - for x in search_in_module( + yield from search_in_module( inference_state, empty_module_context, names=names, @@ -346,8 +335,7 @@ class Project(object): wanted_names=wanted_names, complete=complete, convert=True, - ): - yield x # Python 2... + ) def __repr__(self): return '<%s: %s>' % (self.__class__.__name__, self._path) @@ -355,7 +343,7 @@ class Project(object): def _is_potential_project(path): for name in _CONTAINS_POTENTIAL_PROJECT: - if os.path.exists(os.path.join(path, name)): + if path.joinpath(name).exists(): return True return False @@ -363,7 +351,7 @@ def _is_potential_project(path): def _is_django_path(directory): """ Detects the path of the very well known Django library (if used) """ try: - with open(os.path.join(directory, 'manage.py'), 'rb') as f: + with open(directory.joinpath('manage.py'), 'rb') as f: return b"DJANGO_SETTINGS_MODULE" in f.read() except (FileNotFoundError, IsADirectoryError, PermissionError): return False @@ -380,12 +368,14 @@ def get_default_project(path=None): ``requirements.txt`` and ``MANIFEST.in``. """ if path is None: - path = os.getcwd() + path = Path.cwd() + elif isinstance(path, str): + path = Path(path) - check = os.path.realpath(path) + check = path.absolute() probable_path = None first_no_init_file = None - for dir in traverse_parents(check, include_current=True): + for dir in chain([check], check.parents): try: return Project.load(dir) except (FileNotFoundError, IsADirectoryError, PermissionError): @@ -394,11 +384,11 @@ def get_default_project(path=None): continue if first_no_init_file is None: - if os.path.exists(os.path.join(dir, '__init__.py')): + if dir.joinpath('__init__.py').exists(): # In the case that a __init__.py exists, it's in 99% just a # Python package and the project sits at least one level above. continue - else: + elif not dir.is_file(): first_no_init_file = dir if _is_django_path(dir): @@ -416,7 +406,7 @@ def get_default_project(path=None): if first_no_init_file is not None: return Project(first_no_init_file) - curdir = path if os.path.isdir(path) else os.path.dirname(path) + curdir = path if path.is_dir() else path.parent return Project(curdir) diff --git a/jedi/api/refactoring/__init__.py b/jedi/api/refactoring/__init__.py index d0f7a7b8..32aff1ed 100644 --- a/jedi/api/refactoring/__init__.py +++ b/jedi/api/refactoring/__init__.py @@ -1,7 +1,6 @@ -from os.path import dirname, basename, join, relpath -import os -import re import difflib +from pathlib import Path +from typing import Dict from parso import split_lines @@ -43,15 +42,15 @@ class ChangedFile(object): if self._from_path is None: from_p = '' else: - from_p = relpath(self._from_path, project_path) + from_p = self._from_path.relative_to(project_path) if self._to_path is None: to_p = '' else: - to_p = relpath(self._to_path, project_path) + to_p = self._to_path.relative_to(project_path) diff = difflib.unified_diff( old_lines, new_lines, - fromfile=from_p, - tofile=to_p, + fromfile=str(from_p), + tofile=str(to_p), ) # Apparently there's a space at the end of the diff - for whatever # reason. @@ -79,17 +78,15 @@ class Refactoring(object): self._renames = renames self._file_to_node_changes = file_to_node_changes - def get_changed_files(self): - """ - Returns a path to ``ChangedFile`` map. - """ + def get_changed_files(self) -> Dict[Path, ChangedFile]: def calculate_to_path(p): if p is None: return p + p = str(p) for from_, to in renames: - if p.startswith(from_): - p = to + p[len(from_):] - return p + if p.startswith(str(from_)): + p = str(to) + p[len(str(from_)):] + return Path(p) renames = self.get_renames() return { @@ -115,7 +112,7 @@ class Refactoring(object): project_path = self._inference_state.project.path for from_, to in self.get_renames(): text += 'rename from %s\nrename to %s\n' \ - % (relpath(from_, project_path), relpath(to, project_path)) + % (from_.relative_to(project_path), to.relative_to(project_path)) return text + ''.join(f.get_diff() for f in self.get_changed_files().values()) @@ -127,17 +124,14 @@ class Refactoring(object): f.apply() for old, new in self.get_renames(): - os.rename(old, new) + old.rename(new) def _calculate_rename(path, new_name): - name = basename(path) - dir_ = dirname(path) - if name in ('__init__.py', '__init__.pyi'): - parent_dir = dirname(dir_) - return dir_, join(parent_dir, new_name) - ending = re.search(r'\.pyi?$', name).group(0) - return path, join(dir_, new_name + ending) + dir_ = path.parent + if path.name in ('__init__.py', '__init__.pyi'): + return dir_, dir_.parent.joinpath(new_name) + return path, dir_.joinpath(new_name + path.suffix) def rename(inference_state, definitions, new_name): @@ -150,7 +144,8 @@ def rename(inference_state, definitions, new_name): for d in definitions: tree_name = d._name.tree_name if d.type == 'module' and tree_name is None: - file_renames.add(_calculate_rename(d.module_path, new_name)) + p = None if d.module_path is None else Path(d.module_path) + file_renames.add(_calculate_rename(p, new_name)) else: # This private access is ok in a way. It's not public to # protect Jedi users from seeing it. diff --git a/jedi/api/refactoring/extract.py b/jedi/api/refactoring/extract.py index ad2ded9c..6e7df7e1 100644 --- a/jedi/api/refactoring/extract.py +++ b/jedi/api/refactoring/extract.py @@ -350,8 +350,7 @@ def _find_non_global_names(nodes): if node.type == 'trailer' and node.children[0] == '.': continue - for x in _find_non_global_names(children): # Python 2... - yield x + yield from _find_non_global_names(children) def _get_code_insertion_node(node, is_bound_method): diff --git a/jedi/api/replstartup.py b/jedi/api/replstartup.py index 38aa8e6f..e0f23d19 100644 --- a/jedi/api/replstartup.py +++ b/jedi/api/replstartup.py @@ -9,7 +9,7 @@ just use IPython instead:: Then you will be able to use Jedi completer in your Python interpreter:: $ python - Python 2.7.2+ (default, Jul 20 2012, 22:15:08) + Python 3.9.2+ (default, Jul 20 2020, 22:15:08) [GCC 4.6.1] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import os diff --git a/jedi/api/strings.py b/jedi/api/strings.py index 6653671e..f91da25b 100644 --- a/jedi/api/strings.py +++ b/jedi/api/strings.py @@ -9,7 +9,6 @@ names in a module, but pretty much an arbitrary string. """ import re -from jedi._compatibility import unicode from jedi.inference.names import AbstractArbitraryName from jedi.inference.helpers import infer_call_of_leaf from jedi.api.classes import Completion @@ -19,7 +18,7 @@ _sentinel = object() class StringName(AbstractArbitraryName): - api_type = u'string' + api_type = 'string' is_value_name = False @@ -65,7 +64,7 @@ def _completions_for_dicts(inference_state, dicts, literal_string, cut_end_quote def _create_repr_string(literal_string, dict_key): - if not isinstance(dict_key, (unicode, bytes)) or not literal_string: + if not isinstance(dict_key, (str, bytes)) or not literal_string: return repr(dict_key) r = repr(dict_key) diff --git a/jedi/common.py b/jedi/common.py index 75b69299..eb4b4996 100644 --- a/jedi/common.py +++ b/jedi/common.py @@ -1,18 +1,6 @@ -import os from contextlib import contextmanager -def traverse_parents(path, include_current=False): - if not include_current: - path = os.path.dirname(path) - - previous = None - while previous != path: - yield path - previous = path - path = os.path.dirname(path) - - @contextmanager def monkeypatch(obj, attribute_name, new_value): """ diff --git a/jedi/debug.py b/jedi/debug.py index 753930e1..4066cff7 100644 --- a/jedi/debug.py +++ b/jedi/debug.py @@ -2,8 +2,6 @@ import os import time from contextlib import contextmanager -from jedi._compatibility import encoding, is_py3, u - _inited = False @@ -97,16 +95,14 @@ def increase_indent_cm(title=None, color='MAGENTA'): dbg('End: ' + title, color=color) -def dbg(message, *args, **kwargs): +def dbg(message, *args, color='GREEN'): """ Looks at the stack, to see if a debug message should be printed. """ - # Python 2 compatibility, because it doesn't understand default args - color = kwargs.pop('color', 'GREEN') assert color if debug_function and enable_notice: i = ' ' * _debug_indent _lazy_colorama_init() - debug_function(color, i + 'dbg: ' + message % tuple(u(repr(a)) for a in args)) + debug_function(color, i + 'dbg: ' + message % tuple(repr(a) for a in args)) def warning(message, *args, **kwargs): @@ -116,7 +112,7 @@ def warning(message, *args, **kwargs): if debug_function and enable_warning: i = ' ' * _debug_indent if format: - message = message % tuple(u(repr(a)) for a in args) + message = message % tuple(repr(a) for a in args) debug_function('RED', i + 'warning: ' + message) @@ -135,9 +131,4 @@ def print_to_stdout(color, str_out): """ col = getattr(Fore, color) _lazy_colorama_init() - if not is_py3: - str_out = str_out.encode(encoding, 'replace') print(col + str_out + Fore.RESET) - - -# debug_function = print_to_stdout diff --git a/jedi/file_io.py b/jedi/file_io.py index c4a5d24a..c4518fdb 100644 --- a/jedi/file_io.py +++ b/jedi/file_io.py @@ -65,13 +65,13 @@ class FileIOFolderMixin(object): class ZipFileIO(file_io.KnownContentFileIO, FileIOFolderMixin): """For .zip and .egg archives""" def __init__(self, path, code, zip_path): - super(ZipFileIO, self).__init__(path, code) + super().__init__(path, code) self._zip_path = zip_path def get_last_modified(self): try: return os.path.getmtime(self._zip_path) - except OSError: # Python 3 would probably only need FileNotFoundError + except (FileNotFoundError, PermissionError, NotADirectoryError): return None diff --git a/jedi/inference/__init__.py b/jedi/inference/__init__.py index dbefc52a..68217365 100644 --- a/jedi/inference/__init__.py +++ b/jedi/inference/__init__.py @@ -123,16 +123,14 @@ class InferenceState(object): @property @inference_state_function_cache() def builtins_module(self): - module_name = u'builtins' - if self.environment.version_info.major == 2: - module_name = u'__builtin__' + module_name = 'builtins' builtins_module, = self.import_module((module_name,), sys_path=()) return builtins_module @property @inference_state_function_cache() def typing_module(self): - typing_module, = self.import_module((u'typing',)) + typing_module, = self.import_module(('typing',)) return typing_module def reset_recursion_limitations(self): @@ -178,14 +176,16 @@ class InferenceState(object): return helpers.infer_call_of_leaf(context, name) - def parse_and_get_code(self, code=None, path=None, encoding='utf-8', + def parse_and_get_code(self, code=None, path=None, use_latest_grammar=False, file_io=None, **kwargs): + if path is not None: + path = str(path) if code is None: if file_io is None: file_io = FileIO(path) code = file_io.read() # We cannot just use parso, because it doesn't use errors='replace'. - code = parso.python_bytes_to_unicode(code, encoding=encoding, errors='replace') + code = parso.python_bytes_to_unicode(code, encoding='utf-8', errors='replace') if len(code) > settings._cropped_file_size: code = code[:settings._cropped_file_size] diff --git a/jedi/inference/analysis.py b/jedi/inference/analysis.py index 9ce56370..1e51db55 100644 --- a/jedi/inference/analysis.py +++ b/jedi/inference/analysis.py @@ -3,7 +3,6 @@ Module for statical analysis. """ from parso.python import tree -from jedi._compatibility import force_unicode from jedi import debug from jedi.inference.helpers import is_string @@ -50,13 +49,10 @@ class Error(object): first = self.__class__.__name__[0] return first + str(CODES[self.name][0]) - def __unicode__(self): + def __str__(self): return '%s:%s:%s: %s %s' % (self.path, self.line, self.column, self.code, self.message) - def __str__(self): - return self.__unicode__() - def __eq__(self, other): return (self.path == other.path and self.name == other.name and self._start_pos == other._start_pos) @@ -193,7 +189,7 @@ def _check_for_exception_catch(node_context, jedi_name, exception, payload=None) key, lazy_value = unpacked_args[1] names = list(lazy_value.infer()) assert len(names) == 1 and is_string(names[0]) - assert force_unicode(names[0].get_safe_value()) == payload[1].value + assert names[0].get_safe_value() == payload[1].value # Check objects key, lazy_value = unpacked_args[0] diff --git a/jedi/inference/arguments.py b/jedi/inference/arguments.py index aa200fdb..99de7210 100644 --- a/jedi/inference/arguments.py +++ b/jedi/inference/arguments.py @@ -1,8 +1,8 @@ import re +from itertools import zip_longest from parso.python import tree -from jedi._compatibility import zip_longest from jedi import debug from jedi.inference.utils import PushBackIterator from jedi.inference import analysis @@ -142,11 +142,8 @@ def unpack_arglist(arglist): if arglist is None: return - # Allow testlist here as well for Python2's class inheritance - # definitions. - if not (arglist.type in ('arglist', 'testlist') or ( - # in python 3.5 **arg is an argument, not arglist - arglist.type == 'argument' and arglist.children[0] in ('*', '**'))): + if arglist.type != 'arglist' and not ( + arglist.type == 'argument' and arglist.children[0] in ('*', '**')): yield 0, arglist return @@ -189,8 +186,6 @@ class TreeArguments(AbstractArguments): iterators = [_iterate_star_args(self.context, a, el, funcdef) for a in arrays] for values in list(zip_longest(*iterators)): - # TODO zip_longest yields None, that means this would raise - # an exception? yield None, get_merged_lazy_value( [v for v in values if v is not None] ) diff --git a/jedi/inference/base_value.py b/jedi/inference/base_value.py index c516d463..345d8eee 100644 --- a/jedi/inference/base_value.py +++ b/jedi/inference/base_value.py @@ -8,10 +8,11 @@ just one. """ from functools import reduce from operator import add +from itertools import zip_longest + from parso.python.tree import Name from jedi import debug -from jedi._compatibility import zip_longest, unicode from jedi.parser_utils import clean_scope_docstring from jedi.inference.helpers import SimpleGetItemNotFound from jedi.inference.utils import safe_property @@ -92,7 +93,7 @@ class HelperValueMixin(object): return values def py__await__(self): - await_value_set = self.py__getattribute__(u"__await__") + await_value_set = self.py__getattribute__("__await__") if not await_value_set: debug.warning('Tried to run __await__ on value %s', self) return await_value_set.execute_with_values() @@ -357,7 +358,7 @@ class ValueWrapper(_ValueWrapperBase): class TreeValue(Value): def __init__(self, inference_state, parent_context, tree_node): - super(TreeValue, self).__init__(inference_state, parent_context) + super().__init__(inference_state, parent_context) self.tree_node = tree_node def __repr__(self): @@ -385,7 +386,7 @@ def _getitem(value, index_values, contextualized_node): unused_values = set() for index_value in index_values: index = index_value.get_safe_value(default=None) - if type(index) in (float, int, str, unicode, slice, bytes): + if type(index) in (float, int, str, slice, bytes): try: result |= value.py__simple_getitem__(index) continue diff --git a/jedi/inference/cache.py b/jedi/inference/cache.py index 45f499b7..8360a42a 100644 --- a/jedi/inference/cache.py +++ b/jedi/inference/cache.py @@ -78,7 +78,7 @@ class CachedMetaClass(type): """ @inference_state_as_method_param_cache() def __call__(self, *args, **kwargs): - return super(CachedMetaClass, self).__call__(*args, **kwargs) + return super().__call__(*args, **kwargs) def inference_state_method_generator_cache(): diff --git a/jedi/inference/compiled/__init__.py b/jedi/inference/compiled/__init__.py index b5d435fc..faf5d373 100644 --- a/jedi/inference/compiled/__init__.py +++ b/jedi/inference/compiled/__init__.py @@ -1,4 +1,3 @@ -from jedi._compatibility import unicode from jedi.inference.compiled.value import CompiledValue, CompiledName, \ CompiledValueFilter, CompiledValueName, create_from_access_path from jedi.inference.base_value import LazyValueWrapper @@ -29,7 +28,7 @@ class ExactValue(LazyValueWrapper): if name in ('get_safe_value', 'execute_operation', 'access_handle', 'negate', 'py__bool__', 'is_compiled'): return getattr(self._compiled_value, name) - return super(ExactValue, self).__getattribute__(name) + return super().__getattribute__(name) def _get_wrapped_value(self): instance, = builtin_from_name( @@ -45,7 +44,7 @@ def create_simple_object(inference_state, obj): Only allows creations of objects that are easily picklable across Python versions. """ - assert type(obj) in (int, float, str, bytes, unicode, slice, complex, bool), obj + assert type(obj) in (int, float, str, bytes, slice, complex, bool), repr(obj) compiled_value = create_from_access_path( inference_state, inference_state.compiled_subprocess.create_simple_object(obj) @@ -54,7 +53,7 @@ def create_simple_object(inference_state, obj): def get_string_value_set(inference_state): - return builtin_from_name(inference_state, u'str').execute_with_values() + return builtin_from_name(inference_state, 'str').execute_with_values() def load_module(inference_state, dotted_name, **kwargs): diff --git a/jedi/inference/compiled/access.py b/jedi/inference/compiled/access.py index a02b548b..46dc8249 100644 --- a/jedi/inference/compiled/access.py +++ b/jedi/inference/compiled/access.py @@ -1,4 +1,3 @@ -from __future__ import print_function import inspect import types import sys @@ -6,12 +5,12 @@ import operator as op from collections import namedtuple import warnings import re +import builtins +import typing -from jedi._compatibility import unicode, is_py3, builtins, \ - py_version, force_unicode from jedi.inference.compiled.getattr_static import getattr_static -ALLOWED_GETITEM_TYPES = (str, list, tuple, unicode, bytes, bytearray, dict) +ALLOWED_GETITEM_TYPES = (str, list, tuple, bytes, bytearray, dict) MethodDescriptorType = type(str.replace) # These are not considered classes and access is granted even though they have @@ -28,17 +27,12 @@ NOT_CLASS_TYPES = ( types.MethodType, types.ModuleType, types.TracebackType, - MethodDescriptorType + MethodDescriptorType, + types.MappingProxyType, + types.SimpleNamespace, + types.DynamicClassAttribute, ) -if is_py3: - NOT_CLASS_TYPES += ( - types.MappingProxyType, - types.SimpleNamespace, - types.DynamicClassAttribute, - ) - - # Those types don't exist in typing. MethodDescriptorType = type(str.replace) WrapperDescriptorType = type(set.__iter__) @@ -144,35 +138,22 @@ class AccessPath(object): def __init__(self, accesses): self.accesses = accesses - # Writing both of these methods here looks a bit ridiculous. However with - # the differences of Python 2/3 it's actually necessary, because we will - # otherwise have a accesses attribute that is bytes instead of unicode. - def __getstate__(self): - return self.accesses - - def __setstate__(self, value): - self.accesses = value - def create_access_path(inference_state, obj): access = create_access(inference_state, obj) return AccessPath(access.get_access_path_tuples()) -def _force_unicode_decorator(func): - return lambda *args, **kwargs: force_unicode(func(*args, **kwargs)) - - def get_api_type(obj): if inspect.isclass(obj): - return u'class' + return 'class' elif inspect.ismodule(obj): - return u'module' + return 'module' elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \ or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj): - return u'function' + return 'function' # Everything else... - return u'instance' + return 'instance' class DirectObjectAccess(object): @@ -199,7 +180,7 @@ class DirectObjectAccess(object): return None def py__doc__(self): - return force_unicode(inspect.getdoc(self._obj)) or u'' + return inspect.getdoc(self._obj) or '' def py__name__(self): if not _is_class_instance(self._obj) or \ @@ -214,7 +195,7 @@ class DirectObjectAccess(object): return None try: - return force_unicode(cls.__name__) + return cls.__name__ except AttributeError: return None @@ -260,26 +241,23 @@ class DirectObjectAccess(object): # Avoid some weird hacks that would just fail, because they cannot be # used by pickle. if not isinstance(paths, list) \ - or not all(isinstance(p, (bytes, unicode)) for p in paths): + or not all(isinstance(p, str) for p in paths): return None return paths - @_force_unicode_decorator @shorten_repr def get_repr(self): - builtins = 'builtins', '__builtin__' - if inspect.ismodule(self._obj): return repr(self._obj) # Try to avoid execution of the property. - if safe_getattr(self._obj, '__module__', default='') in builtins: + if safe_getattr(self._obj, '__module__', default='') == 'builtins': return repr(self._obj) type_ = type(self._obj) if type_ == type: return type.__repr__(self._obj) - if safe_getattr(type_, '__module__', default='') in builtins: + if safe_getattr(type_, '__module__', default='') == 'builtins': # Allow direct execution of repr for builtins. return repr(self._obj) return object.__repr__(self._obj) @@ -310,10 +288,10 @@ class DirectObjectAccess(object): name = try_to_get_name(type(self._obj)) if name is None: return () - return tuple(force_unicode(n) for n in name.split('.')) + return tuple(name.split('.')) def dir(self): - return list(map(force_unicode, dir(self._obj))) + return dir(self._obj) def has_iter(self): try: @@ -396,7 +374,7 @@ class DirectObjectAccess(object): return [self._create_access(module), access] def get_safe_value(self): - if type(self._obj) in (bool, bytes, float, int, str, unicode, slice) or self._obj is None: + if type(self._obj) in (bool, bytes, float, int, str, slice) or self._obj is None: return self._obj raise ValueError("Object is type %s and not simple" % type(self._obj)) @@ -464,9 +442,6 @@ class DirectObjectAccess(object): """ Returns Tuple[Optional[str], Tuple[AccessPath, ...]] """ - if sys.version_info < (3, 5): - return None, () - name = None args = () if safe_getattr(self._obj, '__module__', default='') == 'typing': @@ -485,8 +460,6 @@ class DirectObjectAccess(object): return inspect.isclass(self._obj) and self._obj != type def _annotation_to_str(self, annotation): - if py_version < 30: - return '' return inspect.formatannotation(annotation) def get_signature_params(self): @@ -505,8 +478,6 @@ class DirectObjectAccess(object): def _get_signature(self): obj = self._obj - if py_version < 33: - raise ValueError("inspect.signature was introduced in 3.3") try: return inspect.signature(obj) except (RuntimeError, TypeError): @@ -525,15 +496,9 @@ class DirectObjectAccess(object): return None try: - # Python 2 doesn't have typing. - import typing - except ImportError: + o = typing.get_type_hints(self._obj).get('return') + except Exception: pass - else: - try: - o = typing.get_type_hints(self._obj).get('return') - except Exception: - pass return self._create_access_path(o) @@ -546,7 +511,7 @@ class DirectObjectAccess(object): objects of an objects """ tuples = dict( - (force_unicode(name), self.is_allowed_getattr(name)) + (name, self.is_allowed_getattr(name)) for name in self.dir() ) return self.needs_type_completions(), tuples diff --git a/jedi/inference/compiled/getattr_static.py b/jedi/inference/compiled/getattr_static.py index 7225aabd..03c199ef 100644 --- a/jedi/inference/compiled/getattr_static.py +++ b/jedi/inference/compiled/getattr_static.py @@ -7,7 +7,6 @@ information returned to enable Jedi to make decisions. import types from jedi import debug -from jedi._compatibility import py_version _sentinel = object() @@ -39,7 +38,7 @@ def _is_type(obj): return True -def _shadowed_dict_newstyle(klass): +def _shadowed_dict(klass): dict_attr = type.__dict__["__dict__"] for entry in _static_getmro(klass): try: @@ -54,7 +53,7 @@ def _shadowed_dict_newstyle(klass): return _sentinel -def _static_getmro_newstyle(klass): +def _static_getmro(klass): mro = type.__dict__['__mro__'].__get__(klass) if not isinstance(mro, (tuple, list)): # There are unfortunately no tests for this, I was not able to @@ -65,70 +64,8 @@ def _static_getmro_newstyle(klass): return mro -if py_version >= 30: - _shadowed_dict = _shadowed_dict_newstyle - _get_type = type - _static_getmro = _static_getmro_newstyle -else: - def _shadowed_dict(klass): - """ - In Python 2 __dict__ is not overwritable: - - class Foo(object): pass - setattr(Foo, '__dict__', 4) - - Traceback (most recent call last): - File "", line 1, in - TypeError: __dict__ must be a dictionary object - - It applies to both newstyle and oldstyle classes: - - class Foo(object): pass - setattr(Foo, '__dict__', 4) - Traceback (most recent call last): - File "", line 1, in - AttributeError: attribute '__dict__' of 'type' objects is not writable - - It also applies to instances of those objects. However to keep things - straight forward, newstyle classes always use the complicated way of - accessing it while oldstyle classes just use getattr. - """ - if type(klass) is _oldstyle_class_type: - return getattr(klass, '__dict__', _sentinel) - return _shadowed_dict_newstyle(klass) - - class _OldStyleClass: - pass - - _oldstyle_instance_type = type(_OldStyleClass()) - _oldstyle_class_type = type(_OldStyleClass) - - def _get_type(obj): - type_ = object.__getattribute__(obj, '__class__') - if type_ is _oldstyle_instance_type: - # Somehow for old style classes we need to access it directly. - return obj.__class__ - return type_ - - def _static_getmro(klass): - if type(klass) is _oldstyle_class_type: - def oldstyle_mro(klass): - """ - Oldstyle mro is a really simplistic way of look up mro: - https://stackoverflow.com/questions/54867/what-is-the-difference-between-old-style-and-new-style-classes-in-python - """ - yield klass - for base in klass.__bases__: - for yield_from in oldstyle_mro(base): - yield yield_from - - return oldstyle_mro(klass) - - return _static_getmro_newstyle(klass) - - def _safe_hasattr(obj, name): - return _check_class(_get_type(obj), name) is not _sentinel + return _check_class(type(obj), name) is not _sentinel def _safe_is_data_descriptor(obj): @@ -151,7 +88,7 @@ def getattr_static(obj, attr, default=_sentinel): """ instance_result = _sentinel if not _is_type(obj): - klass = _get_type(obj) + klass = type(obj) dict_attr = _shadowed_dict(klass) if (dict_attr is _sentinel or type(dict_attr) is types.MemberDescriptorType): instance_result = _check_instance(obj, attr) diff --git a/jedi/inference/compiled/mixed.py b/jedi/inference/compiled/mixed.py index ce31d7e0..e96957ed 100644 --- a/jedi/inference/compiled/mixed.py +++ b/jedi/inference/compiled/mixed.py @@ -4,11 +4,9 @@ Used only for REPL Completion. import inspect import os -import sys from jedi.parser_utils import get_cached_code_lines -from jedi._compatibility import unwrap from jedi import settings from jedi.cache import memoize_method from jedi.inference import compiled @@ -44,7 +42,7 @@ class MixedObject(ValueWrapper): to modify the runtime. """ def __init__(self, compiled_value, tree_value): - super(MixedObject, self).__init__(tree_value) + super().__init__(tree_value) self.compiled_value = compiled_value self.access_handle = compiled_value.access_handle @@ -115,7 +113,7 @@ class MixedName(NameWrapper): The ``CompiledName._compiled_value`` is our MixedObject. """ def __init__(self, wrapped_name, parent_tree_value): - super(MixedName, self).__init__(wrapped_name) + super().__init__(wrapped_name) self._parent_tree_value = parent_tree_value @property @@ -141,12 +139,12 @@ class MixedName(NameWrapper): class MixedObjectFilter(compiled.CompiledValueFilter): def __init__(self, inference_state, compiled_value, tree_value): - super(MixedObjectFilter, self).__init__(inference_state, compiled_value) + super().__init__(inference_state, compiled_value) self._tree_value = tree_value def _create_name(self, name): return MixedName( - super(MixedObjectFilter, self)._create_name(name), + super()._create_name(name), self._tree_value, ) @@ -163,12 +161,11 @@ def _load_module(inference_state, path): def _get_object_to_check(python_object): """Check if inspect.getfile has a chance to find the source.""" - if sys.version_info[0] > 2: - try: - python_object = unwrap(python_object) - except ValueError: - # Can return a ValueError when it wraps around - pass + try: + python_object = inspect.unwrap(python_object) + except ValueError: + # Can return a ValueError when it wraps around + pass if (inspect.ismodule(python_object) or inspect.isclass(python_object) diff --git a/jedi/inference/compiled/subprocess/__init__.py b/jedi/inference/compiled/subprocess/__init__.py index 35724a48..6f4097c3 100644 --- a/jedi/inference/compiled/subprocess/__init__.py +++ b/jedi/inference/compiled/subprocess/__init__.py @@ -9,19 +9,14 @@ goals: import os import sys +import queue import subprocess -import socket -import errno import traceback +import weakref from functools import partial from threading import Thread -try: - from queue import Queue, Empty -except ImportError: - from Queue import Queue, Empty # python 2.7 -from jedi._compatibility import queue, is_py3, force_unicode, \ - pickle_dump, pickle_load, GeneralizedPopen, weakref +from jedi._compatibility import pickle_dump, pickle_load from jedi import debug from jedi.cache import memoize_method from jedi.inference.compiled.subprocess import functions @@ -31,11 +26,27 @@ from jedi.api.exceptions import InternalError _MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py') +PICKLE_PROTOCOL = 4 -def _enqueue_output(out, queue): +class _GeneralizedPopen(subprocess.Popen): + def __init__(self, *args, **kwargs): + if os.name == 'nt': + try: + # Was introduced in Python 3.7. + CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW + except AttributeError: + CREATE_NO_WINDOW = 0x08000000 + kwargs['creationflags'] = CREATE_NO_WINDOW + # The child process doesn't need file descriptors except 0, 1, 2. + # This is unix only. + kwargs['close_fds'] = 'posix' in sys.builtin_module_names + super().__init__(*args, **kwargs) + + +def _enqueue_output(out, queue_): for line in iter(out.readline, b''): - queue.put(line) + queue_.put(line) def _add_stderr_to_debug(stderr_queue): @@ -46,7 +57,7 @@ def _add_stderr_to_debug(stderr_queue): line = stderr_queue.get_nowait() line = line.decode('utf-8', 'replace') debug.warning('stderr output: %s' % line.rstrip('\n')) - except Empty: + except queue.Empty: break @@ -105,7 +116,7 @@ class InferenceStateSameProcess(_InferenceStateProcess): class InferenceStateSubprocess(_InferenceStateProcess): def __init__(self, inference_state, compiled_subprocess): - super(InferenceStateSubprocess, self).__init__(inference_state) + super().__init__(inference_state) self._used = False self._compiled_subprocess = compiled_subprocess @@ -153,8 +164,6 @@ class InferenceStateSubprocess(_InferenceStateProcess): class CompiledSubprocess(object): is_crashed = False - # Start with 2, gets set after _get_info. - _pickle_protocol = 2 def __init__(self, executable, env_vars=None): self._executable = executable @@ -164,10 +173,9 @@ class CompiledSubprocess(object): def __repr__(self): pid = os.getpid() - return '<%s _executable=%r, _pickle_protocol=%r, is_crashed=%r, pid=%r>' % ( + return '<%s _executable=%r, is_crashed=%r, pid=%r>' % ( self.__class__.__name__, self._executable, - self._pickle_protocol, self.is_crashed, pid, ) @@ -182,17 +190,14 @@ class CompiledSubprocess(object): os.path.dirname(os.path.dirname(parso_path)), '.'.join(str(x) for x in sys.version_info[:3]), ) - process = GeneralizedPopen( + process = _GeneralizedPopen( args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - # Use system default buffering on Python 2 to improve performance - # (this is already the case on Python 3). - bufsize=-1, env=self._env_vars ) - self._stderr_queue = Queue() + self._stderr_queue = queue.Queue() self._stderr_thread = t = Thread( target=_enqueue_output, args=(process.stderr, self._stderr_queue) @@ -231,20 +236,10 @@ class CompiledSubprocess(object): if self.is_crashed: raise InternalError("The subprocess %s has crashed." % self._executable) - if not is_py3: - # Python 2 compatibility - kwargs = {force_unicode(key): value for key, value in kwargs.items()} - data = inference_state_id, function, args, kwargs try: - pickle_dump(data, self._get_process().stdin, self._pickle_protocol) - except (socket.error, IOError) as e: - # Once Python2 will be removed we can just use `BrokenPipeError`. - # Also, somehow in windows it returns EINVAL instead of EPIPE if - # the subprocess dies. - if e.errno not in (errno.EPIPE, errno.EINVAL): - # Not a broken pipe - raise + pickle_dump(data, self._get_process().stdin, PICKLE_PROTOCOL) + except BrokenPipeError: self._kill() raise InternalError("The subprocess %s was killed. Maybe out of memory?" % self._executable) @@ -286,12 +281,11 @@ class CompiledSubprocess(object): class Listener(object): - def __init__(self, pickle_protocol): + def __init__(self): self._inference_states = {} # TODO refactor so we don't need to process anymore just handle # controlling. self._process = _InferenceStateProcess(Listener) - self._pickle_protocol = pickle_protocol def _get_inference_state(self, function, inference_state_id): from jedi.inference import InferenceState @@ -334,15 +328,8 @@ class Listener(object): # because stdout is used for IPC. sys.stdout = open(os.devnull, 'w') stdin = sys.stdin - if sys.version_info[0] > 2: - stdout = stdout.buffer - stdin = stdin.buffer - # Python 2 opens streams in text mode on Windows. Set stdout and stdin - # to binary mode. - elif sys.platform == 'win32': - import msvcrt - msvcrt.setmode(stdout.fileno(), os.O_BINARY) - msvcrt.setmode(stdin.fileno(), os.O_BINARY) + stdout = stdout.buffer + stdin = stdin.buffer while True: try: @@ -356,7 +343,7 @@ class Listener(object): except Exception as e: result = True, traceback.format_exc(), e - pickle_dump(result, stdout, self._pickle_protocol) + pickle_dump(result, stdout, PICKLE_PROTOCOL) class AccessHandle(object): @@ -385,9 +372,8 @@ class AccessHandle(object): if name in ('id', 'access') or name.startswith('_'): raise AttributeError("Something went wrong with unpickling") - # if not is_py3: print >> sys.stderr, name # print('getattr', name, file=sys.stderr) - return partial(self._workaround, force_unicode(name)) + return partial(self._workaround, name) def _workaround(self, name, *args, **kwargs): """ diff --git a/jedi/inference/compiled/subprocess/__main__.py b/jedi/inference/compiled/subprocess/__main__.py index 5e92229f..a9845a38 100644 --- a/jedi/inference/compiled/subprocess/__main__.py +++ b/jedi/inference/compiled/subprocess/__main__.py @@ -1,5 +1,10 @@ import os import sys +from importlib.machinery import PathFinder + +# Remove the first entry, because it's simply a directory entry that equals +# this directory. +del sys.path[0] def _get_paths(): @@ -11,45 +16,24 @@ def _get_paths(): return {'jedi': _jedi_path, 'parso': _parso_path} -# Remove the first entry, because it's simply a directory entry that equals -# this directory. -del sys.path[0] +class _ExactImporter(object): + def __init__(self, path_dct): + self._path_dct = path_dct -if sys.version_info > (3, 4): - from importlib.machinery import PathFinder + def find_module(self, fullname, path=None): + if path is None and fullname in self._path_dct: + p = self._path_dct[fullname] + loader = PathFinder.find_module(fullname, path=[p]) + return loader + return None - class _ExactImporter(object): - def __init__(self, path_dct): - self._path_dct = path_dct - - def find_module(self, fullname, path=None): - if path is None and fullname in self._path_dct: - p = self._path_dct[fullname] - loader = PathFinder.find_module(fullname, path=[p]) - return loader - return None - - # Try to import jedi/parso. - sys.meta_path.insert(0, _ExactImporter(_get_paths())) - from jedi.inference.compiled import subprocess # NOQA - sys.meta_path.pop(0) -else: - import imp - - def load(name): - paths = list(_get_paths().values()) - fp, pathname, description = imp.find_module(name, paths) - return imp.load_module(name, fp, pathname, description) - - load('parso') - load('jedi') - from jedi.inference.compiled import subprocess # NOQA - -from jedi._compatibility import highest_pickle_protocol # noqa: E402 +# Try to import jedi/parso. +sys.meta_path.insert(0, _ExactImporter(_get_paths())) +from jedi.inference.compiled import subprocess # noqa: E402 +sys.meta_path.pop(0) # Retrieve the pickle protocol. host_sys_version = [int(x) for x in sys.argv[2].split('.')] -pickle_protocol = highest_pickle_protocol([sys.version_info, host_sys_version]) # And finally start the client. -subprocess.Listener(pickle_protocol=pickle_protocol).listen() +subprocess.Listener().listen() diff --git a/jedi/inference/compiled/subprocess/functions.py b/jedi/inference/compiled/subprocess/functions.py index 86b5287b..2d3ab607 100644 --- a/jedi/inference/compiled/subprocess/functions.py +++ b/jedi/inference/compiled/subprocess/functions.py @@ -1,14 +1,16 @@ -from __future__ import print_function import sys import os -import re import inspect +import importlib +import warnings +from zipimport import zipimporter +from importlib.machinery import all_suffixes -from jedi._compatibility import find_module, cast_path, force_unicode, \ - all_suffixes, scandir +from jedi._compatibility import cast_path from jedi.inference.compiled import access from jedi import debug from jedi import parser_utils +from jedi.file_io import KnownContentFileIO, ZipFileIO def get_sys_path(): @@ -35,7 +37,7 @@ def get_module_info(inference_state, sys_path=None, full_name=None, **kwargs): if sys_path is not None: sys.path, temp = sys_path, sys.path try: - return find_module(full_name=full_name, **kwargs) + return _find_module(full_name=full_name, **kwargs) except ImportError: return None, None finally: @@ -44,7 +46,7 @@ def get_module_info(inference_state, sys_path=None, full_name=None, **kwargs): def get_builtin_module_names(inference_state): - return list(map(force_unicode, sys.builtin_module_names)) + return sys.builtin_module_names def _test_raise_error(inference_state, exception_type): @@ -90,7 +92,7 @@ def _iter_module_names(inference_state, paths): # Python modules/packages for path in paths: try: - dirs = scandir(path) + dirs = os.scandir(path) except OSError: # The file might not exist or reading it might lead to an error. debug.warning("Not possible to list directory: %s", path) @@ -99,10 +101,9 @@ def _iter_module_names(inference_state, paths): name = dir_entry.name # First Namespaces then modules/stubs if dir_entry.is_dir(): - # pycache is obviously not an interestin namespace. Also the + # pycache is obviously not an interesting namespace. Also the # name must be a valid identifier. - # TODO use str.isidentifier, once Python 2 is removed - if name != '__pycache__' and not re.search(r'\W|^\d', name): + if name != '__pycache__' and name.isidentifier(): yield name else: if name.endswith('.pyi'): # Stub files @@ -113,3 +114,123 @@ def _iter_module_names(inference_state, paths): if modname and '.' not in modname: if modname != '__init__': yield modname + + +def _find_module(string, path=None, full_name=None, is_global_search=True): + """ + Provides information about a module. + + This function isolates the differences in importing libraries introduced with + python 3.3 on; it gets a module name and optionally a path. It will return a + tuple containin an open file for the module (if not builtin), the filename + or the name of the module if it is a builtin one and a boolean indicating + if the module is contained in a package. + """ + spec = None + loader = None + + for finder in sys.meta_path: + if is_global_search and finder != importlib.machinery.PathFinder: + p = None + else: + p = path + try: + find_spec = finder.find_spec + except AttributeError: + # These are old-school clases that still have a different API, just + # ignore those. + continue + + spec = find_spec(string, p) + if spec is not None: + loader = spec.loader + if loader is None and not spec.has_location: + # This is a namespace package. + full_name = string if not path else full_name + implicit_ns_info = ImplicitNSInfo(full_name, spec.submodule_search_locations._path) + return implicit_ns_info, True + break + + return _find_module_py33(string, path, loader) + + +def _find_module_py33(string, path=None, loader=None, full_name=None, is_global_search=True): + loader = loader or importlib.machinery.PathFinder.find_module(string, path) + + if loader is None and path is None: # Fallback to find builtins + try: + with warnings.catch_warnings(record=True): + # Mute "DeprecationWarning: Use importlib.util.find_spec() + # instead." While we should replace that in the future, it's + # probably good to wait until we deprecate Python 3.3, since + # it was added in Python 3.4 and find_loader hasn't been + # removed in 3.6. + loader = importlib.find_loader(string) + except ValueError as e: + # See #491. Importlib might raise a ValueError, to avoid this, we + # just raise an ImportError to fix the issue. + raise ImportError("Originally " + repr(e)) + + if loader is None: + raise ImportError("Couldn't find a loader for {}".format(string)) + + return _from_loader(loader, string) + + +def _from_loader(loader, string): + try: + is_package_method = loader.is_package + except AttributeError: + is_package = False + else: + is_package = is_package_method(string) + try: + get_filename = loader.get_filename + except AttributeError: + return None, is_package + else: + module_path = cast_path(get_filename(string)) + + # To avoid unicode and read bytes, "overwrite" loader.get_source if + # possible. + try: + f = type(loader).get_source + except AttributeError: + raise ImportError("get_source was not defined on loader") + + if f is not importlib.machinery.SourceFileLoader.get_source: + # Unfortunately we are reading unicode here, not bytes. + # It seems hard to get bytes, because the zip importer + # logic just unpacks the zip file and returns a file descriptor + # that we cannot as easily access. Therefore we just read it as + # a string in the cases where get_source was overwritten. + code = loader.get_source(string) + else: + code = _get_source(loader, string) + + if code is None: + return None, is_package + if isinstance(loader, zipimporter): + return ZipFileIO(module_path, code, cast_path(loader.archive)), is_package + + return KnownContentFileIO(module_path, code), is_package + + +def _get_source(loader, fullname): + """ + This method is here as a replacement for SourceLoader.get_source. That + method returns unicode, but we prefer bytes. + """ + path = loader.get_filename(fullname) + try: + return loader.get_data(path) + except OSError: + raise ImportError('source not available through get_data()', + name=fullname) + + +class ImplicitNSInfo(object): + """Stores information returned from an implicit namespace spec""" + def __init__(self, name, paths): + self.name = name + self.paths = paths diff --git a/jedi/inference/compiled/value.py b/jedi/inference/compiled/value.py index 61b134ad..61600983 100644 --- a/jedi/inference/compiled/value.py +++ b/jedi/inference/compiled/value.py @@ -3,10 +3,12 @@ Imitate the parser representation. """ import re from functools import partial +from inspect import Parameter +from pathlib import Path from jedi import debug from jedi.inference.utils import to_list -from jedi._compatibility import force_unicode, Parameter, cast_path +from jedi._compatibility import cast_path from jedi.cache import memoize_method from jedi.inference.filters import AbstractFilter from jedi.inference.names import AbstractNameDefinition, ValueNameMixin, \ @@ -29,7 +31,7 @@ class CheckAttribute(object): def __call__(self, func): self.func = func if self.check_name is None: - self.check_name = force_unicode(func.__name__[2:]) + self.check_name = func.__name__[2:] return self def __get__(self, instance, owner): @@ -43,7 +45,7 @@ class CheckAttribute(object): class CompiledValue(Value): def __init__(self, inference_state, access_handle, parent_context=None): - super(CompiledValue, self).__init__(inference_state, parent_context) + super().__init__(inference_state, parent_context) self.access_handle = access_handle def py__call__(self, arguments): @@ -56,9 +58,9 @@ class CompiledValue(Value): ).execute_annotation() try: - self.access_handle.getattr_paths(u'__call__') + self.access_handle.getattr_paths('__call__') except AttributeError: - return super(CompiledValue, self).py__call__(arguments) + return super().py__call__(arguments) else: if self.access_handle.is_class(): from jedi.inference.value import CompiledInstance @@ -163,7 +165,7 @@ class CompiledValue(Value): try: access = self.access_handle.py__simple_getitem__(index) except AttributeError: - return super(CompiledValue, self).py__simple_getitem__(index) + return super().py__simple_getitem__(index) if access is None: return NO_VALUES @@ -174,19 +176,15 @@ class CompiledValue(Value): if all_access_paths is None: # This means basically that no __getitem__ has been defined on this # object. - return super(CompiledValue, self).py__getitem__(index_value_set, contextualized_node) + return super().py__getitem__(index_value_set, contextualized_node) return ValueSet( create_from_access_path(self.inference_state, access) for access in all_access_paths ) def py__iter__(self, contextualized_node=None): - # Python iterators are a bit strange, because there's no need for - # the __iter__ function as long as __getitem__ is defined (it will - # just start with __getitem__(0). This is especially true for - # Python 2 strings, where `str.__iter__` is not even defined. if not self.access_handle.has_iter(): - for x in super(CompiledValue, self).py__iter__(contextualized_node): + for x in super().py__iter__(contextualized_node): yield x access_path_list = self.access_handle.py__iter__list() @@ -264,7 +262,7 @@ class CompiledValue(Value): v.with_generics(arguments) for v in self.inference_state.typing_module.py__getattribute__(name) ]).execute_annotation() - return super(CompiledValue, self).execute_annotation() + return super().execute_annotation() def negate(self): return create_from_access_path(self.inference_state, self.access_handle.negate()) @@ -315,7 +313,10 @@ class CompiledModule(CompiledValue): return tuple(name.split('.')) def py__file__(self): - return cast_path(self.access_handle.py__file__()) + path = cast_path(self.access_handle.py__file__()) + if path is None: + return None + return Path(path) class CompiledName(AbstractNameDefinition): @@ -456,9 +457,6 @@ class CompiledValueFilter(AbstractFilter): """ To remove quite a few access calls we introduced the callback here. """ - # Always use unicode objects in Python 2 from here. - name = force_unicode(name) - if self._inference_state.allow_descriptor_getattr: pass @@ -502,7 +500,7 @@ class CompiledValueFilter(AbstractFilter): # ``dir`` doesn't include the type names. if not self.is_instance and needs_type_completions: - for filter in builtin_from_name(self._inference_state, u'type').get_filters(): + for filter in builtin_from_name(self._inference_state, 'type').get_filters(): names += filter.values() return names @@ -518,11 +516,11 @@ class CompiledValueFilter(AbstractFilter): docstr_defaults = { - 'floating point number': u'float', - 'character': u'str', - 'integer': u'int', - 'dictionary': u'dict', - 'string': u'str', + 'floating point number': 'float', + 'character': 'str', + 'integer': 'int', + 'dictionary': 'dict', + 'string': 'str', } @@ -534,7 +532,6 @@ def _parse_function_doc(doc): TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None TODO docstrings like 'tuple of integers' """ - doc = force_unicode(doc) # parse round parentheses: def func(a, (b,c)) try: count = 0 @@ -553,7 +550,7 @@ def _parse_function_doc(doc): # UnboundLocalError for undefined end in last line debug.dbg('no brackets found - no param') end = 0 - param_str = u'' + param_str = '' else: # remove square brackets, that show an optional param ( = None) def change_options(m): @@ -571,9 +568,9 @@ def _parse_function_doc(doc): param_str = param_str.replace('-', '_') # see: isinstance.__doc__ # parse return value - r = re.search(u'-[>-]* ', doc[end:end + 7]) + r = re.search('-[>-]* ', doc[end:end + 7]) if r is None: - ret = u'' + ret = '' else: index = end + r.end() # get result type, which can contain newlines diff --git a/jedi/inference/context.py b/jedi/inference/context.py index 60034db6..4cbe5ace 100644 --- a/jedi/inference/context.py +++ b/jedi/inference/context.py @@ -164,7 +164,7 @@ class ValueContext(AbstractContext): Should be defined, otherwise the API returns empty types. """ def __init__(self, value): - super(ValueContext, self).__init__(value.inference_state) + super().__init__(value.inference_state) self._value = value @property @@ -323,8 +323,7 @@ class ModuleContext(TreeContextMixin, ValueContext): ), self.get_global_filter(), ) - for f in filters: # Python 2... - yield f + yield from filters def get_global_filter(self): return GlobalNameFilter(self, self.tree_node) @@ -375,7 +374,7 @@ class ClassContext(TreeContextMixin, ValueContext): class CompForContext(TreeContextMixin, AbstractContext): def __init__(self, parent_context, comp_for): - super(CompForContext, self).__init__(parent_context.inference_state) + super().__init__(parent_context.inference_state) self.tree_node = comp_for self.parent_context = parent_context @@ -439,13 +438,12 @@ def get_global_filters(context, until_position, origin_scope): For global name lookups. The filters will handle name resolution themselves, but here we gather possible filters downwards. - >>> from jedi._compatibility import u, no_unicode_pprint >>> from jedi import Script - >>> script = Script(u(''' + >>> script = Script(''' ... x = ['a', 'b', 'c'] ... def func(): ... y = None - ... ''')) + ... ''') >>> module_node = script._module_node >>> scope = next(module_node.iter_funcdefs()) >>> scope @@ -455,7 +453,7 @@ def get_global_filters(context, until_position, origin_scope): First we get the names from the function scope. - >>> no_unicode_pprint(filters[0]) # doctest: +ELLIPSIS + >>> print(filters[0]) # doctest: +ELLIPSIS MergedFilter(, ) >>> sorted(str(n) for n in filters[0].values()) # doctest: +NORMALIZE_WHITESPACE ['', diff --git a/jedi/inference/docstrings.py b/jedi/inference/docstrings.py index ab34cf93..6c1b9a87 100644 --- a/jedi/inference/docstrings.py +++ b/jedi/inference/docstrings.py @@ -21,7 +21,6 @@ from textwrap import dedent from parso import parse, ParserSyntaxError -from jedi._compatibility import u from jedi import debug from jedi.common import indent_block from jedi.inference.cache import inference_state_method_cache @@ -184,7 +183,7 @@ def _strip_rst_role(type_str): def _infer_for_statement_string(module_context, string): - code = dedent(u(""" + code = dedent(""" def pseudo_docstring_stuff(): ''' Create a pseudo function for docstring statements. @@ -192,7 +191,7 @@ def _infer_for_statement_string(module_context, string): is still a function. ''' {} - """)) + """) if string is None: return [] @@ -201,11 +200,8 @@ def _infer_for_statement_string(module_context, string): # (e.g., 'threading' in 'threading.Thread'). string = 'import %s\n' % element + string - # Take the default grammar here, if we load the Python 2.7 grammar here, it - # will be impossible to use `...` (Ellipsis) as a token. Docstring types - # don't need to conform with the current grammar. debug.dbg('Parse docstring code %s', string, color='BLUE') - grammar = module_context.inference_state.latest_grammar + grammar = module_context.inference_state.grammar try: module = grammar.parse(code.format(indent_block(string)), error_recovery=False) except ParserSyntaxError: diff --git a/jedi/inference/filters.py b/jedi/inference/filters.py index 00e594f7..86fbef9d 100644 --- a/jedi/inference/filters.py +++ b/jedi/inference/filters.py @@ -7,7 +7,6 @@ import weakref from parso.tree import search_ancestor -from jedi._compatibility import use_metaclass from jedi.inference import flow_analysis from jedi.inference.base_value import ValueSet, ValueWrapper, \ LazyValueWrapper @@ -109,13 +108,13 @@ class ParserTreeFilter(AbstractUsedNamesFilter): """ if node_context is None: node_context = parent_context - super(ParserTreeFilter, self).__init__(parent_context, node_context.tree_node) + super().__init__(parent_context, node_context.tree_node) self._node_context = node_context self._origin_scope = origin_scope self._until_position = until_position def _filter(self, names): - names = super(ParserTreeFilter, self)._filter(names) + names = super()._filter(names) names = [n for n in names if self._is_name_reachable(n)] return list(self._check_flows(names)) @@ -143,7 +142,7 @@ class ParserTreeFilter(AbstractUsedNamesFilter): class _FunctionExecutionFilter(ParserTreeFilter): def __init__(self, parent_context, function_value, until_position, origin_scope): - super(_FunctionExecutionFilter, self).__init__( + super().__init__( parent_context, until_position=until_position, origin_scope=origin_scope, @@ -167,9 +166,9 @@ class _FunctionExecutionFilter(ParserTreeFilter): class FunctionExecutionFilter(_FunctionExecutionFilter): - def __init__(self, *args, **kwargs): - self._arguments = kwargs.pop('arguments') # Python 2 - super(FunctionExecutionFilter, self).__init__(*args, **kwargs) + def __init__(self, *args, arguments, **kwargs): + super().__init__(*args, **kwargs) + self._arguments = arguments def _convert_param(self, param, name): return ParamName(self._function_value, name, self._arguments) @@ -246,10 +245,10 @@ class MergedFilter(object): class _BuiltinMappedMethod(ValueWrapper): """``Generator.__next__`` ``dict.values`` methods and so on.""" - api_type = u'function' + api_type = 'function' def __init__(self, value, method, builtin_func): - super(_BuiltinMappedMethod, self).__init__(builtin_func) + super().__init__(builtin_func) self._value = value self._method = method @@ -264,14 +263,9 @@ class SpecialMethodFilter(DictFilter): classes like Generator (for __next__, etc). """ class SpecialMethodName(AbstractNameDefinition): - api_type = u'function' - - def __init__(self, parent_context, string_name, value, builtin_value): - callable_, python_version = value - if python_version is not None and \ - python_version != parent_context.inference_state.environment.version_info.major: - raise KeyError + api_type = 'function' + def __init__(self, parent_context, string_name, callable_, builtin_value): self.parent_context = parent_context self.string_name = string_name self._callable = callable_ @@ -293,7 +287,7 @@ class SpecialMethodFilter(DictFilter): ]) def __init__(self, value, dct, builtin_value): - super(SpecialMethodFilter, self).__init__(dct) + super().__init__(dct) self.value = value self._builtin_value = builtin_value """ @@ -309,7 +303,7 @@ class SpecialMethodFilter(DictFilter): class _OverwriteMeta(type): def __init__(cls, name, bases, dct): - super(_OverwriteMeta, cls).__init__(name, bases, dct) + super().__init__(name, bases, dct) base_dct = {} for base_cls in reversed(cls.__bases__): @@ -334,20 +328,20 @@ class _AttributeOverwriteMixin(object): yield filter -class LazyAttributeOverwrite(use_metaclass(_OverwriteMeta, _AttributeOverwriteMixin, - LazyValueWrapper)): +class LazyAttributeOverwrite(_AttributeOverwriteMixin, LazyValueWrapper, + metaclass=_OverwriteMeta): def __init__(self, inference_state): self.inference_state = inference_state -class AttributeOverwrite(use_metaclass(_OverwriteMeta, _AttributeOverwriteMixin, - ValueWrapper)): +class AttributeOverwrite(_AttributeOverwriteMixin, ValueWrapper, + metaclass=_OverwriteMeta): pass -def publish_method(method_name, python_version_match=None): +def publish_method(method_name): def decorator(func): dct = func.__dict__.setdefault('registered_overwritten_methods', {}) - dct[method_name] = func, python_version_match + dct[method_name] = func return func return decorator diff --git a/jedi/inference/gradual/annotation.py b/jedi/inference/gradual/annotation.py index 05416111..fa3aa5ae 100644 --- a/jedi/inference/gradual/annotation.py +++ b/jedi/inference/gradual/annotation.py @@ -6,10 +6,10 @@ as annotations in future python versions. """ import re +from inspect import Parameter from parso import ParserSyntaxError, parse -from jedi._compatibility import force_unicode, Parameter from jedi.inference.cache import inference_state_method_cache from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference.gradual.base import DefineGenericBaseClass, GenericClass @@ -53,7 +53,7 @@ def _infer_annotation_string(context, string, index=None): value_set = context.infer_node(node) if index is not None: value_set = value_set.filter( - lambda value: value.array_type == u'tuple' # noqa + lambda value: value.array_type == 'tuple' # noqa and len(list(value.py__iter__())) >= index ).py__simple_getitem__(index) return value_set @@ -62,7 +62,7 @@ def _infer_annotation_string(context, string, index=None): def _get_forward_reference_node(context, string): try: new_node = context.inference_state.grammar.parse( - force_unicode(string), + string, start_symbol='eval_input', error_recovery=False ) @@ -138,8 +138,7 @@ def _infer_param(function_value, param): """ annotation = param.annotation if annotation is None: - # If no Python 3-style annotation, look for a Python 2-style comment - # annotation. + # If no Python 3-style annotation, look for a comment annotation. # Identify parameters to function in the same sequence as they would # appear in a type comment. all_params = [child for child in param.parent.children @@ -204,7 +203,8 @@ def infer_return_types(function, arguments): all_annotations = py__annotations__(function.tree_node) annotation = all_annotations.get("return", None) if annotation is None: - # If there is no Python 3-type annotation, look for a Python 2-type annotation + # If there is no Python 3-type annotation, look for an annotation + # comment. node = function.tree_node comment = parser_utils.get_following_comment_same_line(node) if comment is None: diff --git a/jedi/inference/gradual/base.py b/jedi/inference/gradual/base.py index a8c06f65..ed0e5e03 100644 --- a/jedi/inference/gradual/base.py +++ b/jedi/inference/gradual/base.py @@ -70,7 +70,7 @@ class _TypeVarFilter(object): class _AnnotatedClassContext(ClassContext): def get_filters(self, *args, **kwargs): - filters = super(_AnnotatedClassContext, self).get_filters( + filters = super().get_filters( *args, **kwargs ) for f in filters: @@ -164,7 +164,7 @@ class GenericClass(DefineGenericBaseClass, ClassMixin): my_foo_int_cls = Foo[int] """ def __init__(self, class_value, generics_manager): - super(GenericClass, self).__init__(generics_manager) + super().__init__(generics_manager) self._class_value = class_value def _get_wrapped_value(self): @@ -186,7 +186,7 @@ class GenericClass(DefineGenericBaseClass, ClassMixin): return _TypeVarFilter(self.get_generics(), self.list_type_vars()) def py__call__(self, arguments): - instance, = super(GenericClass, self).py__call__(arguments) + instance, = super().py__call__(arguments) return ValueSet([_GenericInstanceWrapper(instance)]) def _as_context(self): @@ -201,7 +201,7 @@ class GenericClass(DefineGenericBaseClass, ClassMixin): return GenericClass(self._class_value, generics_manager) def is_sub_class_of(self, class_value): - if super(GenericClass, self).is_sub_class_of(class_value): + if super().is_sub_class_of(class_value): return True return self._class_value.is_sub_class_of(class_value) @@ -230,7 +230,7 @@ class GenericClass(DefineGenericBaseClass, ClassMixin): else: continue - if py_class.api_type != u'class': + if py_class.api_type != 'class': # Functions & modules don't have an MRO and we're not # expecting a Callable (those are handled separately within # TypingClassValueWithIndex). @@ -309,7 +309,7 @@ class _GenericInstanceWrapper(ValueWrapper): except IndexError: pass elif cls.py__name__() == 'Iterator': - return ValueSet([builtin_from_name(self.inference_state, u'None')]) + return ValueSet([builtin_from_name(self.inference_state, 'None')]) return self._wrapped_value.py__stop_iteration_returns() def get_type_hint(self, add_class_info=True): @@ -326,10 +326,10 @@ class _PseudoTreeNameClass(Value): this class. Essentially this class makes it possible to goto that `Tuple` name, without affecting anything else negatively. """ - api_type = u'class' + api_type = 'class' def __init__(self, parent_context, tree_name): - super(_PseudoTreeNameClass, self).__init__( + super().__init__( parent_context.inference_state, parent_context ) @@ -356,7 +356,7 @@ class _PseudoTreeNameClass(Value): def py__class__(self): # This might not be 100% correct, but it is good enough. The details of # the typing library are not really an issue for Jedi. - return builtin_from_name(self.inference_state, u'type') + return builtin_from_name(self.inference_state, 'type') @property def name(self): @@ -388,7 +388,7 @@ class BaseTypingValue(LazyValueWrapper): class BaseTypingClassWithGenerics(DefineGenericBaseClass): def __init__(self, parent_context, tree_name, generics_manager): - super(BaseTypingClassWithGenerics, self).__init__(generics_manager) + super().__init__(generics_manager) self.inference_state = parent_context.inference_state self.parent_context = parent_context self._tree_name = tree_name @@ -423,7 +423,7 @@ class BaseTypingInstance(LazyValueWrapper): return ValueName(self, self._tree_name) def _get_wrapped_value(self): - object_, = builtin_from_name(self.inference_state, u'object').execute_annotation() + object_, = builtin_from_name(self.inference_state, 'object').execute_annotation() return object_ def __repr__(self): diff --git a/jedi/inference/gradual/stub_value.py b/jedi/inference/gradual/stub_value.py index badfff35..e44d2a56 100644 --- a/jedi/inference/gradual/stub_value.py +++ b/jedi/inference/gradual/stub_value.py @@ -10,7 +10,7 @@ class StubModuleValue(ModuleValue): _module_name_class = StubModuleName def __init__(self, non_stub_value_set, *args, **kwargs): - super(StubModuleValue, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.non_stub_value_set = non_stub_value_set def is_stub(self): @@ -30,7 +30,7 @@ class StubModuleValue(ModuleValue): pass else: names.update(method()) - names.update(super(StubModuleValue, self).sub_modules_dict()) + names.update(super().sub_modules_dict()) return names def _get_stub_filters(self, origin_scope): @@ -40,7 +40,7 @@ class StubModuleValue(ModuleValue): )] + list(self.iter_star_filters()) def get_filters(self, origin_scope=None): - filters = super(StubModuleValue, self).get_filters(origin_scope) + filters = super().get_filters(origin_scope) next(filters, None) # Ignore the first filter and replace it with our own stub_filters = self._get_stub_filters(origin_scope=origin_scope) for f in stub_filters: @@ -57,12 +57,12 @@ class StubModuleContext(ModuleContext): def get_filters(self, until_position=None, origin_scope=None): # Make sure to ignore the position, because positions are not relevant # for stubs. - return super(StubModuleContext, self).get_filters(origin_scope=origin_scope) + return super().get_filters(origin_scope=origin_scope) class TypingModuleWrapper(StubModuleValue): def get_filters(self, *args, **kwargs): - filters = super(TypingModuleWrapper, self).get_filters(*args, **kwargs) + filters = super().get_filters(*args, **kwargs) f = next(filters, None) assert f is not None yield TypingModuleFilterWrapper(f) @@ -75,7 +75,7 @@ class TypingModuleWrapper(StubModuleValue): class TypingModuleContext(ModuleContext): def get_filters(self, *args, **kwargs): - filters = super(TypingModuleContext, self).get_filters(*args, **kwargs) + filters = super().get_filters(*args, **kwargs) yield TypingModuleFilterWrapper(next(filters, None)) for f in filters: yield f @@ -85,7 +85,7 @@ class StubFilter(ParserTreeFilter): name_class = StubName def _is_name_reachable(self, name): - if not super(StubFilter, self)._is_name_reachable(name): + if not super()._is_name_reachable(name): return False # Imports in stub files are only public if they have an "as" diff --git a/jedi/inference/gradual/type_var.py b/jedi/inference/gradual/type_var.py index 1af5870e..acc6f46d 100644 --- a/jedi/inference/gradual/type_var.py +++ b/jedi/inference/gradual/type_var.py @@ -1,4 +1,3 @@ -from jedi._compatibility import unicode, force_unicode from jedi import debug from jedi.inference.base_value import ValueSet, NO_VALUES, ValueWrapper from jedi.inference.gradual.base import BaseTypingValue @@ -40,17 +39,14 @@ class TypeVarClass(BaseTypingValue): return None else: safe_value = method(default=None) - if self.inference_state.environment.version_info.major == 2: - if isinstance(safe_value, bytes): - return force_unicode(safe_value) - if isinstance(safe_value, (str, unicode)): + if isinstance(safe_value, str): return safe_value return None class TypeVar(BaseTypingValue): def __init__(self, parent_context, tree_name, var_name, unpacked_args): - super(TypeVar, self).__init__(parent_context, tree_name) + super().__init__(parent_context, tree_name) self._var_name = var_name self._constraints_lazy_values = [] @@ -124,7 +120,7 @@ class TypeVar(BaseTypingValue): class TypeWrapper(ValueWrapper): def __init__(self, wrapped_value, original_value): - super(TypeWrapper, self).__init__(wrapped_value) + super().__init__(wrapped_value) self._original_value = original_value def execute_annotation(self): diff --git a/jedi/inference/gradual/typeshed.py b/jedi/inference/gradual/typeshed.py index 9f8edc55..e186bfc3 100644 --- a/jedi/inference/gradual/typeshed.py +++ b/jedi/inference/gradual/typeshed.py @@ -2,19 +2,20 @@ import os import re from functools import wraps from collections import namedtuple +from pathlib import Path from jedi import settings from jedi.file_io import FileIO -from jedi._compatibility import FileNotFoundError, cast_path +from jedi._compatibility import cast_path from jedi.parser_utils import get_cached_code_lines from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference.gradual.stub_value import TypingModuleWrapper, StubModuleValue from jedi.inference.value import ModuleValue -_jedi_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -TYPESHED_PATH = os.path.join(_jedi_path, 'third_party', 'typeshed') -DJANGO_INIT_PATH = os.path.join(_jedi_path, 'third_party', 'django-stubs', - 'django-stubs', '__init__.pyi') +_jedi_path = Path(__file__).parent.parent.parent +TYPESHED_PATH = _jedi_path.joinpath('third_party', 'typeshed') +DJANGO_INIT_PATH = _jedi_path.joinpath('third_party', 'django-stubs', + 'django-stubs', '__init__.pyi') _IMPORT_MAP = dict( _collections='collections', @@ -38,8 +39,7 @@ def _create_stub_map(directory_path_info): def generate(): try: listed = os.listdir(directory_path_info.path) - except (FileNotFoundError, OSError): - # OSError is Python 2 + except (FileNotFoundError, NotADirectoryError): return for entry in listed: @@ -59,20 +59,19 @@ def _create_stub_map(directory_path_info): def _get_typeshed_directories(version_info): - check_version_list = ['2and3', str(version_info.major)] + check_version_list = ['2and3', '3'] for base in ['stdlib', 'third_party']: - base_path = os.path.join(TYPESHED_PATH, base) + base_path = TYPESHED_PATH.joinpath(base) base_list = os.listdir(base_path) for base_list_entry in base_list: match = re.match(r'(\d+)\.(\d+)$', base_list_entry) if match is not None: - if int(match.group(1)) == version_info.major \ - and int(match.group(2)) <= version_info.minor: + if match.group(1) == '3' and int(match.group(2)) <= version_info.minor: check_version_list.append(base_list_entry) for check_version in check_version_list: is_third_party = base != 'stdlib' - yield PathInfo(os.path.join(base_path, check_version), is_third_party) + yield PathInfo(str(base_path.joinpath(check_version)), is_third_party) _version_cache = {} @@ -111,7 +110,7 @@ def import_module_decorator(func): # ``os``. python_value_set = ValueSet.from_sets( func(inference_state, (n,), None, sys_path,) - for n in [u'posixpath', u'ntpath', u'macpath', u'os2emxpath'] + for n in ['posixpath', 'ntpath', 'macpath', 'os2emxpath'] ) else: python_value_set = ValueSet.from_sets( @@ -183,7 +182,7 @@ def _try_to_load_stub(inference_state, import_names, python_value_set, return _try_to_load_stub_from_file( inference_state, python_value_set, - file_io=FileIO(DJANGO_INIT_PATH), + file_io=FileIO(str(DJANGO_INIT_PATH)), import_names=import_names, ) @@ -198,8 +197,8 @@ def _try_to_load_stub(inference_state, import_names, python_value_set, file_paths = [] if c.is_namespace(): file_paths = [os.path.join(p, '__init__.pyi') for p in c.py__path__()] - elif file_path is not None and file_path.endswith('.py'): - file_paths = [file_path + 'i'] + elif file_path is not None and file_path.suffix == '.py': + file_paths = [str(file_path) + 'i'] for file_path in file_paths: m = _try_to_load_stub_from_file( @@ -274,7 +273,7 @@ def _load_from_typeshed(inference_state, python_value_set, parent_module_value, def _try_to_load_stub_from_file(inference_state, python_value_set, file_io, import_names): try: stub_module_node = parse_stub_module(inference_state, file_io) - except (OSError, IOError): # IOError is Python 2 only + except OSError: # The file that you're looking for doesn't exist (anymore). return None else: diff --git a/jedi/inference/gradual/typing.py b/jedi/inference/gradual/typing.py index e3a95009..e49a7faf 100644 --- a/jedi/inference/gradual/typing.py +++ b/jedi/inference/gradual/typing.py @@ -7,7 +7,6 @@ This file deals with all the typing.py cases. """ import itertools -from jedi._compatibility import unicode from jedi import debug from jedi.inference.compiled import builtin_from_name, create_simple_object from jedi.inference.base_value import ValueSet, NO_VALUES, Value, \ @@ -72,7 +71,7 @@ class TypingModuleName(NameWrapper): elif name == 'TYPE_CHECKING': # This is needed for e.g. imports that are only available for type # checking or are in cycles. The user can then check this variable. - yield builtin_from_name(inference_state, u'True') + yield builtin_from_name(inference_state, 'True') elif name == 'overload': yield OverloadFunction.create_cached( inference_state, self.parent_context, self.tree_name) @@ -89,12 +88,10 @@ class TypingModuleName(NameWrapper): inference_state, self.parent_context, self.tree_name) elif name in ('no_type_check', 'no_type_check_decorator'): # This is not necessary, as long as we are not doing type checking. - for c in self._wrapped_name.infer(): # Fuck my life Python 2 - yield c + yield from self._wrapped_name.infer() else: # Everything else shouldn't be relevant for type checking. - for c in self._wrapped_name.infer(): # Fuck my life Python 2 - yield c + yield from self._wrapped_name.infer() class TypingModuleFilterWrapper(FilterWrapper): @@ -113,7 +110,7 @@ class ProxyWithGenerics(BaseTypingClassWithGenerics): # Optional is basically just saying it's either None or the actual # type. return self.gather_annotation_classes().execute_annotation() \ - | ValueSet([builtin_from_name(self.inference_state, u'None')]) + | ValueSet([builtin_from_name(self.inference_state, 'None')]) elif string_name == 'Type': # The type is actually already given in the index_value return self._generics_manager[0] @@ -156,7 +153,7 @@ class ProxyWithGenerics(BaseTypingClassWithGenerics): # Optional[T] is equivalent to Union[T, None]. In Jedi unions # are represented by members within a ValueSet, so we extract # the T from the Optional[T] by removing the None value. - none = builtin_from_name(self.inference_state, u'None') + none = builtin_from_name(self.inference_state, 'None') return annotation_generics[0].infer_type_vars( value_set.filter(lambda x: x != none), ) @@ -263,8 +260,6 @@ class TypeAlias(LazyValueWrapper): def _get_wrapped_value(self): module_name, class_name = self._actual.split('.') - if self.inference_state.environment.version_info.major == 2 and module_name == 'builtins': - module_name = '__builtin__' # TODO use inference_state.import_module? from jedi.inference.imports import Importer @@ -418,7 +413,7 @@ class NewTypeFunction(BaseTypingValue): class NewType(Value): def __init__(self, inference_state, parent_context, tree_node, type_value_set): - super(NewType, self).__init__(inference_state, parent_context) + super().__init__(inference_state, parent_context) self._type_value_set = type_value_set self.tree_node = tree_node @@ -461,7 +456,7 @@ class TypedDict(LazyValueWrapper): return ValueName(self, self.tree_node.name) def py__simple_getitem__(self, index): - if isinstance(index, unicode): + if isinstance(index, str): return ValueSet.from_sets( name.infer() for filter in self._definition_class.get_filters(is_instance=True) diff --git a/jedi/inference/gradual/utils.py b/jedi/inference/gradual/utils.py index f77b5d77..83162625 100644 --- a/jedi/inference/gradual/utils.py +++ b/jedi/inference/gradual/utils.py @@ -1,4 +1,5 @@ import os +from pathlib import Path from jedi.inference.gradual.typeshed import TYPESHED_PATH, create_stub_module @@ -9,14 +10,18 @@ def load_proper_stub_module(inference_state, file_io, import_names, module_node) module. """ path = file_io.path - assert path.endswith('.pyi') - if path.startswith(TYPESHED_PATH): - # /foo/stdlib/3/os/__init__.pyi -> stdlib/3/os/__init__ - rest = path[len(TYPESHED_PATH) + 1: -4] - split_paths = tuple(rest.split(os.path.sep)) - # Remove the stdlib/3 or third_party/3.5 part - import_names = split_paths[2:] - if import_names[-1] == '__init__': + path = Path(path) + assert path.suffix == '.pyi' + try: + relative_path = path.relative_to(TYPESHED_PATH) + except ValueError: + pass + else: + # /[...]/stdlib/3/os/__init__.pyi -> stdlib/3/os/__init__ + rest = relative_path.with_suffix('') + # Remove the stdlib/3 or third_party/3.6 part + import_names = rest.parts[2:] + if rest.name == '__init__': import_names = import_names[:-1] if import_names is not None: diff --git a/jedi/inference/helpers.py b/jedi/inference/helpers.py index 3e4d3952..0e344c24 100644 --- a/jedi/inference/helpers.py +++ b/jedi/inference/helpers.py @@ -7,18 +7,17 @@ from contextlib import contextmanager from parso.python import tree -from jedi._compatibility import unicode - def is_stdlib_path(path): # Python standard library paths look like this: - # /usr/lib/python3.5/... + # /usr/lib/python3.9/... # TODO The implementation below is probably incorrect and not complete. - if 'dist-packages' in path or 'site-packages' in path: + parts = path.parts + if 'dist-packages' in parts or 'site-packages' in parts: return False base_path = os.path.join(sys.prefix, 'lib', 'python') - return bool(re.match(re.escape(base_path) + r'\d.\d', path)) + return bool(re.match(re.escape(base_path) + r'\d.\d', str(path))) def deep_ast_copy(obj): @@ -122,11 +121,7 @@ def get_names_of_node(node): def is_string(value): - if value.inference_state.environment.version_info.major == 2: - str_classes = (unicode, bytes) - else: - str_classes = (unicode,) - return value.is_compiled() and isinstance(value.get_safe_value(default=None), str_classes) + return value.is_compiled() and isinstance(value.get_safe_value(default=None), str) def is_literal(value): @@ -144,7 +139,7 @@ def get_int_or_none(value): def get_str_or_none(value): - return _get_safe_value_or_none(value, (bytes, unicode)) + return _get_safe_value_or_none(value, str) def is_number(value): diff --git a/jedi/inference/imports.py b/jedi/inference/imports.py index 8eca36ab..8de3fe84 100644 --- a/jedi/inference/imports.py +++ b/jedi/inference/imports.py @@ -5,18 +5,15 @@ not any actual importing done. This module is about finding modules in the filesystem. This can be quite tricky sometimes, because Python imports are not always that simple. -This module uses imp for python up to 3.2 and importlib for python 3.3 on; the -correct implementation is delegated to _compatibility. - This module also supports import autocompletion, which means to complete statements like ``from datetim`` (cursor at the end would return ``datetime``). """ import os +from pathlib import Path from parso.python import tree from parso.tree import search_ancestor -from jedi._compatibility import ImplicitNSInfo, force_unicode, FileNotFoundError from jedi import debug from jedi import settings from jedi.file_io import FolderIO @@ -31,6 +28,7 @@ from jedi.inference.names import ImportName, SubModuleName from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference.gradual.typeshed import import_module_decorator, \ create_stub_module, parse_stub_module +from jedi.inference.compiled.subprocess.functions import ImplicitNSInfo from jedi.plugins import plugin_manager @@ -211,7 +209,7 @@ class Importer(object): # somewhere out of the filesystem. self._infer_possible = False else: - self._fixed_sys_path = [force_unicode(base_directory)] + self._fixed_sys_path = [base_directory] if base_import_path is None: if import_path: @@ -240,7 +238,10 @@ class Importer(object): # inference we want to show the user as much as possible. # See GH #1446. self._inference_state.get_sys_path(add_init_paths=not is_completion) - + sys_path.check_sys_path_modifications(self._module_context) + + [ + str(p) for p + in sys_path.check_sys_path_modifications(self._module_context) + ] ) def follow(self): @@ -332,7 +333,7 @@ def import_module_by_names(inference_state, import_names, sys_path=None, sys_path = inference_state.get_sys_path() str_import_names = tuple( - force_unicode(i.value if isinstance(i, tree.Name) else i) + i.value if isinstance(i, tree.Name) else i for i in import_names ) value_set = [None] @@ -470,19 +471,19 @@ def load_module_from_path(inference_state, file_io, import_names=None, is_packag here to ensure that a random path is still properly loaded into the Jedi module structure. """ - path = file_io.path + path = Path(file_io.path) if import_names is None: e_sys_path = inference_state.get_sys_path() import_names, is_package = sys_path.transform_path_to_dotted(e_sys_path, path) else: assert isinstance(is_package, bool) - is_stub = file_io.path.endswith('.pyi') + is_stub = path.suffix == '.pyi' if is_stub: folder_io = file_io.get_parent_folder() if folder_io.path.endswith('-stubs'): folder_io = FolderIO(folder_io.path[:-6]) - if file_io.path.endswith('__init__.pyi'): + if path.name == '__init__.pyi': python_file_io = folder_io.get_file_io('__init__.py') else: python_file_io = folder_io.get_file_io(import_names[-1] + '.py') @@ -513,7 +514,7 @@ def load_module_from_path(inference_state, file_io, import_names=None, is_packag def load_namespace_from_path(inference_state, folder_io): import_names, is_package = sys_path.transform_path_to_dotted( inference_state.get_sys_path(), - folder_io.path + Path(folder_io.path) ) from jedi.inference.value.namespace import ImplicitNamespaceValue return ImplicitNamespaceValue(inference_state, import_names, [folder_io.path]) diff --git a/jedi/inference/lazy_value.py b/jedi/inference/lazy_value.py index c7c22a91..0ece8690 100644 --- a/jedi/inference/lazy_value.py +++ b/jedi/inference/lazy_value.py @@ -29,7 +29,7 @@ class LazyKnownValues(AbstractLazyValue): class LazyUnknownValue(AbstractLazyValue): def __init__(self, min=1, max=1): - super(LazyUnknownValue, self).__init__(None, min, max) + super().__init__(None, min, max) def infer(self): return NO_VALUES @@ -37,7 +37,7 @@ class LazyUnknownValue(AbstractLazyValue): class LazyTreeValue(AbstractLazyValue): def __init__(self, context, node, min=1, max=1): - super(LazyTreeValue, self).__init__(node, min, max) + super().__init__(node, min, max) self.context = context # We need to save the predefined names. It's an unfortunate side effect # that needs to be tracked otherwise results will be wrong. diff --git a/jedi/inference/names.py b/jedi/inference/names.py index ccf42d4c..81161359 100644 --- a/jedi/inference/names.py +++ b/jedi/inference/names.py @@ -1,8 +1,8 @@ from abc import abstractmethod +from inspect import Parameter from parso.tree import search_ancestor -from jedi._compatibility import Parameter from jedi.parser_utils import find_statement_documentation, clean_scope_docstring from jedi.inference.utils import unite from jedi.inference.base_value import ValueSet, NO_VALUES @@ -123,7 +123,7 @@ class AbstractTreeName(AbstractNameDefinition): else: return None - return super(AbstractTreeName, self).get_qualified_names(include_module_names) + return super().get_qualified_names(include_module_names) def _get_qualified_names(self): parent_names = self.parent_context.get_qualified_names() @@ -242,7 +242,7 @@ class ValueNameMixin(object): def get_root_context(self): if self.parent_context is None: # A module return self._value.as_context() - return super(ValueNameMixin, self).get_root_context() + return super().get_root_context() def get_defining_qualified_value(self): context = self.parent_context @@ -257,7 +257,7 @@ class ValueNameMixin(object): class ValueName(ValueNameMixin, AbstractTreeName): def __init__(self, value, tree_name): - super(ValueName, self).__init__(value.parent_context, tree_name) + super().__init__(value.parent_context, tree_name) self._value = value def goto(self): @@ -372,7 +372,7 @@ class _ParamMixin(object): class ParamNameInterface(_ParamMixin): - api_type = u'param' + api_type = 'param' def get_kind(self): raise NotImplementedError @@ -429,7 +429,7 @@ class BaseTreeParamName(ParamNameInterface, AbstractTreeName): class _ActualTreeParamName(BaseTreeParamName): def __init__(self, function_value, tree_name): - super(_ActualTreeParamName, self).__init__( + super().__init__( function_value.get_default_param_context(), tree_name) self.function_value = function_value @@ -499,11 +499,11 @@ class _ActualTreeParamName(BaseTreeParamName): class AnonymousParamName(_ActualTreeParamName): @plugin_manager.decorate(name='goto_anonymous_param') def goto(self): - return super(AnonymousParamName, self).goto() + return super().goto() @plugin_manager.decorate(name='infer_anonymous_param') def infer(self): - values = super(AnonymousParamName, self).infer() + values = super().infer() if values: return values from jedi.inference.dynamic_params import dynamic_param_lookup @@ -527,11 +527,11 @@ class AnonymousParamName(_ActualTreeParamName): class ParamName(_ActualTreeParamName): def __init__(self, function_value, tree_name, arguments): - super(ParamName, self).__init__(function_value, tree_name) + super().__init__(function_value, tree_name) self.arguments = arguments def infer(self): - values = super(ParamName, self).infer() + values = super().infer() if values: return values @@ -627,7 +627,7 @@ class StubNameMixin(object): names = convert_names(names, prefer_stub_to_compiled=False) if self in names: - return super(StubNameMixin, self).py__doc__() + return super().py__doc__() else: # We have signatures ourselves in stubs, so don't use signatures # from the implementation. @@ -637,7 +637,7 @@ class StubNameMixin(object): # From here on down we make looking up the sys.version_info fast. class StubName(StubNameMixin, TreeNameDefinition): def infer(self): - inferred = super(StubName, self).infer() + inferred = super().infer() if self.string_name == 'version_info' and self.get_root_context().py__name__() == 'sys': from jedi.inference.gradual.stub_value import VersionInfo return ValueSet(VersionInfo(c) for c in inferred) diff --git a/jedi/inference/param.py b/jedi/inference/param.py index c1ce541a..1f296215 100644 --- a/jedi/inference/param.py +++ b/jedi/inference/param.py @@ -1,4 +1,5 @@ from collections import defaultdict +from inspect import Parameter from jedi import debug from jedi.inference.utils import PushBackIterator @@ -6,7 +7,6 @@ from jedi.inference import analysis from jedi.inference.lazy_value import LazyKnownValue, \ LazyTreeValue, LazyUnknownValue from jedi.inference.value import iterable -from jedi._compatibility import Parameter from jedi.inference.names import ParamName @@ -20,8 +20,7 @@ def _add_argument_issue(error_name, lazy_value, message): class ExecutedParamName(ParamName): def __init__(self, function_value, arguments, param_node, lazy_value, is_default=False): - super(ExecutedParamName, self).__init__( - function_value, param_node.name, arguments=arguments) + super().__init__(function_value, param_node.name, arguments=arguments) self._lazy_value = lazy_value self._is_default = is_default diff --git a/jedi/inference/references.py b/jedi/inference/references.py index 4a1321ed..b729c2fd 100644 --- a/jedi/inference/references.py +++ b/jedi/inference/references.py @@ -3,7 +3,6 @@ import re from parso import python_bytes_to_unicode -from jedi._compatibility import FileNotFoundError from jedi.debug import dbg from jedi.file_io import KnownContentFileIO from jedi.inference.imports import SubModuleName, load_module_from_path @@ -273,9 +272,8 @@ def get_module_contexts_containing_name(inference_state, module_contexts, name, return file_io_iterator = _find_python_files_in_sys_path(inference_state, module_contexts) - for x in search_in_file_ios(inference_state, file_io_iterator, name, - limit_reduction=limit_reduction): - yield x # Python 2... + yield from search_in_file_ios(inference_state, file_io_iterator, name, + limit_reduction=limit_reduction) def search_in_file_ios(inference_state, file_io_iterator, name, limit_reduction=1): diff --git a/jedi/inference/signature.py b/jedi/inference/signature.py index 77d3960d..5f203f79 100644 --- a/jedi/inference/signature.py +++ b/jedi/inference/signature.py @@ -1,4 +1,5 @@ -from jedi._compatibility import Parameter +from inspect import Parameter + from jedi.cache import memoize_method from jedi import debug from jedi import parser_utils @@ -67,7 +68,7 @@ class AbstractSignature(_SignatureMixin): class TreeSignature(AbstractSignature): def __init__(self, value, function_value=None, is_bound=False): - super(TreeSignature, self).__init__(value, is_bound) + super().__init__(value, is_bound) self._function_value = function_value or value def bind(self, value): @@ -121,7 +122,7 @@ class TreeSignature(AbstractSignature): class BuiltinSignature(AbstractSignature): def __init__(self, value, return_string, function_value=None, is_bound=False): - super(BuiltinSignature, self).__init__(value, is_bound) + super().__init__(value, is_bound) self._return_string = return_string self.__function_value = function_value diff --git a/jedi/inference/star_args.py b/jedi/inference/star_args.py index 0cda3839..9a1a5163 100644 --- a/jedi/inference/star_args.py +++ b/jedi/inference/star_args.py @@ -10,8 +10,8 @@ This means for example in this case:: The signature here for bar should be `bar(b, c)` instead of bar(*args). """ +from inspect import Parameter -from jedi._compatibility import Parameter from jedi.inference.utils import to_list from jedi.inference.names import ParamNameWrapper from jedi.inference.helpers import is_big_annoying_library @@ -32,8 +32,6 @@ def _iter_nodes_for_param(param_name): argument = name.parent if argument.type == 'argument' \ and argument.children[0] == '*' * param_name.star_count: - # No support for Python 2.7 here, but they are end-of-life - # anyway trailer = search_ancestor(argument, 'trailer') if trailer is not None: # Make sure we're in a function context = execution_context.create_context(trailer) @@ -210,7 +208,7 @@ def process_params(param_names, star_count=3): # default means both * and ** class ParamNameFixedKind(ParamNameWrapper): def __init__(self, param_name, new_kind): - super(ParamNameFixedKind, self).__init__(param_name) + super().__init__(param_name) self._new_kind = new_kind def get_kind(self): diff --git a/jedi/inference/syntax_tree.py b/jedi/inference/syntax_tree.py index 7ad7c895..fb7743cb 100644 --- a/jedi/inference/syntax_tree.py +++ b/jedi/inference/syntax_tree.py @@ -5,7 +5,6 @@ import copy from parso.python import tree -from jedi._compatibility import force_unicode, unicode from jedi import debug from jedi import parser_utils from jedi.inference.base_value import ValueSet, NO_VALUES, ContextualizedNode, \ @@ -225,12 +224,10 @@ def _infer_node(context, element): | context.infer_node(element.children[-1])) elif typ == 'operator': # Must be an ellipsis, other operators are not inferred. - # In Python 2 ellipsis is coded as three single dot tokens, not - # as one token 3 dot token. - if element.value not in ('.', '...'): + if element.value != '...': origin = element.parent raise AssertionError("unhandled operator %s in %s " % (repr(element.value), origin)) - return ValueSet([compiled.builtin_from_name(inference_state, u'Ellipsis')]) + return ValueSet([compiled.builtin_from_name(inference_state, 'Ellipsis')]) elif typ == 'dotted_name': value_set = infer_atom(context, element.children[0]) for next_name in element.children[2::2]: @@ -289,10 +286,6 @@ def infer_atom(context, atom): """ state = context.inference_state if atom.type == 'name': - if atom.value in ('True', 'False', 'None'): - # Python 2... - return ValueSet([compiled.builtin_from_name(state, atom.value)]) - # This is the first global lookup. stmt = tree.search_ancestor( atom, 'expr_stmt', 'lambdef' @@ -312,9 +305,6 @@ def infer_atom(context, atom): # For False/True/None if atom.value in ('False', 'True', 'None'): return ValueSet([compiled.builtin_from_name(state, atom.value)]) - elif atom.value == 'print': - # print e.g. could be inferred like this in Python 2.7 - return NO_VALUES elif atom.value == 'yield': # Contrary to yield from, yield can just appear alone to return a # value when used with `.send()`. @@ -329,7 +319,7 @@ def infer_atom(context, atom): value_set = infer_atom(context, atom.children[0]) for string in atom.children[1:]: right = infer_atom(context, string) - value_set = _infer_comparison(context, value_set, u'+', right) + value_set = _infer_comparison(context, value_set, '+', right) return value_set elif atom.type == 'fstring': return compiled.get_string_value_set(state) @@ -567,7 +557,7 @@ def _is_tuple(value): def _bool_to_value(inference_state, bool_): - return compiled.builtin_from_name(inference_state, force_unicode(str(bool_))) + return compiled.builtin_from_name(inference_state, str(bool_)) def _get_tuple_ints(value): @@ -590,10 +580,10 @@ def _get_tuple_ints(value): def _infer_comparison_part(inference_state, context, left, operator, right): l_is_num = is_number(left) r_is_num = is_number(right) - if isinstance(operator, unicode): + if isinstance(operator, str): str_operator = operator else: - str_operator = force_unicode(str(operator.value)) + str_operator = str(operator.value) if str_operator == '*': # for iterables, ignore * operations @@ -747,7 +737,7 @@ def tree_name_to_values(inference_state, context, tree_name): types = infer_expr_stmt(context, node, tree_name) elif typ == 'with_stmt': value_managers = context.infer_node(node.get_test_node_from_name(tree_name)) - enter_methods = value_managers.py__getattribute__(u'__enter__') + enter_methods = value_managers.py__getattribute__('__enter__') return enter_methods.execute_with_values() elif typ in ('import_from', 'import_name'): types = imports.infer_import(context, tree_name) @@ -861,8 +851,7 @@ def _infer_subscript_list(context, index): return ValueSet([iterable.Slice(context, None, None, None)]) elif index.type == 'subscript' and not index.children[0] == '.': - # subscript basically implies a slice operation, except for Python 2's - # Ellipsis. + # subscript basically implies a slice operation # e.g. array[:3] result = [] for el in index.children: diff --git a/jedi/inference/sys_path.py b/jedi/inference/sys_path.py index 5b82ec15..38d96221 100644 --- a/jedi/inference/sys_path.py +++ b/jedi/inference/sys_path.py @@ -1,11 +1,11 @@ import os import re +from pathlib import Path +from importlib.machinery import all_suffixes -from jedi._compatibility import unicode, force_unicode, all_suffixes from jedi.inference.cache import inference_state_method_cache from jedi.inference.base_value import ContextualizedNode from jedi.inference.helpers import is_string, get_str_or_none -from jedi.common import traverse_parents from jedi.parser_utils import get_cached_code_lines from jedi.file_io import FileIO from jedi import settings @@ -14,8 +14,9 @@ from jedi import debug _BUILDOUT_PATH_INSERTION_LIMIT = 10 -def _abs_path(module_context, path): - if os.path.isabs(path): +def _abs_path(module_context, path: str): + path = Path(path) + if path.is_absolute(): return path module_path = module_context.py__file__() @@ -24,9 +25,8 @@ def _abs_path(module_context, path): # system. return None - base_dir = os.path.dirname(module_path) - path = force_unicode(path) - return os.path.abspath(os.path.join(base_dir, path)) + base_dir = module_path.parent + return base_dir.joinpath(path).absolute() def _paths_from_assignment(module_context, expr_stmt): @@ -148,7 +148,7 @@ def discover_buildout_paths(inference_state, script_path): def _get_paths_from_buildout_script(inference_state, buildout_script_path): - file_io = FileIO(buildout_script_path) + file_io = FileIO(str(buildout_script_path)) try: module_node = inference_state.parse( file_io=file_io, @@ -164,20 +164,20 @@ def _get_paths_from_buildout_script(inference_state, buildout_script_path): inference_state, module_node, file_io=file_io, string_names=None, - code_lines=get_cached_code_lines(inference_state.grammar, buildout_script_path), + code_lines=get_cached_code_lines(inference_state.grammar, str(buildout_script_path)), ).as_context() for path in check_sys_path_modifications(module_context): yield path -def _get_parent_dir_with_file(path, filename): - for parent in traverse_parents(path): - if os.path.isfile(os.path.join(parent, filename)): +def _get_parent_dir_with_file(path: Path, filename): + for parent in path.parents: + if parent.joinpath(filename).is_file(): return parent return None -def _get_buildout_script_paths(search_path): +def _get_buildout_script_paths(search_path: Path): """ if there is a 'buildout.cfg' file in one of the parent directories of the given module it will return a list of all files in the buildout bin @@ -189,13 +189,13 @@ def _get_buildout_script_paths(search_path): project_root = _get_parent_dir_with_file(search_path, 'buildout.cfg') if not project_root: return - bin_path = os.path.join(project_root, 'bin') - if not os.path.exists(bin_path): + bin_path = project_root.joinpath('bin') + if not bin_path.exists(): return for filename in os.listdir(bin_path): try: - filepath = os.path.join(bin_path, filename) + filepath = bin_path.joinpath(filename) with open(filepath, 'r') as f: firstline = f.readline() if firstline.startswith('#!') and 'python' in firstline: @@ -203,14 +203,14 @@ def _get_buildout_script_paths(search_path): except (UnicodeDecodeError, IOError) as e: # Probably a binary file; permission error or race cond. because # file got deleted. Ignore it. - debug.warning(unicode(e)) + debug.warning(e) continue def remove_python_path_suffix(path): for suffix in all_suffixes() + ['.pyi']: - if path.endswith(suffix): - path = path[:-len(suffix)] + if path.suffix == suffix: + path = path.with_name(path.stem) break return path @@ -219,8 +219,7 @@ def transform_path_to_dotted(sys_path, module_path): """ Returns the dotted path inside a sys.path as a list of names. e.g. - >>> from os.path import abspath - >>> transform_path_to_dotted([abspath("/foo")], abspath('/foo/bar/baz.py')) + >>> transform_path_to_dotted([str(Path("/foo").absolute())], Path('/foo/bar/baz.py').absolute()) (('bar', 'baz'), False) Returns (None, False) if the path doesn't really resolve to anything. @@ -228,21 +227,22 @@ def transform_path_to_dotted(sys_path, module_path): """ # First remove the suffix. module_path = remove_python_path_suffix(module_path) + if module_path.name.startswith('.'): + return None, False # Once the suffix was removed we are using the files as we know them. This # means that if someone uses an ending like .vim for a Python file, .vim # will be part of the returned dotted part. - is_package = module_path.endswith(os.path.sep + '__init__') + is_package = module_path.name == '__init__' if is_package: - # -1 to remove the separator - module_path = module_path[:-len('__init__') - 1] + module_path = module_path.parent def iter_potential_solutions(): for p in sys_path: - if module_path.startswith(p): + if str(module_path).startswith(p): # Strip the trailing slash/backslash - rest = module_path[len(p):] + rest = str(module_path)[len(p):] # On Windows a path can also use a slash. if rest.startswith(os.path.sep) or rest.startswith('/'): # Remove a slash in cases it's still there. diff --git a/jedi/inference/utils.py b/jedi/inference/utils.py index 422e17e7..e4c66537 100644 --- a/jedi/inference/utils.py +++ b/jedi/inference/utils.py @@ -1,12 +1,8 @@ """ A universal module with functions / classes without dependencies. """ -import sys -import contextlib import functools import re import os -from jedi._compatibility import reraise - _sep = os.path.sep if os.path.altsep is not None: @@ -36,7 +32,6 @@ class UncaughtAttributeError(Exception): """ Important, because `__getattr__` and `hasattr` catch AttributeErrors implicitly. This is really evil (mainly because of `__getattr__`). - `hasattr` in Python 2 is even more evil, because it catches ALL exceptions. Therefore this class originally had to be derived from `BaseException` instead of `Exception`. But because I removed relevant `hasattr` from the code base, we can now switch back to `Exception`. @@ -65,17 +60,13 @@ def reraise_uncaught(func): difficult. This decorator is to help us getting there by changing `AttributeError` to `UncaughtAttributeError` to avoid unexpected catch. This helps us noticing bugs earlier and facilitates debugging. - - .. note:: Treating StopIteration here is easy. - Add that feature when needed. """ @functools.wraps(func) def wrapper(*args, **kwds): try: return func(*args, **kwds) - except AttributeError: - exc_info = sys.exc_info() - reraise(UncaughtAttributeError(exc_info[1]), exc_info[2]) + except AttributeError as e: + raise UncaughtAttributeError(e) from e return wrapper @@ -91,25 +82,9 @@ class PushBackIterator(object): def __iter__(self): return self - def next(self): - """ Python 2 Compatibility """ - return self.__next__() - def __next__(self): if self.pushes: self.current = self.pushes.pop() else: self.current = next(self.iterator) return self.current - - -@contextlib.contextmanager -def ignored(*exceptions): - """ - Value manager that ignores all of the specified exceptions. This will - be in the standard library starting with Python 3.5. - """ - try: - yield - except exceptions: - pass diff --git a/jedi/inference/value/decorator.py b/jedi/inference/value/decorator.py index 637f08f6..4a4a9b17 100644 --- a/jedi/inference/value/decorator.py +++ b/jedi/inference/value/decorator.py @@ -8,7 +8,7 @@ from jedi.inference.base_value import ValueWrapper, ValueSet class Decoratee(ValueWrapper): def __init__(self, wrapped_value, original_value): - super(Decoratee, self).__init__(wrapped_value) + super().__init__(wrapped_value) self._original_value = original_value def py__doc__(self): diff --git a/jedi/inference/value/dynamic_arrays.py b/jedi/inference/value/dynamic_arrays.py index e0b38f54..7da14952 100644 --- a/jedi/inference/value/dynamic_arrays.py +++ b/jedi/inference/value/dynamic_arrays.py @@ -170,7 +170,7 @@ class _DynamicArrayAdditions(HelperValueMixin): class _Modification(ValueWrapper): def __init__(self, wrapped_value, assigned_values, contextualized_key): - super(_Modification, self).__init__(wrapped_value) + super().__init__(wrapped_value) self._assigned_values = assigned_values self._contextualized_key = contextualized_key diff --git a/jedi/inference/value/function.py b/jedi/inference/value/function.py index 6f4da364..356e91cc 100644 --- a/jedi/inference/value/function.py +++ b/jedi/inference/value/function.py @@ -1,6 +1,5 @@ from parso.python import tree -from jedi._compatibility import use_metaclass from jedi import debug from jedi.inference.cache import inference_state_method_cache, CachedMetaClass from jedi.inference import compiled @@ -26,7 +25,7 @@ from jedi.inference.gradual.generics import TupleGenericManager class LambdaName(AbstractNameDefinition): string_name = '' - api_type = u'function' + api_type = 'function' def __init__(self, lambda_value): self._lambda_value = lambda_value @@ -55,7 +54,7 @@ class FunctionAndClassBase(TreeValue): class FunctionMixin(object): - api_type = u'function' + api_type = 'function' def get_filters(self, origin_scope=None): cls = self.py__class__() @@ -126,7 +125,7 @@ class FunctionMixin(object): return [TreeSignature(f) for f in self.get_signature_functions()] -class FunctionValue(use_metaclass(CachedMetaClass, FunctionMixin, FunctionAndClassBase)): +class FunctionValue(FunctionMixin, FunctionAndClassBase, metaclass=CachedMetaClass): @classmethod def from_context(cls, context, tree_node): def create(tree_node): @@ -161,7 +160,7 @@ class FunctionValue(use_metaclass(CachedMetaClass, FunctionMixin, FunctionAndCla return function def py__class__(self): - c, = values_from_qualified_names(self.inference_state, u'types', u'FunctionType') + c, = values_from_qualified_names(self.inference_state, 'types', 'FunctionType') return c def get_default_param_context(self): @@ -173,7 +172,7 @@ class FunctionValue(use_metaclass(CachedMetaClass, FunctionMixin, FunctionAndCla class FunctionNameInClass(NameWrapper): def __init__(self, class_context, name): - super(FunctionNameInClass, self).__init__(name) + super().__init__(name) self._class_context = class_context def get_defining_qualified_value(self): @@ -182,7 +181,7 @@ class FunctionNameInClass(NameWrapper): class MethodValue(FunctionValue): def __init__(self, inference_state, class_context, *args, **kwargs): - super(MethodValue, self).__init__(inference_state, *args, **kwargs) + super().__init__(inference_state, *args, **kwargs) self.class_context = class_context def get_default_param_context(self): @@ -198,7 +197,7 @@ class MethodValue(FunctionValue): @property def name(self): - return FunctionNameInClass(self.class_context, super(MethodValue, self).name) + return FunctionNameInClass(self.class_context, super().name) class BaseFunctionExecutionContext(ValueContext, TreeContextMixin): @@ -238,7 +237,7 @@ class BaseFunctionExecutionContext(ValueContext, TreeContextMixin): try: children = r.children except AttributeError: - ctx = compiled.builtin_from_name(self.inference_state, u'None') + ctx = compiled.builtin_from_name(self.inference_state, 'None') value_set |= ValueSet([ctx]) else: value_set |= self.infer_node(children[1]) @@ -250,7 +249,7 @@ class BaseFunctionExecutionContext(ValueContext, TreeContextMixin): def _get_yield_lazy_value(self, yield_expr): if yield_expr.type == 'keyword': # `yield` just yields None. - ctx = compiled.builtin_from_name(self.inference_state, u'None') + ctx = compiled.builtin_from_name(self.inference_state, 'None') yield LazyKnownValue(ctx) return @@ -330,8 +329,6 @@ class BaseFunctionExecutionContext(ValueContext, TreeContextMixin): if is_coroutine: if self.is_generator(): - if inference_state.environment.version_info < (3, 6): - return NO_VALUES async_generator_classes = inference_state.typing_module \ .py__getattribute__('AsyncGenerator') @@ -339,13 +336,10 @@ class BaseFunctionExecutionContext(ValueContext, TreeContextMixin): # The contravariant doesn't seem to be defined. generics = (yield_values.py__class__(), NO_VALUES) return ValueSet( - # In Python 3.6 AsyncGenerator is still a class. GenericClass(c, TupleGenericManager(generics)) for c in async_generator_classes ).execute_annotation() else: - if inference_state.environment.version_info < (3, 5): - return NO_VALUES async_classes = inference_state.typing_module.py__getattribute__('Coroutine') return_values = self.get_return_values() # Only the first generic is relevant. @@ -362,7 +356,7 @@ class BaseFunctionExecutionContext(ValueContext, TreeContextMixin): class FunctionExecutionContext(BaseFunctionExecutionContext): def __init__(self, function_value, arguments): - super(FunctionExecutionContext, self).__init__(function_value) + super().__init__(function_value) self._arguments = arguments def get_filters(self, until_position=None, origin_scope=None): @@ -403,7 +397,7 @@ class AnonymousFunctionExecution(BaseFunctionExecutionContext): class OverloadedFunctionValue(FunctionMixin, ValueWrapper): def __init__(self, function, overloaded_functions): - super(OverloadedFunctionValue, self).__init__(function) + super().__init__(function) self._overloaded_functions = overloaded_functions def py__call__(self, arguments): diff --git a/jedi/inference/value/instance.py b/jedi/inference/value/instance.py index dab389d5..45678ec2 100644 --- a/jedi/inference/value/instance.py +++ b/jedi/inference/value/instance.py @@ -25,7 +25,7 @@ from jedi.parser_utils import function_is_staticmethod, function_is_classmethod class InstanceExecutedParamName(ParamName): def __init__(self, instance, function_value, tree_name): - super(InstanceExecutedParamName, self).__init__( + super().__init__( function_value, tree_name, arguments=None) self._instance = instance @@ -38,7 +38,7 @@ class InstanceExecutedParamName(ParamName): class AnonymousMethodExecutionFilter(AnonymousFunctionExecutionFilter): def __init__(self, instance, *args, **kwargs): - super(AnonymousMethodExecutionFilter, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self._instance = instance def _convert_param(self, param, name): @@ -55,12 +55,12 @@ class AnonymousMethodExecutionFilter(AnonymousFunctionExecutionFilter): self._function_value, name ) - return super(AnonymousMethodExecutionFilter, self)._convert_param(param, name) + return super()._convert_param(param, name) class AnonymousMethodExecutionContext(BaseFunctionExecutionContext): def __init__(self, instance, value): - super(AnonymousMethodExecutionContext, self).__init__(value) + super().__init__(value) self.instance = instance def get_filters(self, until_position=None, origin_scope=None): @@ -83,15 +83,15 @@ class AnonymousMethodExecutionContext(BaseFunctionExecutionContext): class MethodExecutionContext(FunctionExecutionContext): def __init__(self, instance, *args, **kwargs): - super(MethodExecutionContext, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.instance = instance class AbstractInstanceValue(Value): - api_type = u'instance' + api_type = 'instance' def __init__(self, inference_state, parent_context, class_value): - super(AbstractInstanceValue, self).__init__(inference_state, parent_context) + super().__init__(inference_state, parent_context) # Generated instances are classes that are just generated by self # (No arguments) used. self.class_value = class_value @@ -141,8 +141,7 @@ class CompiledInstance(AbstractInstanceValue): # This is not really a compiled class, it's just an instance from a # compiled class. def __init__(self, inference_state, parent_context, class_value, arguments): - super(CompiledInstance, self).__init__(inference_state, parent_context, - class_value) + super().__init__(inference_state, parent_context, class_value) self._arguments = arguments def get_filters(self, origin_scope=None, include_self_names=True): @@ -234,14 +233,14 @@ class _BaseTreeInstance(AbstractInstanceValue): # other way around. if is_big_annoying_library(self.parent_context): return NO_VALUES - names = (self.get_function_slot_names(u'__getattr__') - or self.get_function_slot_names(u'__getattribute__')) + names = (self.get_function_slot_names('__getattr__') + or self.get_function_slot_names('__getattribute__')) return self.execute_function_slots(names, name) def py__getitem__(self, index_value_set, contextualized_node): - names = self.get_function_slot_names(u'__getitem__') + names = self.get_function_slot_names('__getitem__') if not names: - return super(_BaseTreeInstance, self).py__getitem__( + return super().py__getitem__( index_value_set, contextualized_node, ) @@ -250,9 +249,9 @@ class _BaseTreeInstance(AbstractInstanceValue): return ValueSet.from_sets(name.infer().execute(args) for name in names) def py__iter__(self, contextualized_node=None): - iter_slot_names = self.get_function_slot_names(u'__iter__') + iter_slot_names = self.get_function_slot_names('__iter__') if not iter_slot_names: - return super(_BaseTreeInstance, self).py__iter__(contextualized_node) + return super().py__iter__(contextualized_node) def iterate(): for generator in self.execute_function_slots(iter_slot_names): @@ -261,11 +260,7 @@ class _BaseTreeInstance(AbstractInstanceValue): return iterate() def py__next__(self, contextualized_node=None): - # `__next__` logic. - if self.inference_state.environment.version_info.major == 2: - name = u'next' - else: - name = u'__next__' + name = u'__next__' next_slot_names = self.get_function_slot_names(name) if next_slot_names: yield LazyKnownValues( @@ -275,10 +270,10 @@ class _BaseTreeInstance(AbstractInstanceValue): debug.warning('Instance has no __next__ function in %s.', self) def py__call__(self, arguments): - names = self.get_function_slot_names(u'__call__') + names = self.get_function_slot_names('__call__') if not names: # Means the Instance is not callable. - return super(_BaseTreeInstance, self).py__call__(arguments) + return super().py__call__(arguments) return ValueSet.from_sets(name.infer().execute(arguments) for name in names) @@ -293,10 +288,10 @@ class _BaseTreeInstance(AbstractInstanceValue): if result is not NotImplemented: return result - names = self.get_function_slot_names(u'__get__') + names = self.get_function_slot_names('__get__') if names: if instance is None: - instance = compiled.builtin_from_name(self.inference_state, u'None') + instance = compiled.builtin_from_name(self.inference_state, 'None') return self.execute_function_slots(names, instance, class_value) else: return ValueSet([self]) @@ -322,7 +317,7 @@ class TreeInstance(_BaseTreeInstance): if settings.dynamic_array_additions: arguments = get_dynamic_array_instance(self, arguments) - super(TreeInstance, self).__init__(inference_state, parent_context, class_value) + super().__init__(inference_state, parent_context, class_value) self._arguments = arguments self.tree_node = class_value.tree_node @@ -396,7 +391,7 @@ class TreeInstance(_BaseTreeInstance): else: if key == index: return lazy_context.infer() - return super(TreeInstance, self).py__simple_getitem__(index) + return super().py__simple_getitem__(index) def __repr__(self): return "<%s of %s(%s)>" % (self.__class__.__name__, self.class_value, @@ -411,7 +406,7 @@ class CompiledInstanceName(compiled.CompiledName): def __init__(self, inference_state, instance, klass, name): parent_value = klass.parent_context.get_value() assert parent_value is not None, "How? Please reproduce and report" - super(CompiledInstanceName, self).__init__( + super().__init__( inference_state, parent_value, name.string_name @@ -449,7 +444,7 @@ class CompiledInstanceClassFilter(AbstractFilter): class BoundMethod(FunctionMixin, ValueWrapper): def __init__(self, instance, class_context, function): - super(BoundMethod, self).__init__(function) + super().__init__(function) self.instance = instance self._class_context = class_context @@ -460,11 +455,11 @@ class BoundMethod(FunctionMixin, ValueWrapper): def name(self): return FunctionNameInClass( self._class_context, - super(BoundMethod, self).name + super().name ) def py__class__(self): - c, = values_from_qualified_names(self.inference_state, u'types', u'MethodType') + c, = values_from_qualified_names(self.inference_state, 'types', 'MethodType') return c def _get_arguments(self, arguments): @@ -492,7 +487,7 @@ class BoundMethod(FunctionMixin, ValueWrapper): ] def get_signatures(self): - return [sig.bind(self) for sig in super(BoundMethod, self).get_signatures()] + return [sig.bind(self) for sig in super().get_signatures()] def __repr__(self): return '<%s: %s>' % (self.__class__.__name__, self._wrapped_value) @@ -525,7 +520,7 @@ class SelfName(TreeNameDefinition): class LazyInstanceClassName(NameWrapper): def __init__(self, instance, class_member_name): - super(LazyInstanceClassName, self).__init__(class_member_name) + super().__init__(class_member_name) self._instance = instance @iterator_to_value_set @@ -572,7 +567,7 @@ class SelfAttributeFilter(ClassFilter): This class basically filters all the use cases where `self.*` was assigned. """ def __init__(self, instance, instance_class, node_context, origin_scope): - super(SelfAttributeFilter, self).__init__( + super().__init__( class_value=instance_class, node_context=node_context, origin_scope=origin_scope, @@ -616,7 +611,7 @@ class SelfAttributeFilter(ClassFilter): class InstanceArguments(TreeArgumentsWrapper): def __init__(self, instance, arguments): - super(InstanceArguments, self).__init__(arguments) + super().__init__(arguments) self.instance = instance def unpack(self, func=None): diff --git a/jedi/inference/value/iterable.py b/jedi/inference/value/iterable.py index d2411a80..46c2b8ba 100644 --- a/jedi/inference/value/iterable.py +++ b/jedi/inference/value/iterable.py @@ -2,9 +2,6 @@ Contains all classes and functions to deal with lists, dicts, generators and iterators in general. """ -import sys - -from jedi._compatibility import force_unicode, is_py3 from jedi.inference import compiled from jedi.inference import analysis from jedi.inference.lazy_value import LazyKnownValue, LazyKnownValues, \ @@ -27,7 +24,7 @@ class IterableMixin(object): return self.py__iter__(contextualized_node) def py__stop_iteration_returns(self): - return ValueSet([compiled.builtin_from_name(self.inference_state, u'None')]) + return ValueSet([compiled.builtin_from_name(self.inference_state, 'None')]) # At the moment, safe values are simple values like "foo", 1 and not # lists/dicts. Therefore as a small speed optimization we can just do the @@ -35,14 +32,7 @@ class IterableMixin(object): # doing this in the end as well. # This mostly speeds up patterns like `sys.version_info >= (3, 0)` in # typeshed. - if sys.version_info[0] == 2: - # Python 2........... - def get_safe_value(self, default=sentinel): - if default is sentinel: - raise ValueError("There exists no safe value for value %s" % self) - return default - else: - get_safe_value = Value.get_safe_value + get_safe_value = Value.get_safe_value class GeneratorBase(LazyAttributeOverwrite, IterableMixin): @@ -64,13 +54,12 @@ class GeneratorBase(LazyAttributeOverwrite, IterableMixin): return ValueSet([self]) @publish_method('send') - @publish_method('next', python_version_match=2) - @publish_method('__next__', python_version_match=3) + @publish_method('__next__') def _next(self, arguments): return ValueSet.from_sets(lazy_value.infer() for lazy_value in self.py__iter__()) def py__stop_iteration_returns(self): - return ValueSet([compiled.builtin_from_name(self.inference_state, u'None')]) + return ValueSet([compiled.builtin_from_name(self.inference_state, 'None')]) @property def name(self): @@ -86,7 +75,7 @@ class GeneratorBase(LazyAttributeOverwrite, IterableMixin): class Generator(GeneratorBase): """Handling of `yield` functions.""" def __init__(self, inference_state, func_execution_context): - super(Generator, self).__init__(inference_state) + super().__init__(inference_state) self._func_execution_context = func_execution_context def py__iter__(self, contextualized_node=None): @@ -194,7 +183,7 @@ class _DictMixin(object): class Sequence(LazyAttributeOverwrite, IterableMixin): - api_type = u'instance' + api_type = 'instance' @property def name(self): @@ -233,14 +222,14 @@ class Sequence(LazyAttributeOverwrite, IterableMixin): class _BaseComprehension(ComprehensionMixin): def __init__(self, inference_state, defining_context, sync_comp_for_node, entry_node): assert sync_comp_for_node.type == 'sync_comp_for' - super(_BaseComprehension, self).__init__(inference_state) + super().__init__(inference_state) self._defining_context = defining_context self._sync_comp_for_node = sync_comp_for_node self._entry_node = entry_node class ListComprehension(_BaseComprehension, Sequence): - array_type = u'list' + array_type = 'list' def py__simple_getitem__(self, index): if isinstance(index, slice): @@ -253,7 +242,7 @@ class ListComprehension(_BaseComprehension, Sequence): class SetComprehension(_BaseComprehension, Sequence): - array_type = u'set' + array_type = 'set' class GeneratorComprehension(_BaseComprehension, GeneratorBase): @@ -271,11 +260,11 @@ class _DictKeyMixin(object): class DictComprehension(ComprehensionMixin, Sequence, _DictKeyMixin): - array_type = u'dict' + array_type = 'dict' def __init__(self, inference_state, defining_context, sync_comp_for_node, key_node, value_node): assert sync_comp_for_node.type == 'sync_comp_for' - super(DictComprehension, self).__init__(inference_state) + super().__init__(inference_state) self._defining_context = defining_context self._sync_comp_for_node = sync_comp_for_node self._entry_node = key_node @@ -328,25 +317,25 @@ class DictComprehension(ComprehensionMixin, Sequence, _DictKeyMixin): class SequenceLiteralValue(Sequence): _TUPLE_LIKE = 'testlist_star_expr', 'testlist', 'subscriptlist' - mapping = {'(': u'tuple', - '[': u'list', - '{': u'set'} + mapping = {'(': 'tuple', + '[': 'list', + '{': 'set'} def __init__(self, inference_state, defining_context, atom): - super(SequenceLiteralValue, self).__init__(inference_state) + super().__init__(inference_state) self.atom = atom self._defining_context = defining_context if self.atom.type in self._TUPLE_LIKE: - self.array_type = u'tuple' + self.array_type = 'tuple' else: self.array_type = SequenceLiteralValue.mapping[atom.children[0]] """The builtin name of the array (list, set, tuple or dict).""" def _get_generics(self): - if self.array_type == u'tuple': + if self.array_type == 'tuple': return tuple(x.infer().py__class__() for x in self.py__iter__()) - return super(SequenceLiteralValue, self)._get_generics() + return super()._get_generics() def py__simple_getitem__(self, index): """Here the index is an int/str. Raises IndexError/KeyError.""" @@ -436,10 +425,12 @@ class SequenceLiteralValue(Sequence): class DictLiteralValue(_DictMixin, SequenceLiteralValue, _DictKeyMixin): - array_type = u'dict' + array_type = 'dict' def __init__(self, inference_state, defining_context, atom): - super(SequenceLiteralValue, self).__init__(inference_state) + # Intentionally don't call the super class. This is definitely a sign + # that the architecture is bad and we should refactor. + Sequence.__init__(self, inference_state) self._defining_context = defining_context self.atom = atom @@ -448,7 +439,7 @@ class DictLiteralValue(_DictMixin, SequenceLiteralValue, _DictKeyMixin): compiled_value_index = compiled.create_simple_object(self.inference_state, index) for key, value in self.get_tree_entries(): for k in self._defining_context.infer_node(key): - for key_v in k.execute_operation(compiled_value_index, u'=='): + for key_v in k.execute_operation(compiled_value_index, '=='): if key_v.get_safe_value(): return self._defining_context.infer_node(value) raise SimpleGetItemNotFound('No key found in dictionary %s.' % self) @@ -502,7 +493,7 @@ class _FakeSequence(Sequence): """ type should be one of "tuple", "list" """ - super(_FakeSequence, self).__init__(inference_state) + super().__init__(inference_state) self._lazy_value_list = lazy_value_list def py__simple_getitem__(self, index): @@ -524,18 +515,18 @@ class _FakeSequence(Sequence): class FakeTuple(_FakeSequence): - array_type = u'tuple' + array_type = 'tuple' class FakeList(_FakeSequence): - array_type = u'tuple' + array_type = 'tuple' class FakeDict(_DictMixin, Sequence, _DictKeyMixin): - array_type = u'dict' + array_type = 'dict' def __init__(self, inference_state, dct): - super(FakeDict, self).__init__(inference_state) + super().__init__(inference_state) self._dct = dct def py__iter__(self, contextualized_node=None): @@ -543,21 +534,6 @@ class FakeDict(_DictMixin, Sequence, _DictKeyMixin): yield LazyKnownValue(compiled.create_simple_object(self.inference_state, key)) def py__simple_getitem__(self, index): - if is_py3 and self.inference_state.environment.version_info.major == 2: - # In Python 2 bytes and unicode compare. - if isinstance(index, bytes): - index_unicode = force_unicode(index) - try: - return self._dct[index_unicode].infer() - except KeyError: - pass - elif isinstance(index, str): - index_bytes = index.encode('utf-8') - try: - return self._dct[index_bytes].infer() - except KeyError: - pass - with reraise_getitem_errors(KeyError, TypeError): lazy_value = self._dct[index] return lazy_value.infer() @@ -584,7 +560,7 @@ class FakeDict(_DictMixin, Sequence, _DictKeyMixin): class MergedArray(Sequence): def __init__(self, inference_state, arrays): - super(MergedArray, self).__init__(inference_state) + super().__init__(inference_state) self.array_type = arrays[-1].array_type self._arrays = arrays diff --git a/jedi/inference/value/klass.py b/jedi/inference/value/klass.py index 33510445..c277a508 100644 --- a/jedi/inference/value/klass.py +++ b/jedi/inference/value/klass.py @@ -37,7 +37,6 @@ py__doc__() Returns the docstring for a value. """ from jedi import debug -from jedi._compatibility import use_metaclass from jedi.parser_utils import get_cached_parent_scope, expr_is_dotted from jedi.inference.cache import inference_state_method_cache, CachedMetaClass, \ inference_state_method_generator_cache @@ -56,7 +55,7 @@ from jedi.plugins import plugin_manager class ClassName(TreeNameDefinition): def __init__(self, class_value, tree_name, name_context, apply_decorators): - super(ClassName, self).__init__(name_context, tree_name) + super().__init__(name_context, tree_name) self._apply_decorators = apply_decorators self._class_value = class_value @@ -78,7 +77,7 @@ class ClassName(TreeNameDefinition): class ClassFilter(ParserTreeFilter): def __init__(self, class_value, node_context=None, until_position=None, origin_scope=None, is_instance=False): - super(ClassFilter, self).__init__( + super().__init__( class_value.as_context(), node_context, until_position=until_position, origin_scope=origin_scope, @@ -125,7 +124,7 @@ class ClassFilter(ParserTreeFilter): or self._equals_origin_scope() def _filter(self, names): - names = super(ClassFilter, self)._filter(names) + names = super()._filter(names) return [name for name in names if self._access_possible(name)] @@ -145,7 +144,7 @@ class ClassMixin(object): return ValueSet([TreeInstance(self.inference_state, self.parent_context, self, arguments)]) def py__class__(self): - return compiled.builtin_from_name(self.inference_state, u'type') + return compiled.builtin_from_name(self.inference_state, 'type') @property def name(self): @@ -192,13 +191,11 @@ class ClassMixin(object): if include_metaclasses: metaclasses = self.get_metaclasses() if metaclasses: - for f in self.get_metaclass_filters(metaclasses, is_instance): - yield f # Python 2.. + yield from self.get_metaclass_filters(metaclasses, is_instance) for cls in self.py__mro__(): if cls.is_compiled(): - for filter in cls.get_filters(is_instance=is_instance): - yield filter + yield from cls.get_filters(is_instance=is_instance) else: yield ClassFilter( self, node_context=cls.as_context(), @@ -207,7 +204,7 @@ class ClassMixin(object): ) if not is_instance and include_type_when_class: from jedi.inference.compiled import builtin_from_name - type_ = builtin_from_name(self.inference_state, u'type') + type_ = builtin_from_name(self.inference_state, 'type') assert isinstance(type_, ClassValue) if type_ != self: # We are not using execute_with_values here, because the @@ -321,8 +318,8 @@ class ClassMixin(object): return ValueSet({self}) -class ClassValue(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase)): - api_type = u'class' +class ClassValue(ClassMixin, FunctionAndClassBase, metaclass=CachedMetaClass): + api_type = 'class' @inference_state_method_cache() def list_type_vars(self): diff --git a/jedi/inference/value/module.py b/jedi/inference/value/module.py index 12d2bfcf..2091d88b 100644 --- a/jedi/inference/value/module.py +++ b/jedi/inference/value/module.py @@ -1,4 +1,5 @@ import os +from pathlib import Path from jedi.inference.cache import inference_state_method_cache from jedi.inference.names import AbstractNameDefinition, ModuleName @@ -16,7 +17,7 @@ class _ModuleAttributeName(AbstractNameDefinition): """ For module attributes like __file__, __str__ and so on. """ - api_type = u'instance' + api_type = 'instance' def __init__(self, parent_module, string_name, string_value=None): self.parent_context = parent_module @@ -26,9 +27,6 @@ class _ModuleAttributeName(AbstractNameDefinition): def infer(self): if self._string_value is not None: s = self._string_value - if self.parent_context.inference_state.environment.version_info.major == 2 \ - and not isinstance(s, bytes): - s = s.encode('utf-8') return ValueSet([ create_simple_object(self.parent_context.inference_state, s) ]) @@ -73,7 +71,7 @@ class ModuleMixin(SubModuleDictMixin): yield star_filter def py__class__(self): - c, = values_from_qualified_names(self.inference_state, u'types', u'ModuleType') + c, = values_from_qualified_names(self.inference_state, 'types', 'ModuleType') return c def is_module(self): @@ -92,9 +90,9 @@ class ModuleMixin(SubModuleDictMixin): names = ['__package__', '__doc__', '__name__'] # All the additional module attributes are strings. dct = dict((n, _ModuleAttributeName(self, n)) for n in names) - file = self.py__file__() - if file is not None: - dct['__file__'] = _ModuleAttributeName(self, '__file__', file) + path = self.py__file__() + if path is not None: + dct['__file__'] = _ModuleAttributeName(self, '__file__', str(path)) return dct def iter_star_filters(self): @@ -137,11 +135,11 @@ class ModuleMixin(SubModuleDictMixin): class ModuleValue(ModuleMixin, TreeValue): - api_type = u'module' + api_type = 'module' def __init__(self, inference_state, module_node, code_lines, file_io=None, string_names=None, is_package=False): - super(ModuleValue, self).__init__( + super().__init__( inference_state, parent_context=None, tree_node=module_node @@ -150,32 +148,32 @@ class ModuleValue(ModuleMixin, TreeValue): if file_io is None: self._path = None else: - self._path = file_io.path + self._path = Path(file_io.path) self.string_names = string_names # Optional[Tuple[str, ...]] self.code_lines = code_lines self._is_package = is_package def is_stub(self): - if self._path is not None and self._path.endswith('.pyi'): + if self._path is not None and self._path.suffix == '.pyi': # Currently this is the way how we identify stubs when e.g. goto is # used in them. This could be changed if stubs would be identified # sooner and used as StubModuleValue. return True - return super(ModuleValue, self).is_stub() + return super().is_stub() def py__name__(self): if self.string_names is None: return None return '.'.join(self.string_names) - def py__file__(self): + def py__file__(self) -> Path: """ In contrast to Python's __file__ can be None. """ if self._path is None: return None - return os.path.abspath(self._path) + return self._path.absolute() def is_package(self): return self._is_package diff --git a/jedi/inference/value/namespace.py b/jedi/inference/value/namespace.py index 48a09e4a..0ea9ecf4 100644 --- a/jedi/inference/value/namespace.py +++ b/jedi/inference/value/namespace.py @@ -23,11 +23,11 @@ class ImplicitNamespaceValue(Value, SubModuleDictMixin): # Is a module like every other module, because if you import an empty # folder foobar it will be available as an object: # . - api_type = u'module' + api_type = 'module' parent_context = None def __init__(self, inference_state, string_names, paths): - super(ImplicitNamespaceValue, self).__init__(inference_state, parent_context=None) + super().__init__(inference_state, parent_context=None) self.inference_state = inference_state self.string_names = string_names self._paths = paths diff --git a/jedi/parser_utils.py b/jedi/parser_utils.py index ff412e13..dd86178b 100644 --- a/jedi/parser_utils.py +++ b/jedi/parser_utils.py @@ -1,5 +1,6 @@ import re import textwrap +from ast import literal_eval from inspect import cleandoc from weakref import WeakKeyDictionary @@ -7,8 +8,6 @@ from parso.python import tree from parso.cache import parser_cache from parso import split_lines -from jedi._compatibility import literal_eval, force_unicode - _EXECUTE_NODES = {'funcdef', 'classdef', 'import_from', 'import_name', 'test', 'or_test', 'and_test', 'not_test', 'comparison', 'expr', 'xor_expr', 'and_expr', 'shift_expr', 'arith_expr', @@ -102,10 +101,7 @@ def clean_scope_docstring(scope_node): # leaves anymore that might be part of the docstring. A # docstring can also look like this: ``'foo' 'bar' # Returns a literal cleaned version of the ``Token``. - cleaned = cleandoc(safe_literal_eval(node.value)) - # Since we want the docstr output to be always unicode, just - # force it. - return force_unicode(cleaned) + return cleandoc(safe_literal_eval(node.value)) return '' @@ -117,10 +113,7 @@ def find_statement_documentation(tree_node): if maybe_string.type == 'simple_stmt': maybe_string = maybe_string.children[0] if maybe_string.type == 'string': - cleaned = cleandoc(safe_literal_eval(maybe_string.value)) - # Since we want the docstr output to be always unicode, just - # force it. - return force_unicode(cleaned) + return cleandoc(safe_literal_eval(maybe_string.value)) return '' @@ -131,15 +124,7 @@ def safe_literal_eval(value): # manually, but that's right now not implemented. return '' - try: - return literal_eval(value) - except SyntaxError: - # It's possible to create syntax errors with literals like rb'' in - # Python 2. This should not be possible and in that case just return an - # empty string. - # Before Python 3.3 there was a more strict definition in which order - # you could define literals. - return '' + return literal_eval(value) def get_signature(funcdef, width=72, call_string=None, diff --git a/jedi/plugins/django.py b/jedi/plugins/django.py index 88a3bfae..cd443bbd 100644 --- a/jedi/plugins/django.py +++ b/jedi/plugins/django.py @@ -1,7 +1,8 @@ """ Module is used to infer Django model fields. """ -from jedi._compatibility import Parameter +from inspect import Parameter + from jedi import debug from jedi.inference.cache import inference_state_function_cache from jedi.inference.base_value import ValueSet, iterator_to_value_set, ValueWrapper @@ -113,7 +114,7 @@ def _infer_field(cls, field_name, is_instance): class DjangoModelName(NameWrapper): def __init__(self, cls, name, is_instance): - super(DjangoModelName, self).__init__(name) + super().__init__(name) self._cls = cls self._is_instance = is_instance @@ -257,7 +258,7 @@ class GenericFieldWrapper(AttributeOverwrite, ClassMixin): class DjangoModelSignature(AbstractSignature): def __init__(self, value, field_names): - super(DjangoModelSignature, self).__init__(value) + super().__init__(value) self._field_names = field_names def get_param_names(self, resolve_stars=False): @@ -266,7 +267,7 @@ class DjangoModelSignature(AbstractSignature): class DjangoParamName(BaseTreeParamName): def __init__(self, field_name): - super(DjangoParamName, self).__init__(field_name.parent_context, field_name.tree_name) + super().__init__(field_name.parent_context, field_name.tree_name) self._field_name = field_name def get_kind(self): @@ -278,7 +279,7 @@ class DjangoParamName(BaseTreeParamName): class QuerySetMethodWrapper(ValueWrapper): def __init__(self, method, model_cls): - super(QuerySetMethodWrapper, self).__init__(method) + super().__init__(method) self._model_cls = model_cls def py__get__(self, instance, class_value): @@ -288,7 +289,7 @@ class QuerySetMethodWrapper(ValueWrapper): class QuerySetBoundMethodWrapper(ValueWrapper): def __init__(self, method, model_cls): - super(QuerySetBoundMethodWrapper, self).__init__(method) + super().__init__(method) self._model_cls = model_cls def get_signatures(self): diff --git a/jedi/plugins/flask.py b/jedi/plugins/flask.py index 693c3ae7..8d67b839 100644 --- a/jedi/plugins/flask.py +++ b/jedi/plugins/flask.py @@ -6,14 +6,14 @@ def import_module(callback): def wrapper(inference_state, import_names, module_context, *args, **kwargs): if len(import_names) == 3 and import_names[:2] == ('flask', 'ext'): # New style. - ipath = (u'flask_' + import_names[2]), + ipath = ('flask_' + import_names[2]), value_set = callback(inference_state, ipath, None, *args, **kwargs) if value_set: return value_set - value_set = callback(inference_state, (u'flaskext',), None, *args, **kwargs) + value_set = callback(inference_state, ('flaskext',), None, *args, **kwargs) return callback( inference_state, - (u'flaskext', import_names[2]), + ('flaskext', import_names[2]), next(iter(value_set)), *args, **kwargs ) diff --git a/jedi/plugins/pytest.py b/jedi/plugins/pytest.py index f9b04284..432385e3 100644 --- a/jedi/plugins/pytest.py +++ b/jedi/plugins/pytest.py @@ -1,5 +1,6 @@ +from pathlib import Path + from parso.python.tree import search_ancestor -from jedi._compatibility import FileNotFoundError from jedi.inference.cache import inference_state_method_cache from jedi.inference.imports import load_module_from_path from jedi.inference.filters import ParserTreeFilter @@ -129,7 +130,7 @@ def _iter_pytest_modules(module_context, skip_own_module=False): sys_path = module_context.inference_state.get_sys_path() while any(folder.path.startswith(p) for p in sys_path): file_io = folder.get_file_io('conftest.py') - if file_io.path != module_context.py__file__(): + if Path(file_io.path) != module_context.py__file__(): try: m = load_module_from_path(module_context.inference_state, file_io) yield m.as_context() @@ -144,7 +145,7 @@ def _iter_pytest_modules(module_context, skip_own_module=False): class FixtureFilter(ParserTreeFilter): def _filter(self, names): - for name in super(FixtureFilter, self)._filter(names): + for name in super()._filter(names): funcdef = name.parent if funcdef.type == 'funcdef': # Class fixtures are not supported diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index f24cadc9..37ed12b2 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -11,8 +11,8 @@ compiled module that returns the types for C-builtins. """ import parso import os +from inspect import Parameter -from jedi._compatibility import force_unicode, Parameter from jedi import debug from jedi.inference.utils import safe_property from jedi.inference.helpers import get_str_or_none @@ -182,14 +182,9 @@ def argument_clinic(clinic_string, want_value=False, want_context=False, @argument_clinic('iterator[, default], /', want_inference_state=True) def builtins_next(iterators, defaults, inference_state): - if inference_state.environment.version_info.major == 2: - name = 'next' - else: - name = '__next__' - # TODO theoretically we have to check here if something is an iterator. # That is probably done by checking if it's not a class. - return defaults | iterators.py__getattribute__(name).execute_with_values() + return defaults | iterators.py__getattribute__('__next__').execute_with_values() @argument_clinic('iterator[, default], /') @@ -208,7 +203,7 @@ def builtins_getattr(objects, names, defaults=None): debug.warning('getattr called without str') continue else: - return value.py__getattribute__(force_unicode(string)) + return value.py__getattribute__(string) return NO_VALUES @@ -259,14 +254,13 @@ def builtins_super(types, objects, context): class ReversedObject(AttributeOverwrite): def __init__(self, reversed_obj, iter_list): - super(ReversedObject, self).__init__(reversed_obj) + super().__init__(reversed_obj) self._iter_list = iter_list def py__iter__(self, contextualized_node): return self._iter_list - @publish_method('next', python_version_match=2) - @publish_method('__next__', python_version_match=3) + @publish_method('__next__') def _next(self, arguments): return ValueSet.from_sets( lazy_value.infer() for lazy_value in self._iter_list @@ -329,7 +323,7 @@ def builtins_isinstance(objects, types, arguments, inference_state): analysis.add(lazy_value.context, 'type-error-isinstance', node, message) return ValueSet( - compiled.builtin_from_name(inference_state, force_unicode(str(b))) + compiled.builtin_from_name(inference_state, str(b)) for b in bool_results ) @@ -346,7 +340,7 @@ def builtins_staticmethod(functions): class ClassMethodObject(ValueWrapper): def __init__(self, class_method_obj, function): - super(ClassMethodObject, self).__init__(class_method_obj) + super().__init__(class_method_obj) self._function = function def py__get__(self, instance, class_value): @@ -358,7 +352,7 @@ class ClassMethodObject(ValueWrapper): class ClassMethodGet(ValueWrapper): def __init__(self, get_method, klass, function): - super(ClassMethodGet, self).__init__(get_method) + super().__init__(get_method) self._class = klass self._function = function @@ -371,7 +365,7 @@ class ClassMethodGet(ValueWrapper): class ClassMethodArguments(TreeArgumentsWrapper): def __init__(self, klass, arguments): - super(ClassMethodArguments, self).__init__(arguments) + super().__init__(arguments) self._class = klass def unpack(self, func=None): @@ -391,7 +385,7 @@ def builtins_classmethod(functions, value, arguments): class PropertyObject(AttributeOverwrite, ValueWrapper): def __init__(self, property_obj, function): - super(PropertyObject, self).__init__(property_obj) + super().__init__(property_obj) self._function = function def py__get__(self, instance, class_value): @@ -426,11 +420,11 @@ def collections_namedtuple(value, arguments, callback): inference_state = value.inference_state # Process arguments - name = u'jedi_unknown_namedtuple' + name = 'jedi_unknown_namedtuple' for c in _follow_param(inference_state, arguments, 0): x = get_str_or_none(c) if x is not None: - name = force_unicode(x) + name = x break # TODO here we only use one of the types, we should use all. @@ -440,10 +434,10 @@ def collections_namedtuple(value, arguments, callback): _fields = list(param_values)[0] string = get_str_or_none(_fields) if string is not None: - fields = force_unicode(string).replace(',', ' ').split() + fields = string.replace(',', ' ').split() elif isinstance(_fields, iterable.Sequence): fields = [ - force_unicode(get_str_or_none(v)) + get_str_or_none(v) for lazy_value in _fields.py__iter__() for v in lazy_value.infer() ] @@ -456,7 +450,7 @@ def collections_namedtuple(value, arguments, callback): typename=name, field_names=tuple(fields), num_fields=len(fields), - arg_list=repr(tuple(fields)).replace("u'", "").replace("'", "")[1:-1], + arg_list=repr(tuple(fields)).replace("'", "")[1:-1], repr_fmt='', field_defs='\n'.join(_NAMEDTUPLE_FIELD_TEMPLATE.format(index=index, name=name) for index, name in enumerate(fields)) @@ -475,7 +469,7 @@ def collections_namedtuple(value, arguments, callback): class PartialObject(ValueWrapper): def __init__(self, actual_value, arguments, instance=None): - super(PartialObject, self).__init__(actual_value) + super().__init__(actual_value) self._arguments = arguments self._instance = instance @@ -538,7 +532,7 @@ class PartialMethodObject(PartialObject): class PartialSignature(SignatureWrapper): def __init__(self, wrapped_signature, skipped_arg_count, skipped_arg_set): - super(PartialSignature, self).__init__(wrapped_signature) + super().__init__(wrapped_signature) self._skipped_arg_count = skipped_arg_count self._skipped_arg_set = skipped_arg_set @@ -631,7 +625,7 @@ class DataclassWrapper(ValueWrapper, ClassMixin): class DataclassSignature(AbstractSignature): def __init__(self, value, param_names): - super(DataclassSignature, self).__init__(value) + super().__init__(value) self._param_names = param_names def get_param_names(self, resolve_stars=False): @@ -640,7 +634,7 @@ class DataclassSignature(AbstractSignature): class DataclassParamName(BaseTreeParamName): def __init__(self, parent_context, tree_name, annotation_node, default_node): - super(DataclassParamName, self).__init__(parent_context, tree_name) + super().__init__(parent_context, tree_name) self.annotation_node = annotation_node self.default_node = default_node @@ -656,7 +650,7 @@ class DataclassParamName(BaseTreeParamName): class ItemGetterCallable(ValueWrapper): def __init__(self, instance, args_value_set): - super(ItemGetterCallable, self).__init__(instance) + super().__init__(instance) self._args_value_set = args_value_set @repack_with_argument_clinic('item, /') @@ -694,7 +688,7 @@ class WrapsCallable(ValueWrapper): class Wrapped(ValueWrapper, FunctionMixin): def __init__(self, func, original_function): - super(Wrapped, self).__init__(func) + super().__init__(func) self._original_function = original_function @property @@ -732,7 +726,7 @@ def _create_string_input_function(func): @argument_clinic('*args, /', want_callback=True) def _os_path_join(args_set, callback): if len(args_set) == 1: - string = u'' + string = '' sequence, = args_set is_first = True for lazy_value in sequence.py__iter__(): @@ -744,7 +738,7 @@ def _os_path_join(args_set, callback): break if not is_first: string += os.path.sep - string += force_unicode(s) + string += s is_first = False else: return ValueSet([compiled.create_simple_object(sequence.inference_state, string)]) diff --git a/jedi/utils.py b/jedi/utils.py index 9d675174..631cf473 100644 --- a/jedi/utils.py +++ b/jedi/utils.py @@ -2,7 +2,6 @@ Utilities for end-users. """ -from __future__ import absolute_import import __main__ from collections import namedtuple import logging diff --git a/scripts/diff_parser_profile.py b/scripts/diff_parser_profile.py index a152a3ed..93a12029 100755 --- a/scripts/diff_parser_profile.py +++ b/scripts/diff_parser_profile.py @@ -18,7 +18,6 @@ from docopt import docopt from jedi.parser.python import load_grammar from jedi.parser.diff import DiffParser from jedi.parser.python import ParserWithRecovery -from jedi._compatibility import u from jedi.common import splitlines import jedi @@ -37,14 +36,15 @@ def main(args): with open(args['']) as f: code = f.read() grammar = load_grammar() - parser = ParserWithRecovery(grammar, u(code)) + parser = ParserWithRecovery(grammar, code) # Make sure used_names is loaded parser.module.used_names - code = code + '\na\n' # Add something so the diff parser needs to run. + code = code + '\na\n' # Add something so the diff parser needs to run. lines = splitlines(code, keepends=True) cProfile.runctx('run(parser, lines)', globals(), locals(), sort=args['-s']) + if __name__ == '__main__': args = docopt(__doc__) main(args) diff --git a/scripts/profile_output.py b/scripts/profile_output.py index 3497ce6b..53e0046c 100755 --- a/scripts/profile_output.py +++ b/scripts/profile_output.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3.6 -# -*- coding: utf-8 -*- """ Profile a piece of Python code with ``profile``. Tries a completion on a certain piece of code. @@ -19,11 +18,7 @@ Options: """ import time -try: - # For Python 2 - import cProfile as profile -except ImportError: - import profile +import profile import pstats from docopt import docopt diff --git a/setup.py b/setup.py index 131d0473..044d7d5a 100755 --- a/setup.py +++ b/setup.py @@ -33,12 +33,11 @@ setup(name='jedi', keywords='python completion refactoring vim', long_description=readme, packages=find_packages(exclude=['test', 'test.*']), - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', + python_requires='>=3.6', install_requires=install_requires, extras_require={ 'testing': [ - # Pytest 5 doesn't support Python 2 anymore. - 'pytest>=3.9.0,<5.0.0', + 'pytest<6.0.0', # docopt for sith doctests 'docopt', # coloroma for colored debug output @@ -58,10 +57,7 @@ setup(name='jedi', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', diff --git a/sith.py b/sith.py index dcdc39e3..3b286894 100755 --- a/sith.py +++ b/sith.py @@ -44,7 +44,6 @@ Options: --pudb Launch pudb when error is raised. """ -from __future__ import print_function, division, unicode_literals from docopt import docopt import json diff --git a/test/completion/arrays.py b/test/completion/arrays.py index 3d2e108f..545082d6 100644 --- a/test/completion/arrays.py +++ b/test/completion/arrays.py @@ -272,9 +272,6 @@ dic = {str(key): ''} #? str() dic[''] -# Just skip Python 2 tests from here. EoL soon, I'm too lazy for it. -# python > 2.7 - for x in {1: 3.0, '': 1j}: #? int() str() @@ -473,7 +470,6 @@ def test_func(): #? int() tuple({1})[0] -# python > 2.7 # ----------------- # PEP 3132 Extended Iterable Unpacking (star unpacking) # ----------------- diff --git a/test/completion/async_.py b/test/completion/async_.py index e77a290e..2b6ad699 100644 --- a/test/completion/async_.py +++ b/test/completion/async_.py @@ -5,8 +5,6 @@ Currently we're not supporting completion of them, but they should at least not raise errors or return extremely strange results. """ -# python >= 3.5 - async def x(): return 1 diff --git a/test/completion/basic.py b/test/completion/basic.py index b0e71bad..97bfeecd 100644 --- a/test/completion/basic.py +++ b/test/completion/basic.py @@ -291,14 +291,6 @@ except ImportError as i_a: i_a #? ImportError() i_a -try: - import math -except ImportError, i_b: - # TODO check this only in Python2 - ##? ['i_b'] - i_b - ##? ImportError() - i_b class MyException(Exception): @@ -344,8 +336,6 @@ def foo(my_t=some_defa #? ['some_default'] def foo(my_t=some_defa, my_t2=some_defa -# python > 2.7 - #? ['my_type'] def foo(my_t: lala=some_defa, my_t2: my_typ #? ['my_type'] diff --git a/test/completion/classes.py b/test/completion/classes.py index 9d5c7c3d..9f1468b9 100644 --- a/test/completion/classes.py +++ b/test/completion/classes.py @@ -382,7 +382,6 @@ getattr(getattr, 1) getattr(str, []) -# python >= 3.5 class Base(): def ret(self, b): return b diff --git a/test/completion/comprehensions.py b/test/completion/comprehensions.py index 55f2b0c3..e0d4054c 100644 --- a/test/completion/comprehensions.py +++ b/test/completion/comprehensions.py @@ -174,8 +174,6 @@ class X(): #? int() X([1]).foo() -# set/dict comprehensions were introduced in 2.7, therefore: -# python >= 2.7 # ----------------- # dict comprehensions # ----------------- diff --git a/test/completion/dynamic_arrays.py b/test/completion/dynamic_arrays.py index 36715a98..61cc3839 100644 --- a/test/completion/dynamic_arrays.py +++ b/test/completion/dynamic_arrays.py @@ -388,7 +388,6 @@ k = 'a' #? int() some_dct[k] -# python > 3.5 some_other_dct = dict(some_dct, c=set) #? int() some_other_dct['a'] diff --git a/test/completion/generators.py b/test/completion/generators.py index 570ce309..798398cf 100644 --- a/test/completion/generators.py +++ b/test/completion/generators.py @@ -242,8 +242,6 @@ def x(): # yield from # ----------------- -# python > 2.7 - def yield_from(): yield from iter([1]) diff --git a/test/completion/named_param.py b/test/completion/named_param.py index 811c97e8..de8073e9 100644 --- a/test/completion/named_param.py +++ b/test/completion/named_param.py @@ -97,7 +97,6 @@ def x(): pass # ----------------- # Only keyword arguments are valid # ----------------- -# python >= 3.5 def x(bam, *, bar, baz): pass diff --git a/test/completion/pep0484_basic.py b/test/completion/pep0484_basic.py index a20475ca..54ec8770 100644 --- a/test/completion/pep0484_basic.py +++ b/test/completion/pep0484_basic.py @@ -1,7 +1,5 @@ """ Pep-0484 type hinting """ -# python > 2.7 - class A(): pass diff --git a/test/completion/pep0484_generic_mismatches.py b/test/completion/pep0484_generic_mismatches.py index c848dd10..6f765c99 100644 --- a/test/completion/pep0484_generic_mismatches.py +++ b/test/completion/pep0484_generic_mismatches.py @@ -1,4 +1,3 @@ -# python >= 3.4 import typing from typing import ( Callable, diff --git a/test/completion/pep0484_generic_parameters.py b/test/completion/pep0484_generic_parameters.py index cbbc4537..0be4e9cb 100644 --- a/test/completion/pep0484_generic_parameters.py +++ b/test/completion/pep0484_generic_parameters.py @@ -1,4 +1,3 @@ -# python >= 3.4 from typing import ( Callable, Dict, diff --git a/test/completion/pep0484_generic_passthroughs.py b/test/completion/pep0484_generic_passthroughs.py index 63692051..49b133a8 100644 --- a/test/completion/pep0484_generic_passthroughs.py +++ b/test/completion/pep0484_generic_passthroughs.py @@ -1,4 +1,3 @@ -# python >= 3.4 from typing import ( Any, Callable, diff --git a/test/completion/pep0484_typing.py b/test/completion/pep0484_typing.py index 1fab1ad5..dae4b9b3 100644 --- a/test/completion/pep0484_typing.py +++ b/test/completion/pep0484_typing.py @@ -1,7 +1,5 @@ """ -Test the typing library, with docstrings. This is needed since annotations -are not supported in python 2.7 else then annotating by comment (and this is -still TODO at 2016-01-23) +Test the typing library, with docstrings and annotations """ import typing class B: @@ -295,8 +293,6 @@ y = type(PlainInt) #? type.mro y.mro -# python > 2.7 - class TestDefaultDict(typing.DefaultDict[str, int]): def setdud(self): pass @@ -323,7 +319,6 @@ for key in x.keys(): for value in x.values(): #? int() value -# python > 2.7 """ diff --git a/test/completion/precedence.py b/test/completion/precedence.py index 25d4663f..5d8da12d 100644 --- a/test/completion/precedence.py +++ b/test/completion/precedence.py @@ -178,8 +178,6 @@ from datetime import datetime, timedelta # magic methods # ----------------- -# python >= 3.5 - class C: def __sub__(self, other) -> int: ... def __radd__(self, other) -> float: ... diff --git a/test/completion/pytest.py b/test/completion/pytest.py index c4030918..f49fd921 100644 --- a/test/completion/pytest.py +++ b/test/completion/pytest.py @@ -1,4 +1,3 @@ -# python > 2.7 import pytest from pytest import fixture @@ -130,8 +129,6 @@ def test_p(monkeypatch): #? ['setattr'] monkeypatch.setatt -# python > 2.7 - #? ['capsysbinary'] def test_p(capsysbin diff --git a/test/completion/stdlib.py b/test/completion/stdlib.py index 48ab1ee4..94eb5328 100644 --- a/test/completion/stdlib.py +++ b/test/completion/stdlib.py @@ -133,48 +133,6 @@ weakref.ref(1) #? int() None weakref.ref(1)() -# ----------------- -# functools -# ----------------- -import functools - -basetwo = functools.partial(int, base=2) -#? int() -basetwo() - -def function(a, b): - return a, b -a = functools.partial(function, 0) - -#? int() -a('')[0] -#? str() -a('')[1] - -kw = functools.partial(function, b=1.0) -tup = kw(1) -#? int() -tup[0] -#? float() -tup[1] - -def my_decorator(f): - @functools.wraps(f) - def wrapper(*args, **kwds): - return f(*args, **kwds) - return wrapper - -@my_decorator -def example(a): - return a - -#? str() -example('') - -# From GH #1574 -#? float() -functools.wraps(functools.partial(str, 1))(lambda: 1.0)() - # ----------------- # sqlite3 (#84) # ----------------- @@ -253,7 +211,7 @@ z.read('name').upper # ----------------- # contextlib # ----------------- -# python > 2.7 + from typing import Iterator import contextlib with contextlib.closing('asd') as string: @@ -384,7 +342,6 @@ class Test(metaclass=Meta): # Enum # ----------------- -# python > 2.7 import enum class X(enum.Enum): @@ -409,8 +366,47 @@ X().name X().attr_x.attr_y.value # ----------------- -# functools Python 3.5+ +# functools # ----------------- +import functools + +basetwo = functools.partial(int, base=2) +#? int() +basetwo() + +def function(a, b): + return a, b +a = functools.partial(function, 0) + +#? int() +a('')[0] +#? str() +a('')[1] + +kw = functools.partial(function, b=1.0) +tup = kw(1) +#? int() +tup[0] +#? float() +tup[1] + +def my_decorator(f): + @functools.wraps(f) + def wrapper(*args, **kwds): + return f(*args, **kwds) + return wrapper + +@my_decorator +def example(a): + return a + +#? str() +example('') + +# From GH #1574 +#? float() +functools.wraps(functools.partial(str, 1))(lambda: 1.0)() + class X: def function(self, a, b): return a, b @@ -463,11 +459,6 @@ X().just_partial('')[0] #? str() X().just_partial('')[1] - -# ----------------- -# functools Python 3.8 -# ----------------- - # python >= 3.8 @functools.lru_cache diff --git a/test/completion/stubs.py b/test/completion/stubs.py index 4e24eef1..da952372 100644 --- a/test/completion/stubs.py +++ b/test/completion/stubs.py @@ -1,4 +1,3 @@ -# python > 2.7 from stub_folder import with_stub, stub_only, with_stub_folder, stub_only_folder # ------------------------- @@ -35,6 +34,7 @@ from stub_folder.with_stub import in_ #? ['with_stub', 'stub_only', 'with_stub_folder', 'stub_only_folder'] from stub_folder. + # ------------------------- # Folders # ------------------------- diff --git a/test/completion/types.py b/test/completion/types.py index 753af6c3..e67be4e1 100644 --- a/test/completion/types.py +++ b/test/completion/types.py @@ -135,7 +135,6 @@ set_t2.c # ----------------- # pep 448 unpacking generalizations # ----------------- -# python >= 3.5 d = {'a': 3} dc = {v: 3 for v in ['a']} diff --git a/test/completion/usages.py b/test/completion/usages.py index 312765b2..9ed9ae61 100644 --- a/test/completion/usages.py +++ b/test/completion/usages.py @@ -354,7 +354,6 @@ class DefinitelyNotGlobal: # stubs # ----------------- -# python > 2.7 from stub_folder import with_stub #< ('stub:stub_folder.with_stub', 5, 4), ('stub_folder.with_stub', 5, 4), (0, 10) with_stub.stub_function diff --git a/test/conftest.py b/test/conftest.py index 26254000..a0b2ea14 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -102,9 +102,7 @@ def collect_static_analysis_tests(base_dir, test_files): @pytest.fixture(scope='session') def venv_path(tmpdir_factory, environment): - if environment.version_info.major < 3: - pytest.skip("python -m venv does not exist in Python 2") - elif isinstance(environment, InterpreterEnvironment): + if isinstance(environment, InterpreterEnvironment): # The environment can be a tox virtualenv environment which we don't # want, so use the system environment. environment = get_system_environment( diff --git a/test/examples/absolute_import/local_module.py b/test/examples/absolute_import/local_module.py deleted file mode 100644 index aa4bf007..00000000 --- a/test/examples/absolute_import/local_module.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -This is a module that imports the *standard library* unittest, -despite there being a local "unittest" module. It specifies that it -wants the stdlib one with the ``absolute_import`` __future__ import. - -The twisted equivalent of this module is ``twisted.trial._synctest``. -""" -from __future__ import absolute_import - -import unittest - - -class Assertions(unittest.TestCase): - pass diff --git a/test/examples/absolute_import/unittest.py b/test/examples/absolute_import/unittest.py deleted file mode 100644 index eee1e937..00000000 --- a/test/examples/absolute_import/unittest.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -This is a module that shadows a builtin (intentionally). - -It imports a local module, which in turn imports stdlib unittest (the -name shadowed by this module). If that is properly resolved, there's -no problem. However, if jedi doesn't understand absolute_imports, it -will get this module again, causing infinite recursion. -""" -from local_module import Assertions - - -class TestCase(Assertions): - def test(self): - self.assertT diff --git a/test/helpers.py b/test/helpers.py index f2782829..b11f4941 100644 --- a/test/helpers.py +++ b/test/helpers.py @@ -3,25 +3,18 @@ A helper module for testing, improves compatibility for testing (as ``jedi._compatibility``) as well as introducing helper functions. """ -import sys from contextlib import contextmanager -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest -TestCase = unittest.TestCase - import os import pytest -from os.path import abspath, dirname, join from functools import partial, wraps from jedi import Project +from pathlib import Path -test_dir = dirname(abspath(__file__)) +test_dir = Path(__file__).absolute().parent test_dir_project = Project(test_dir) -root_dir = dirname(test_dir) -example_dir = join(test_dir, 'examples') +root_dir = test_dir.parent +example_dir = test_dir.joinpath('examples') sample_int = 1 # This is used in completion/imports.py @@ -32,7 +25,7 @@ skip_if_not_windows = partial(pytest.param, def get_example_dir(*names): - return join(example_dir, *names) + return example_dir.joinpath(*names) def cwd_at(path): @@ -53,10 +46,10 @@ def cwd_at(path): @contextmanager def set_cwd(path, absolute_path=False): - repo_root = os.path.dirname(test_dir) + repo_root = test_dir.parent - oldcwd = os.getcwd() - os.chdir(os.path.join(repo_root, path)) + oldcwd = Path.cwd() + os.chdir(repo_root.joinpath(path)) try: yield finally: diff --git a/test/refactor.py b/test/refactor.py index 2a997b19..582beeb6 100644 --- a/test/refactor.py +++ b/test/refactor.py @@ -4,11 +4,9 @@ Refactoring tests work a little bit similar to integration tests. But the idea is here to compare two versions of code. If you want to add a new test case, just look at the existing ones in the ``test/refactor`` folder and copy them. """ -from __future__ import with_statement import os import platform import re -import sys from parso import split_lines @@ -90,8 +88,6 @@ def _collect_file_tests(code, path, lines_to_execute): def collect_dir_tests(base_dir, test_files): - if sys.version_info[0] == 2: - return for f_name in os.listdir(base_dir): files_to_execute = [a for a in test_files.items() if a[0] in f_name] lines_to_execute = reduce(lambda x, y: x + y[1], files_to_execute, []) diff --git a/test/run.py b/test/run.py index 731e9b4a..76b248eb 100755 --- a/test/run.py +++ b/test/run.py @@ -114,7 +114,6 @@ import pytest import jedi from jedi import debug -from jedi._compatibility import unicode, is_py3 from jedi.api.classes import Name from jedi.api.completion import get_user_context from jedi import parser_utils @@ -165,7 +164,7 @@ class BaseTestCase(object): class IntegrationTestCase(BaseTestCase): def __init__(self, test_type, correct, line_nr, column, start, line, path=None, skip_version_info=None): - super(IntegrationTestCase, self).__init__(skip_version_info) + super().__init__(skip_version_info) self.test_type = test_type self.correct = correct self.line_nr = line_nr @@ -295,7 +294,7 @@ class StaticAnalysisCase(BaseTestCase): for line in self._source.splitlines(): skip_version_info = skip_python_version(line) or skip_version_info - super(StaticAnalysisCase, self).__init__(skip_version_info) + super().__init__(skip_version_info) def collect_comparison(self): cases = [] @@ -401,12 +400,8 @@ def collect_dir_tests(base_dir, test_files, check_thirdparty=False): path = os.path.join(base_dir, f_name) - if is_py3: - with open(path, encoding='utf-8', newline='') as f: - source = f.read() - else: - with open(path) as f: - source = unicode(f.read(), 'UTF-8') + with open(path, newline='') as f: + source = f.read() for case in collect_file_tests(path, StringIO(source), lines_to_execute): @@ -431,7 +426,7 @@ Options: --pdb Enable pdb debugging on fail. -d, --debug Enable text output debugging (please install ``colorama``). --thirdparty Also run thirdparty tests (in ``completion/thirdparty``). - --env A Python version, like 2.7, 3.8, etc. + --env A Python version, like 3.9, 3.8, etc. """ if __name__ == '__main__': import docopt diff --git a/test/static_analysis/python2.py b/test/static_analysis/python2.py deleted file mode 100644 index 4d896e3e..00000000 --- a/test/static_analysis/python2.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -Some special cases of Python 2. -""" -# python <= 2.7 - -# print is syntax: -print 1 -print(1) - -#! 6 name-error -print NOT_DEFINED diff --git a/test/test_api/test_analysis.py b/test/test_api/test_analysis.py index 9723d2eb..64f9c22e 100644 --- a/test/test_api/test_analysis.py +++ b/test/test_api/test_analysis.py @@ -1,10 +1,4 @@ -import sys - -import pytest - - -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") -def test_issue436(Script, skip_python2): +def test_issue436(Script): code = "bar = 0\nbar += 'foo' + 4" errors = set(repr(e) for e in Script(code)._analysis()) assert len(errors) == 2 diff --git a/test/test_api/test_api.py b/test/test_api/test_api.py index fcb9cf9e..c86be78c 100644 --- a/test/test_api/test_api.py +++ b/test/test_api/test_api.py @@ -3,20 +3,17 @@ Test all things related to the ``jedi.api`` module. """ import os -import sys from textwrap import dedent import pytest from pytest import raises from parso import cache -from jedi._compatibility import unicode from jedi import preload_module from jedi.inference.gradual import typeshed from test.helpers import test_dir, get_example_dir -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, EoL") def test_preload_modules(): def check_loaded(*modules): for grammar_cache in cache.parser_cache.values(): @@ -25,7 +22,7 @@ def test_preload_modules(): # Filter the typeshed parser cache. typeshed_cache_count = sum( 1 for path in grammar_cache - if path is not None and path.startswith(typeshed.TYPESHED_PATH) + if path is not None and str(path).startswith(str(typeshed.TYPESHED_PATH)) ) # +1 for None module (currently used) assert len(grammar_cache) - typeshed_cache_count == len(modules) + 1 @@ -119,8 +116,8 @@ def test_completion_on_complex_literals(Script): # However this has been disabled again, because it apparently annoyed # users. So no completion after j without a space :) assert not Script('4j').complete() - assert ({c.name for c in Script('4j ').complete()} == - {'if', 'and', 'in', 'is', 'not', 'or'}) + assert ({c.name for c in Script('4j ').complete()} + == {'if', 'and', 'in', 'is', 'not', 'or'}) def test_goto_non_name(Script, environment): @@ -205,11 +202,11 @@ def test_goto_follow_imports(Script): import inspect inspect.isfunction""") definition, = Script(code).goto(column=0, follow_imports=True) - assert 'inspect.py' in definition.module_path + assert definition.module_path.name == 'inspect.py' assert (definition.line, definition.column) == (1, 0) definition, = Script(code).goto(follow_imports=True) - assert 'inspect.py' in definition.module_path + assert definition.module_path.name == 'inspect.py' assert (definition.line, definition.column) > (1, 0) code = '''def param(p): pass\nparam(1)''' @@ -240,11 +237,11 @@ def test_goto_module(Script): assert module.module_path == expected base_path = get_example_dir('simple_import') - path = os.path.join(base_path, '__init__.py') + path = base_path.joinpath('__init__.py') - check(1, os.path.join(base_path, 'module.py')) - check(1, os.path.join(base_path, 'module.py'), follow_imports=True) - check(5, os.path.join(base_path, 'module2.py')) + check(1, base_path.joinpath('module.py')) + check(1, base_path.joinpath('module.py'), follow_imports=True) + check(5, base_path.joinpath('module2.py')) def test_goto_definition_cursor(Script): @@ -320,7 +317,7 @@ def test_goto_follow_builtin_imports(Script): def test_docstrings_for_completions(Script): for c in Script('').complete(): - assert isinstance(c.docstring(), (str, unicode)) + assert isinstance(c.docstring(), str) def test_fuzzy_completion(Script): @@ -331,9 +328,7 @@ def test_fuzzy_completion(Script): def test_math_fuzzy_completion(Script, environment): script = Script('import math\nmath.og') - expected = ['copysign', 'log', 'log10', 'log1p'] - if environment.version_info.major >= 3: - expected.append('log2') + expected = ['copysign', 'log', 'log10', 'log1p', 'log2'] completions = script.complete(fuzzy=True) assert expected == [comp.name for comp in completions] for c in completions: diff --git a/test/test_api/test_api_classes_follow_definition.py b/test/test_api/test_api_classes_follow_definition.py index 4bf3f254..96108141 100644 --- a/test/test_api/test_api_classes_follow_definition.py +++ b/test/test_api/test_api_classes_follow_definition.py @@ -36,10 +36,7 @@ def test_follow_import_incomplete(Script, environment): # incomplete `from * import` part datetime = check_follow_definition_types(Script, "from datetime import datetim") - if environment.version_info.major == 2: - assert datetime == ['class'] - else: - assert set(datetime) == {'class', 'instance'} # py3: builtin and pure py version + assert set(datetime) == {'class', 'instance'} # py3: builtin and pure py version # os.path check ospath = check_follow_definition_types(Script, "from os.path import abspat") assert set(ospath) == {'function'} diff --git a/test/test_api/test_call_signatures.py b/test/test_api/test_call_signatures.py index d880dc4c..59e3a640 100644 --- a/test/test_api/test_call_signatures.py +++ b/test/test_api/test_call_signatures.py @@ -1,10 +1,9 @@ -import sys from textwrap import dedent import inspect +from unittest import TestCase import pytest -from ..helpers import TestCase from jedi import cache from jedi.parser_utils import get_signature from jedi import Interpreter @@ -126,7 +125,7 @@ def test_get_signatures_whitespace(Script): abs( def x(): pass - """) + """) # noqa assert_signature(Script, s, 'abs', 0, line=1, column=5) @@ -240,13 +239,12 @@ def test_complex(Script, environment): func1, = sig1._name.infer() func2, = sig2._name.infer() - if environment.version_info.major == 3: - # Do these checks just for Python 3, I'm too lazy to deal with this - # legacy stuff. ~ dave. - assert get_signature(func1.tree_node) \ - == 'compile(pattern: AnyStr, flags: _FlagsType = ...) -> Pattern[AnyStr]' - assert get_signature(func2.tree_node) \ - == 'compile(pattern: Pattern[AnyStr], flags: _FlagsType = ...) ->\nPattern[AnyStr]' + # Do these checks just for Python 3, I'm too lazy to deal with this + # legacy stuff. ~ dave. + assert get_signature(func1.tree_node) \ + == 'compile(pattern: AnyStr, flags: _FlagsType = ...) -> Pattern[AnyStr]' + assert get_signature(func2.tree_node) \ + == 'compile(pattern: Pattern[AnyStr], flags: _FlagsType = ...) ->\nPattern[AnyStr]' # jedi-vim #70 s = """def foo(""" @@ -374,10 +372,8 @@ def test_keyword_argument_index(Script, environment): def get(source, column=None): return Script(source).get_signatures(column=column)[0] - # The signature of sorted changed from 2 to 3. - py2_offset = int(environment.version_info.major == 2) - assert get('sorted([], key=a').index == 1 + py2_offset - assert get('sorted([], key=').index == 1 + py2_offset + assert get('sorted([], key=a').index == 1 + assert get('sorted([], key=').index == 1 assert get('sorted([], no_key=a').index is None kw_func = 'def foo(a, b): pass\nfoo(b=3, a=4)' @@ -504,7 +500,7 @@ _calls = [ @pytest.mark.parametrize('ending', ['', ')']) @pytest.mark.parametrize('code, call, expected_index', _calls) -def test_signature_index(skip_python2, Script, environment, code, call, expected_index, ending): +def test_signature_index(Script, environment, code, call, expected_index, ending): if isinstance(expected_index, tuple): expected_index = expected_index[environment.version_info > (3, 8)] if environment.version_info < (3, 8): @@ -515,7 +511,6 @@ def test_signature_index(skip_python2, Script, environment, code, call, expected assert expected_index == index -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Python 2 doesn't support __signature__") @pytest.mark.parametrize('code', ['foo', 'instance.foo']) def test_arg_defaults(Script, environment, code): def foo(arg="bla", arg1=1): diff --git a/test/test_api/test_classes.py b/test/test_api/test_classes.py index c756b5e6..27b227c1 100644 --- a/test/test_api/test_classes.py +++ b/test/test_api/test_classes.py @@ -3,7 +3,6 @@ from textwrap import dedent from inspect import cleandoc -import os import pytest @@ -534,8 +533,8 @@ def test_execute(Script, code, description): ('from pkg import Foo; Foo().bar', 'bar', 'module.py'), ]) def test_inheritance_module_path(Script, goto, code, name, file_name): - base_path = os.path.join(get_example_dir('inheritance'), 'pkg') - whatever_path = os.path.join(base_path, 'NOT_EXISTING.py') + base_path = get_example_dir('inheritance', 'pkg') + whatever_path = base_path.joinpath('NOT_EXISTING.py') script = Script(code, path=whatever_path) if goto is None: @@ -544,7 +543,7 @@ def test_inheritance_module_path(Script, goto, code, name, file_name): func, = script.goto(follow_imports=goto) assert func.type == 'function' assert func.name == name - assert func.module_path == os.path.join(base_path, file_name) + assert func.module_path == base_path.joinpath(file_name) def test_definition_goto_follow_imports(Script): @@ -595,7 +594,7 @@ def test_definition_goto_follow_imports(Script): 'foo(x: str, y: int=None) -> Union[int, str]'), ] ) -def test_get_type_hint(Script, code, expected, skip_pre_python36): +def test_get_type_hint(Script, code, expected): code = 'from typing import *\n' + code d, = Script(code).goto() assert d.get_type_hint() == expected diff --git a/test/test_api/test_completion.py b/test/test_api/test_completion.py index 300781f6..7805faba 100644 --- a/test/test_api/test_completion.py +++ b/test/test_api/test_completion.py @@ -1,13 +1,11 @@ from os.path import join, sep as s, dirname, expanduser import os -import sys from textwrap import dedent import pytest from ..helpers import root_dir from jedi.api.helpers import _start_match, _fuzzy_match -from jedi._compatibility import scandir def test_in_whitespace(Script): @@ -76,10 +74,7 @@ def test_loading_unicode_files_with_bad_global_charset(Script, monkeypatch, tmpd dirname = str(tmpdir.mkdir('jedi-test')) filename1 = join(dirname, 'test1.py') filename2 = join(dirname, 'test2.py') - if sys.version_info < (3, 0): - data = "# coding: latin-1\nfoo = 'm\xf6p'\n" - else: - data = "# coding: latin-1\nfoo = 'm\xf6p'\n".encode("latin-1") + data = "# coding: latin-1\nfoo = 'm\xf6p'\n".encode("latin-1") with open(filename1, "wb") as f: f.write(data) @@ -88,7 +83,7 @@ def test_loading_unicode_files_with_bad_global_charset(Script, monkeypatch, tmpd def test_complete_expanduser(Script): - possibilities = scandir(expanduser('~')) + possibilities = os.scandir(expanduser('~')) non_dots = [p for p in possibilities if not p.name.startswith('.') and len(p.name) > 1] item = non_dots[0] line = "'~%s%s'" % (os.sep, item.name) @@ -144,9 +139,6 @@ def test_in_comment_before_string(Script): def test_async(Script, environment): - if environment.version_info < (3, 5): - pytest.skip() - code = dedent(''' foo = 3 async def x(): @@ -229,8 +221,8 @@ current_dirname = os.path.basename(dirname(dirname(dirname(__file__)))) ('example.py', 'rb"' + join('..', current_dirname, 'tes'), None, ['t' + s]), # Absolute paths - (None, '"' + join(root_dir, 'test', 'test_ca'), None, ['che.py"']), - (None, '"%s"' % join(root_dir, 'test', 'test_ca'), len(root_dir) + 14, ['che.py']), + (None, f'"{root_dir.joinpath("test", "test_ca")}', None, ['che.py"']), + (None, f'"{root_dir.joinpath("test", "test_ca")}"', len(str(root_dir)) + 14, ['che.py']), # Longer quotes ('example.py', 'r"""test', None, [s]), @@ -248,9 +240,7 @@ current_dirname = os.path.basename(dirname(dirname(dirname(__file__)))) ('example.py', 'x = f("te" + "st"', 16, [s]), ('example.py', 'x = f("te" + "st")', 16, [s]), ('example.py', 'x = f("t" + "est")', 16, [s]), - # This is actually not correct, but for now leave it here, because of - # Python 2. - ('example.py', 'x = f(b"t" + "est")', 17, [s]), + ('example.py', 'x = f(b"t" + "est")', 17, []), ('example.py', '"test" + "', None, [s]), # __file__ @@ -378,11 +368,10 @@ _dict_keys_completion_tests = [ ] -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") @pytest.mark.parametrize( 'added_code, column, expected', _dict_keys_completion_tests ) -def test_dict_keys_completions(Script, added_code, column, expected, skip_pre_python36): +def test_dict_keys_completions(Script, added_code, column, expected): code = dedent(r''' ints = {1: ''} ints[50] = 3.0 @@ -404,8 +393,7 @@ def test_dict_keys_completions(Script, added_code, column, expected, skip_pre_py assert [c.complete for c in comps] == expected -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") -def test_dict_keys_in_weird_case(Script, skip_pre_python36): +def test_dict_keys_in_weird_case(Script): assert Script('a[\n# foo\nx]').complete(line=2, column=0) diff --git a/test/test_api/test_documentation.py b/test/test_api/test_documentation.py index 1fe8bfd9..104555cd 100644 --- a/test/test_api/test_documentation.py +++ b/test/test_api/test_documentation.py @@ -91,7 +91,7 @@ def test_version_info(Script): assert c.docstring() == 'sys.version_info\n\nVersion information as a named tuple.' -def test_builtin_docstring(goto_or_help_or_infer, skip_python2): +def test_builtin_docstring(goto_or_help_or_infer): d, = goto_or_help_or_infer('open') doc = d.docstring() @@ -99,7 +99,7 @@ def test_builtin_docstring(goto_or_help_or_infer, skip_python2): assert 'Open file' in doc -def test_docstring_decorator(goto_or_help_or_infer, skip_python2): +def test_docstring_decorator(goto_or_help_or_infer): code = dedent(''' import types diff --git a/test/test_api/test_environment.py b/test/test_api/test_environment.py index ef3815a8..621fcb8e 100644 --- a/test/test_api/test_environment.py +++ b/test/test_api/test_environment.py @@ -4,7 +4,6 @@ import sys import pytest import jedi -from jedi._compatibility import py_version from jedi.api.environment import get_default_environment, find_virtualenvs, \ InvalidPythonEnvironment, find_system_environments, \ get_system_environment, create_environment, InterpreterEnvironment, \ @@ -27,13 +26,13 @@ def test_find_system_environments(): @pytest.mark.parametrize( 'version', - ['2.7', '3.5', '3.6', '3.7'] + ['3.6', '3.7', '3.8', '3.9'] ) def test_versions(version): try: env = get_system_environment(version) except InvalidPythonEnvironment: - if int(version.replace('.', '')) == py_version: + if int(version.replace('.', '')) == str(sys.version_info[0]) + str(sys.version_info[1]): # At least the current version has to work raise pytest.skip() @@ -44,7 +43,7 @@ def test_versions(version): def test_load_module(inference_state): access_path = inference_state.compiled_subprocess.load_module( - dotted_name=u'math', + dotted_name='math', sys_path=inference_state.get_sys_path() ) name, access_handle = access_path.accesses[0] @@ -118,7 +117,6 @@ def test_create_environment_executable(): assert environment.executable == sys.executable -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") def test_get_default_environment_from_env_does_not_use_safe(tmpdir, monkeypatch): fake_python = os.path.join(str(tmpdir), 'fake_python') with open(fake_python, 'w', newline='') as f: diff --git a/test/test_api/test_full_name.py b/test/test_api/test_full_name.py index 1822a493..44690141 100644 --- a/test/test_api/test_full_name.py +++ b/test/test_api/test_full_name.py @@ -14,11 +14,11 @@ There are three kinds of test: """ import textwrap +from unittest import TestCase import pytest import jedi -from ..helpers import TestCase class MixinTestFullName(object): @@ -46,9 +46,6 @@ class TestFullNameWithGotoDefinitions(MixinTestFullName, TestCase): operation = 'infer' def test_tuple_mapping(self): - if self.environment.version_info.major == 2: - pytest.skip('Python 2 also yields None.') - self.check(""" import re any_re = re.compile('.*') diff --git a/test/test_api/test_interpreter.py b/test/test_api/test_interpreter.py index 0155d49f..33d33816 100644 --- a/test/test_api/test_interpreter.py +++ b/test/test_api/test_interpreter.py @@ -3,26 +3,14 @@ Tests of ``jedi.api.Interpreter``. """ import sys import warnings +import typing import pytest import jedi -from jedi._compatibility import is_py3, py_version from jedi.inference.compiled import mixed from importlib import import_module -if py_version > 30: - def exec_(source, global_map): - exec(source, global_map) -else: - eval(compile("""def exec_(source, global_map): - exec source in global_map """, 'blub', 'exec')) - -if py_version > 35: - import typing -else: - typing = None - class _GlobalNameSpace: class SideEffectContainer: @@ -139,9 +127,7 @@ def test_complete_raw_module(): def test_complete_raw_instance(): import datetime dt = datetime.datetime(2013, 1, 1) - completions = ['time', 'timetz', 'timetuple'] - if is_py3: - completions += ['timestamp'] + completions = ['time', 'timetz', 'timetuple', 'timestamp'] _assert_interpreter_complete('(dt - dt).ti', locals(), completions) @@ -198,7 +184,6 @@ def test_property_warnings(stacklevel, allow_unsafe_getattr): _assert_interpreter_complete('foo.prop.uppe', locals(), expected) -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") @pytest.mark.parametrize('class_is_findable', [False, True]) def test__getattr__completions(allow_unsafe_getattr, class_is_findable): class CompleteGetattr(object): @@ -289,7 +274,6 @@ def test_property_content(): assert def_.name == 'int' -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") def test_param_completion(): def foo(bar): pass @@ -307,7 +291,6 @@ def test_endless_yield(): _assert_interpreter_complete('list(lst)[9000].rea', locals(), ['real']) -@pytest.mark.skipif('py_version < 33', reason='inspect.signature was created in 3.3.') def test_completion_params(): foo = lambda a, b=3: None @@ -320,12 +303,11 @@ def test_completion_params(): assert t.name == 'int' -@pytest.mark.skipif('py_version < 33', reason='inspect.signature was created in 3.3.') def test_completion_param_annotations(): # Need to define this function not directly in Python. Otherwise Jedi is too # clever and uses the Python code instead of the signature object. code = 'def foo(a: 1, b: str, c: int = 1.0) -> bytes: pass' - exec_(code, locals()) + exec(code, locals()) script = jedi.Interpreter('foo', [locals()]) c, = script.complete() sig, = c.get_signatures() @@ -342,7 +324,6 @@ def test_completion_param_annotations(): assert d.name == 'bytes' -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") def test_keyword_argument(): def f(some_keyword_argument): pass @@ -351,12 +332,10 @@ def test_keyword_argument(): assert c.name == 'some_keyword_argument=' assert c.complete == 'ord_argument=' - # This needs inspect.signature to work. - if is_py3: - # Make it impossible for jedi to find the source of the function. - f.__name__ = 'xSOMETHING' - c, = jedi.Interpreter("x(some_keyw", [{'x': f}]).complete() - assert c.name == 'some_keyword_argument=' + # Make it impossible for jedi to find the source of the function. + f.__name__ = 'xSOMETHING' + c, = jedi.Interpreter("x(some_keyw", [{'x': f}]).complete() + assert c.name == 'some_keyword_argument=' def test_more_complex_instances(): @@ -405,16 +384,12 @@ def test_dir_magic_method(allow_unsafe_getattr): raise AttributeError(name) def __dir__(self): - if is_py3: - names = object.__dir__(self) - else: - names = dir(object()) - return ['foo', 'bar'] + names + return ['foo', 'bar'] + object.__dir__(self) itp = jedi.Interpreter("ca.", [{'ca': CompleteAttrs()}]) completions = itp.complete() names = [c.name for c in completions] - assert ('__dir__' in names) == is_py3 + assert ('__dir__' in names) is True assert '__class__' in names assert 'foo' in names assert 'bar' in names @@ -455,7 +430,6 @@ def test_sys_path_docstring(): # Was an issue in #1298 s.complete(line=2, column=4)[0].docstring() -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") @pytest.mark.parametrize( 'code, completions', [ ('x[0].uppe', ['upper']), @@ -501,7 +475,6 @@ def test_simple_completions(code, completions): assert [d.name for d in defs] == completions -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Python 2 doesn't have lru_cache") def test__wrapped__(): from functools import lru_cache @@ -514,7 +487,6 @@ def test__wrapped__(): assert c.line == syslogs_to_df.__wrapped__.__code__.co_firstlineno + 1 -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") def test_illegal_class_instance(): class X: __class__ = 1 @@ -524,14 +496,12 @@ def test_illegal_class_instance(): assert not v.is_instance() -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") @pytest.mark.parametrize('module_name', ['sys', 'time', 'unittest.mock']) def test_core_module_completes(module_name): module = import_module(module_name) assert jedi.Interpreter('module.', [locals()]).complete() -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") @pytest.mark.parametrize( 'code, expected, index', [ ('a(', ['a', 'b', 'c'], 0), @@ -557,7 +527,6 @@ def test_partial_signatures(code, expected, index): assert index == sig.index -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") def test_type_var(): """This was an issue before, see Github #1369""" import typing @@ -566,7 +535,6 @@ def test_type_var(): assert def_.name == 'TypeVar' -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") @pytest.mark.parametrize('class_is_findable', [False, True]) def test_param_annotation_completion(class_is_findable): class Foo: @@ -580,7 +548,6 @@ def test_param_annotation_completion(class_is_findable): assert def_.name == 'bar' -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") @pytest.mark.parametrize( 'code, column, expected', [ ('strs[', 5, ["'asdf'", "'fbar'", "'foo'", Ellipsis]), @@ -599,7 +566,7 @@ def test_param_annotation_completion(class_is_findable): ] ) def test_dict_completion(code, column, expected): - strs = {'asdf': 1, u"""foo""": 2, r'fbar': 3} + strs = {'asdf': 1, """foo""": 2, r'fbar': 3} mixed = {1: 2, 1.10: 4, None: 6, r'a\sdf': 8, b'foo': 9} class Inherited(dict): @@ -617,7 +584,6 @@ def test_dict_completion(code, column, expected): assert [c.complete for c in comps] == expected -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") @pytest.mark.parametrize( 'code, types', [ ('dct[1]', ['int']), @@ -641,7 +607,6 @@ def bar(): return float -@pytest.mark.skipif(sys.version_info < (3, 6), reason="Ignore Python 2, because EOL") @pytest.mark.parametrize( 'annotations, result, code', [ ({}, [], ''), diff --git a/test/test_api/test_keyword.py b/test/test_api/test_keyword.py index 3e5b9344..efafdfa3 100644 --- a/test/test_api/test_keyword.py +++ b/test/test_api/test_keyword.py @@ -2,14 +2,12 @@ Test of keywords and ``jedi.keywords`` """ -import pytest - def test_goto_keyword(Script): """ Bug: goto assignments on ``in`` used to raise AttributeError:: - 'unicode' object has no attribute 'generate_call_path' + 'str' object has no attribute 'generate_call_path' """ Script('in').goto() @@ -17,10 +15,7 @@ def test_goto_keyword(Script): def test_keyword(Script, environment): """ github jedi-vim issue #44 """ defs = Script("print").infer() - if environment.version_info.major < 3: - assert defs == [] - else: - assert [d.docstring() for d in defs] + assert [d.docstring() for d in defs] assert Script("import").goto() == [] @@ -45,16 +40,12 @@ def test_keyword_attributes(Script): assert def_.full_name is None assert def_.line is def_.column is None assert def_.in_builtin_module() is True - assert def_.module_name in ('builtins', '__builtin__') - assert 'typeshed' in def_.module_path + assert def_.module_name == 'builtins' + assert 'typeshed' in def_.module_path.parts assert def_.type == 'keyword' def test_none_keyword(Script, environment): - if environment.version_info.major == 2: - # Just don't care about Python 2 anymore, it's almost gone. - pytest.skip() - none, = Script('None').complete() assert not none.docstring() assert none.name == 'None' diff --git a/test/test_api/test_project.py b/test/test_api/test_project.py index 48e83378..c9bbe008 100644 --- a/test/test_api/test_project.py +++ b/test/test_api/test_project.py @@ -1,5 +1,5 @@ import os -import sys +from pathlib import Path import pytest @@ -22,13 +22,12 @@ def test_django_default_project(Script): def test_django_default_project_of_file(Script): project = get_default_project(__file__) - d = os.path.dirname - assert project._path == d(d(d(__file__))) + assert project._path == Path(__file__).parent.parent.parent def test_interpreter_project_path(): # Run from anywhere it should be the cwd. - dir = os.path.join(root_dir, 'test') + dir = Path(root_dir).joinpath('test') with set_cwd(dir): project = Interpreter('', [locals()])._inference_state.project assert project._path == dir @@ -133,8 +132,7 @@ def test_load_save_project(tmpdir): ('multiprocessin', ['multiprocessing'], dict(complete=True)), ] ) -@pytest.mark.skipif(sys.version_info < (3, 6), reason="Ignore Python 2, because EOL") -def test_search(string, full_names, kwargs, skip_pre_python36): +def test_search(string, full_names, kwargs): some_search_test_var = 1.0 project = Project(test_dir) if kwargs.pop('complete', False) is True: @@ -152,8 +150,7 @@ def test_search(string, full_names, kwargs, skip_pre_python36): ('test_load_save_p', ['roject'], False), ] ) -@pytest.mark.skipif(sys.version_info < (3, 6), reason="Ignore Python 2, because EOL") -def test_complete_search(Script, string, completions, all_scopes, skip_pre_python36): +def test_complete_search(Script, string, completions, all_scopes): project = Project(test_dir) defs = project.complete_search(string, all_scopes=all_scopes) assert [d.complete for d in defs] == completions diff --git a/test/test_api/test_refactoring.py b/test/test_api/test_refactoring.py index bf03d879..c229c129 100644 --- a/test/test_api/test_refactoring.py +++ b/test/test_api/test_refactoring.py @@ -1,38 +1,32 @@ import os -import sys from textwrap import dedent +from pathlib import Path import pytest import jedi -@pytest.fixture(autouse=True) -def skip_old_python(skip_pre_python36): - if sys.version_info < (3, 6): - pytest.skip() - - @pytest.fixture() def dir_with_content(tmpdir): with open(os.path.join(tmpdir.strpath, 'modx.py'), 'w', newline='') as f: f.write('import modx\nfoo\n') # self reference - return tmpdir.strpath + return Path(tmpdir.strpath) def test_rename_mod(Script, dir_with_content): script = Script( 'import modx; modx\n', - path=os.path.join(dir_with_content, 'some_script.py'), + path=dir_with_content.joinpath('some_script.py'), project=jedi.Project(dir_with_content), ) refactoring = script.rename(line=1, new_name='modr') refactoring.apply() - p1 = os.path.join(dir_with_content, 'modx.py') - p2 = os.path.join(dir_with_content, 'modr.py') + p1 = dir_with_content.joinpath('modx.py') + p2 = dir_with_content.joinpath('modr.py') expected_code = 'import modr\nfoo\n' - assert not os.path.exists(p1) + assert not p1.exists() with open(p2, newline='') as f: assert f.read() == expected_code diff --git a/test/test_api/test_search.py b/test/test_api/test_search.py index 0f38b560..20bb7286 100644 --- a/test/test_api/test_search.py +++ b/test/test_api/test_search.py @@ -65,10 +65,7 @@ class SomeClass: ('SomeCl.twice', [], dict(all_scopes=True, complete=True, fuzzy=True)), ] ) -def test_simple_search(Script, string, descriptions, kwargs, skip_pre_python36): - if sys.version_info < (3, 6): - pytest.skip() - +def test_simple_search(Script, string, descriptions, kwargs): if kwargs.pop('complete', False) is True: defs = Script(path=__file__).complete_search(string, **kwargs) else: diff --git a/test/test_api/test_signatures.py b/test/test_api/test_signatures.py index 40cbf03d..a211c1e3 100644 --- a/test/test_api/test_signatures.py +++ b/test/test_api/test_signatures.py @@ -1,5 +1,3 @@ -import sys - import pytest _tuple_code = 'from typing import Tuple\ndef f(x: Tuple[int]): ...\nf' @@ -20,7 +18,7 @@ _tuple_code = 'from typing import Tuple\ndef f(x: Tuple[int]): ...\nf' ('def f(*args: int, **kwargs: str): ...\nf', ['class int', 'class str'], False), ] ) -def test_param_annotation(Script, code, expected_params, execute_annotation, skip_python2): +def test_param_annotation(Script, code, expected_params, execute_annotation): func, = Script(code).goto() sig, = func.get_signatures() for p, expected in zip(sig.params, expected_params): @@ -51,7 +49,6 @@ def test_param_default(Script, code, expected_params): assert annotation.description == expected -@pytest.mark.skipif(sys.version_info < (3, 5), reason="Python <3.5 doesn't support __signature__") @pytest.mark.parametrize( 'code, index, param_code, kind', [ ('def f(x=1): pass\nf', 0, 'x=1', 'POSITIONAL_OR_KEYWORD'), @@ -61,7 +58,7 @@ def test_param_default(Script, code, expected_params): ('def f(*args, x): pass\nf', 1, 'x', 'KEYWORD_ONLY'), ] ) -def test_param_kind_and_name(code, index, param_code, kind, Script, skip_python2): +def test_param_kind_and_name(code, index, param_code, kind, Script): func, = Script(code).goto() sig, = func.get_signatures() param = sig.params[index] diff --git a/test/test_api/test_unicode.py b/test/test_api/test_unicode.py index 91090d1a..54a11eb5 100644 --- a/test/test_api/test_unicode.py +++ b/test/test_api/test_unicode.py @@ -1,55 +1,48 @@ -# -*- coding: utf-8 -*- """ All character set and unicode related tests. """ -from jedi._compatibility import u, unicode from jedi import Project def test_unicode_script(Script): - """ normally no unicode objects are being used. (<=2.7) """ - s = unicode("import datetime; datetime.timedelta") + s = "import datetime; datetime.timedelta" completions = Script(s).complete() assert len(completions) - assert type(completions[0].description) is unicode + assert type(completions[0].description) is str - s = u("author='öä'; author") + s = "author='öä'; author" completions = Script(s).complete() x = completions[0].description - assert type(x) is unicode + assert type(x) is str - s = u("#-*- coding: iso-8859-1 -*-\nauthor='öä'; author") + s = "#-*- coding: iso-8859-1 -*-\nauthor='öä'; author" s = s.encode('latin-1') completions = Script(s).complete() - assert type(completions[0].description) is unicode + assert type(completions[0].description) is str def test_unicode_attribute(Script): """ github jedi-vim issue #94 """ - s1 = u('#-*- coding: utf-8 -*-\nclass Person():\n' - ' name = "e"\n\nPerson().name.') + s1 = ('#-*- coding: utf-8 -*-\nclass Person():\n' + ' name = "e"\n\nPerson().name.') completions1 = Script(s1).complete() assert 'strip' in [c.name for c in completions1] - s2 = u('#-*- coding: utf-8 -*-\nclass Person():\n' - ' name = "é"\n\nPerson().name.') + s2 = ('#-*- coding: utf-8 -*-\nclass Person():\n' + ' name = "é"\n\nPerson().name.') completions2 = Script(s2).complete() assert 'strip' in [c.name for c in completions2] def test_multibyte_script(Script): """ `jedi.Script` must accept multi-byte string source. """ - try: - code = u("import datetime; datetime.d") - comment = u("# multi-byte comment あいうえおä") - s = (u('%s\n%s') % (code, comment)).encode('utf-8') - except NameError: - pass # python 3 has no unicode method - else: - assert len(Script(s).complete(1, len(code))) + code = "import datetime; datetime.d" + comment = "# multi-byte comment あいうえおä" + s = ('%s\n%s') % (code, comment) + assert len(Script(s).complete(1, len(code))) def test_goto_definition_at_zero(Script): - """At zero usually sometimes raises unicode issues.""" + """Infer at zero sometimes raises issues.""" assert Script("a").infer(1, 1) == [] s = Script("str").infer(1, 1) assert len(s) == 1 @@ -69,7 +62,7 @@ def test_complete_at_zero(Script): def test_wrong_encoding(Script, tmpdir): x = tmpdir.join('x.py') # Use both latin-1 and utf-8 (a really broken file). - x.write_binary(u'foobar = 1\nä'.encode('latin-1') + u'ä'.encode('utf-8')) + x.write_binary('foobar = 1\nä'.encode('latin-1') + 'ä'.encode('utf-8')) project = Project('.', sys_path=[tmpdir.strpath]) c, = Script('import x; x.foo', project=project).complete() diff --git a/test/test_compatibility.py b/test/test_compatibility.py deleted file mode 100644 index c3c5c0e5..00000000 --- a/test/test_compatibility.py +++ /dev/null @@ -1,18 +0,0 @@ -from collections import namedtuple -from jedi._compatibility import highest_pickle_protocol - - -def test_highest_pickle_protocol(): - v = namedtuple('version', 'major, minor') - assert highest_pickle_protocol([v(2, 7), v(2, 7)]) == 2 - assert highest_pickle_protocol([v(2, 7), v(3, 8)]) == 2 - assert highest_pickle_protocol([v(2, 7), v(3, 5)]) == 2 - assert highest_pickle_protocol([v(2, 7), v(3, 6)]) == 2 - assert highest_pickle_protocol([v(3, 8), v(2, 7)]) == 2 - assert highest_pickle_protocol([v(3, 8), v(3, 8)]) == 4 - assert highest_pickle_protocol([v(3, 8), v(3, 5)]) == 4 - assert highest_pickle_protocol([v(3, 8), v(3, 6)]) == 4 - assert highest_pickle_protocol([v(3, 6), v(2, 7)]) == 2 - assert highest_pickle_protocol([v(3, 6), v(3, 8)]) == 4 - assert highest_pickle_protocol([v(3, 6), v(3, 5)]) == 4 - assert highest_pickle_protocol([v(3, 6), v(3, 6)]) == 4 diff --git a/test/test_deprecation.py b/test/test_deprecation.py index 5c4425c7..39e2baa9 100644 --- a/test/test_deprecation.py +++ b/test/test_deprecation.py @@ -1,10 +1,7 @@ -# -*- coding: utf-8 -*- import warnings import pytest -from jedi._compatibility import u - @pytest.fixture(autouse=True) def check_for_warning(recwarn): @@ -40,9 +37,3 @@ def test_usages(Script): def test_call_signatures(Script): d1, = Script('abs(float(\nstr(', line=1, column=4).call_signatures() assert d1.name == 'abs' - - -def test_encoding_parameter(Script): - name = u('hö') - s = Script(name.encode('latin-1'), encoding='latin-1') - assert s._module_node.get_code() == name diff --git a/test/test_file_io.py b/test/test_file_io.py index 61b5b1c3..bbf2170b 100644 --- a/test/test_file_io.py +++ b/test/test_file_io.py @@ -15,7 +15,7 @@ def test_folder_io_walk(): root, folder_ios, file_ios = next(iterator) assert folder_ios assert root.path == join(root_dir, 'ns2') - folder_ios[:] = [] # Not folder_ios.clear() because Python 2 + folder_ios.clear() assert next(iterator, None) is None @@ -23,5 +23,5 @@ def test_folder_io_walk2(): root_dir = get_example_dir('namespace_package') iterator = FolderIO(root_dir).walk() root, folder_ios, file_ios = next(iterator) - folder_ios[:] = [] # Not folder_ios.clear() because Python 2 + folder_ios.clear() assert next(iterator, None) is None diff --git a/test/test_inference/test_absolute_import.py b/test/test_inference/test_absolute_import.py deleted file mode 100644 index 72017dc9..00000000 --- a/test/test_inference/test_absolute_import.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -Tests ``from __future__ import absolute_import`` (only important for -Python 2.X) -""" -from jedi import Project -from .. import helpers - - -def test_can_complete_when_shadowing(Script): - path = helpers.get_example_dir('absolute_import', 'unittest.py') - script = Script(path=path, project=Project(helpers.get_example_dir('absolute_import'))) - assert script.complete() diff --git a/test/test_inference/test_annotations.py b/test/test_inference/test_annotations.py index 7940d7db..879b2ec5 100644 --- a/test/test_inference/test_annotations.py +++ b/test/test_inference/test_annotations.py @@ -9,9 +9,6 @@ def test_simple_annotations(Script, environment): If annotations adhere to PEP-0484, we use them (they override inference), else they are parsed but ignored """ - if environment.version_info.major == 2: - pytest.skip() - source = dedent("""\ def annot(a:3): return a @@ -45,18 +42,12 @@ def test_simple_annotations(Script, environment): r'1\n' ]) def test_illegal_forward_references(Script, environment, reference): - if environment.version_info.major == 2: - pytest.skip() - source = 'def foo(bar: "%s"): bar' % reference assert not Script(source).infer() def test_lambda_forward_references(Script, environment): - if environment.version_info.major == 2: - pytest.skip() - source = 'def foo(bar: "lambda: 3"): bar' # For now just receiving the 3 is ok. I'm doubting that this is what we diff --git a/test/test_inference/test_buildout_detection.py b/test/test_inference/test_buildout_detection.py index 3d44c2d4..01d56a78 100644 --- a/test/test_inference/test_buildout_detection.py +++ b/test/test_inference/test_buildout_detection.py @@ -1,7 +1,7 @@ import os from textwrap import dedent +from pathlib import Path -from jedi._compatibility import force_unicode from jedi.inference.sys_path import _get_parent_dir_with_file, \ _get_buildout_script_paths, check_sys_path_modifications @@ -14,18 +14,18 @@ def check_module_test(Script, code): def test_parent_dir_with_file(Script): - path = get_example_dir('buildout_project', 'src', 'proj_name') + path = Path(get_example_dir('buildout_project', 'src', 'proj_name')) parent = _get_parent_dir_with_file(path, 'buildout.cfg') assert parent is not None - assert parent.endswith(os.path.join('test', 'examples', 'buildout_project')) + assert str(parent).endswith(os.path.join('test', 'examples', 'buildout_project')) def test_buildout_detection(Script): - path = get_example_dir('buildout_project', 'src', 'proj_name') - scripts = list(_get_buildout_script_paths(os.path.join(path, 'module_name.py'))) - assert len(scripts) == 1 + path = Path(get_example_dir('buildout_project', 'src', 'proj_name')) + paths = list(_get_buildout_script_paths(path.joinpath('module_name.py'))) + assert len(paths) == 1 appdir_path = os.path.normpath(os.path.join(path, '../../bin/app')) - assert scripts[0] == appdir_path + assert str(paths[0]) == appdir_path def test_append_on_non_sys_path(Script): @@ -58,17 +58,17 @@ def test_sys_path_with_modifications(Script): """) paths = Script(code, path=path)._inference_state.get_sys_path() - assert '/tmp/.buildout/eggs/important_package.egg' in paths + assert os.path.abspath('/tmp/.buildout/eggs/important_package.egg') in paths def test_path_from_sys_path_assignment(Script): - code = dedent(""" + code = dedent(f""" #!/usr/bin/python import sys sys.path[0:0] = [ - '/usr/lib/python3.8/site-packages', - '/home/test/.buildout/eggs/important_package.egg' + {os.path.abspath('/usr/lib/python3.8/site-packages')!r}, + {os.path.abspath('/home/test/.buildout/eggs/important_package.egg')!r}, ] path[0:0] = [1] @@ -79,6 +79,6 @@ def test_path_from_sys_path_assignment(Script): sys.exit(important_package.main())""") paths = check_module_test(Script, code) - paths = list(map(force_unicode, paths)) assert 1 not in paths - assert '/home/test/.buildout/eggs/important_package.egg' in paths + assert os.path.abspath('/home/test/.buildout/eggs/important_package.egg') \ + in map(str, paths) diff --git a/test/test_inference/test_compiled.py b/test/test_inference/test_compiled.py index 92111b2a..e0315e52 100644 --- a/test/test_inference/test_compiled.py +++ b/test/test_inference/test_compiled.py @@ -1,6 +1,5 @@ from textwrap import dedent import math -import sys from collections import Counter from datetime import datetime @@ -13,26 +12,22 @@ from jedi.inference.syntax_tree import _infer_comparison_part def test_simple(inference_state, environment): - obj = compiled.create_simple_object(inference_state, u'_str_') - upper, = obj.py__getattribute__(u'upper') + obj = compiled.create_simple_object(inference_state, '_str_') + upper, = obj.py__getattribute__('upper') objs = list(upper.execute_with_values()) assert len(objs) == 1 - if environment.version_info.major == 2: - expected = 'unicode' - else: - expected = 'str' - assert objs[0].name.string_name == expected + assert objs[0].name.string_name == 'str' def test_builtin_loading(inference_state): - string, = inference_state.builtins_module.py__getattribute__(u'str') - from_name, = string.py__getattribute__(u'__init__') + string, = inference_state.builtins_module.py__getattribute__('str') + from_name, = string.py__getattribute__('__init__') assert from_name.tree_node assert not from_name.py__doc__() # It's a stub def test_next_docstr(inference_state): - next_ = compiled.builtin_from_name(inference_state, u'next') + next_ = compiled.builtin_from_name(inference_state, 'next') assert next_.tree_node is not None assert next_.py__doc__() == '' # It's a stub for non_stub in _stub_to_python_value_set(next_): @@ -53,9 +48,9 @@ def test_doc(inference_state): Even CompiledValue docs always return empty docstrings - not None, that's just a Jedi API definition. """ - str_ = compiled.create_simple_object(inference_state, u'') + str_ = compiled.create_simple_object(inference_state, '') # Equals `''.__getnewargs__` - obj, = str_.py__getattribute__(u'__getnewargs__') + obj, = str_.py__getattribute__('__getnewargs__') assert obj.py__doc__() == '' @@ -66,13 +61,9 @@ def test_string_literals(Script, environment): assert typ('""') == 'str' assert typ('r""') == 'str' - if environment.version_info.major > 2: - assert typ('br""') == 'bytes' - assert typ('b""') == 'bytes' - assert typ('u""') == 'str' - else: - assert typ('b""') == 'str' - assert typ('u""') == 'unicode' + assert typ('br""') == 'bytes' + assert typ('b""') == 'bytes' + assert typ('u""') == 'str' def test_method_completion(Script, environment): @@ -95,9 +86,6 @@ def test_time_docstring(Script): def test_dict_values(Script, environment): - if environment.version_info.major == 2: - # It looks like typeshed for Python 2 returns Any. - pytest.skip() assert Script('import sys\nsys.modules["alshdb;lasdhf"]').infer() @@ -142,13 +130,10 @@ def test_parent_context(same_process_inference_state, attribute, expected_name, x, = o.py__getattribute__(attribute) assert x.py__name__() == expected_name module_name = x.parent_context.py__name__() - if module_name == '__builtin__': - module_name = 'builtins' # Python 2 assert module_name == expected_parent assert x.parent_context.parent_context is None -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") @pytest.mark.parametrize( 'obj, expected_names', [ ('', ['str']), @@ -177,7 +162,7 @@ def test_operation(Script, inference_state, create_compiled_object): false, true = _infer_comparison_part( inference_state, b.parent_context, left=list(b.execute_with_values())[0], - operator=u'is not', + operator='is not', right=b, ) assert false.py__name__() == 'bool' diff --git a/test/test_inference/test_context.py b/test/test_inference/test_context.py index 9552f633..2889e09d 100644 --- a/test/test_inference/test_context.py +++ b/test/test_inference/test_context.py @@ -1,6 +1,3 @@ -from jedi._compatibility import force_unicode - - def test_module_attributes(Script): def_, = Script('__name__').complete() assert def_.name == '__name__' @@ -13,9 +10,9 @@ def test_module_attributes(Script): def test_module__file__(Script, environment): assert not Script('__file__').infer() def_, = Script('__file__', path='example.py').infer() - value = force_unicode(def_._name._value.get_safe_value()) + value = def_._name._value.get_safe_value() assert value.endswith('example.py') def_, = Script('import antigravity; antigravity.__file__').infer() - value = force_unicode(def_._name._value.get_safe_value()) + value = def_._name._value.get_safe_value() assert value.endswith('.py') diff --git a/test/test_inference/test_docstring.py b/test/test_inference/test_docstring.py index 0154db84..3fc54eb1 100644 --- a/test/test_inference/test_docstring.py +++ b/test/test_inference/test_docstring.py @@ -3,7 +3,6 @@ Testing of docstring related issues and especially ``jedi.docstrings``. """ import os -import sys from textwrap import dedent import pytest @@ -25,11 +24,6 @@ except ImportError: else: numpy_unavailable = False -if sys.version_info.major == 2: - # In Python 2 there's an issue with tox/docutils that makes the tests fail, - # Python 2 is soon end-of-life, so just don't support numpydoc for it anymore. - numpydoc_unavailable = True - def test_function_doc(Script): defs = Script(""" @@ -163,7 +157,7 @@ def test_docstring_params_formatting(Script): assert defs[0].docstring() == 'func(param1, param2, param3)' -def test_import_function_docstring(Script, skip_pre_python35): +def test_import_function_docstring(Script): code = "from stub_folder import with_stub; with_stub.stub_function" path = os.path.join(test_dir, 'completion', 'import_function_docstring.py') c, = Script(code, path=path).complete() @@ -422,7 +416,7 @@ def test_decorator(Script): assert d.docstring(raw=True) == 'Nice docstring' -def test_method_decorator(Script, skip_pre_python35): +def test_method_decorator(Script): code = dedent(''' def decorator(func): @wraps(func) @@ -443,7 +437,7 @@ def test_method_decorator(Script, skip_pre_python35): assert d.docstring() == 'wrapper(f)\n\nNice docstring' -def test_partial(Script, skip_pre_python36): +def test_partial(Script): code = dedent(''' def foo(): 'x y z' diff --git a/test/test_inference/test_gradual/test_conversion.py b/test/test_inference/test_gradual/test_conversion.py index fd05d265..fd201aee 100644 --- a/test/test_inference/test_gradual/test_conversion.py +++ b/test/test_inference/test_gradual/test_conversion.py @@ -56,7 +56,7 @@ def test_goto_on_file(Script): assert d2.name == 'Bar' -def test_goto_import(Script, skip_pre_python35): +def test_goto_import(Script): code = 'from abc import ABC; ABC' d, = Script(code).goto(only_stubs=True) assert d.is_stub() diff --git a/test/test_inference/test_gradual/test_stubs.py b/test/test_inference/test_gradual/test_stubs.py index 6683474a..b3a042c7 100644 --- a/test/test_inference/test_gradual/test_stubs.py +++ b/test/test_inference/test_gradual/test_stubs.py @@ -43,11 +43,6 @@ from test.helpers import root_dir ]) def test_infer_and_goto(Script, code, full_name, has_stub, has_python, way, kwargs, type_, options, environment): - if environment.version_info < (3, 5): - # We just don't care about much of the detailed Python 2 failures - # anymore, because its end-of-life soon. - pytest.skip() - if type_ == 'infer' and full_name == 'typing.Sequence' and environment.version_info >= (3, 7): # In Python 3.7+ there's not really a sequence definition, there's just # a name that leads nowhere. @@ -92,4 +87,4 @@ def test_infer_and_goto(Script, code, full_name, has_stub, has_python, way, assert has_python == (not d.is_stub()) assert d.full_name == full_name - assert d.is_stub() == d.module_path.endswith('.pyi') + assert d.is_stub() == (d.module_path.suffix == '.pyi') diff --git a/test/test_inference/test_gradual/test_typeshed.py b/test/test_inference/test_gradual/test_typeshed.py index 157128c6..ca48db11 100644 --- a/test/test_inference/test_gradual/test_typeshed.py +++ b/test/test_inference/test_gradual/test_typeshed.py @@ -14,20 +14,13 @@ TYPESHED_PYTHON3 = os.path.join(typeshed.TYPESHED_PATH, 'stdlib', '3') def test_get_typeshed_directories(): def get_dirs(version_info): return { - p.path.replace(typeshed.TYPESHED_PATH, '').lstrip(os.path.sep) + p.path.replace(str(typeshed.TYPESHED_PATH), '').lstrip(os.path.sep) for p in typeshed._get_typeshed_directories(version_info) } def transform(set_): return {x.replace('/', os.path.sep) for x in set_} - dirs = get_dirs(PythonVersionInfo(2, 7)) - assert dirs == transform({'stdlib/2and3', 'stdlib/2', 'third_party/2and3', 'third_party/2'}) - - dirs = get_dirs(PythonVersionInfo(3, 5)) - assert dirs == transform({'stdlib/2and3', 'stdlib/3', - 'third_party/2and3', 'third_party/3'}) - dirs = get_dirs(PythonVersionInfo(3, 6)) assert dirs == transform({'stdlib/2and3', 'stdlib/3', 'stdlib/3.6', 'third_party/2and3', @@ -59,7 +52,7 @@ def test_keywords_variable(Script): assert seq.name == 'Sequence' # This points towards the typeshed implementation stub_seq, = seq.goto(only_stubs=True) - assert typeshed.TYPESHED_PATH in stub_seq.module_path + assert str(stub_seq.module_path).startswith(str(typeshed.TYPESHED_PATH)) def test_class(Script): @@ -98,8 +91,12 @@ def test_sys_exc_info(Script): none, def_ = Script(code + '[1]').infer() # It's an optional. assert def_.name == 'BaseException' + assert def_.module_path == typeshed.TYPESHED_PATH.joinpath( + 'stdlib', '2and3', 'builtins.pyi' + ) assert def_.type == 'instance' assert none.name == 'NoneType' + assert none.module_path is None none, def_ = Script(code + '[0]').infer() assert def_.name == 'BaseException' @@ -110,18 +107,15 @@ def test_sys_getwindowsversion(Script, environment): # This should only exist on Windows, but type inference should happen # everywhere. definitions = Script('import sys; sys.getwindowsversion().major').infer() - if environment.version_info.major == 2: - assert not definitions - else: - def_, = definitions - assert def_.name == 'int' + def_, = definitions + assert def_.name == 'int' def test_sys_hexversion(Script): script = Script('import sys; sys.hexversion') def_, = script.complete() assert isinstance(def_._name, StubName), def_._name - assert typeshed.TYPESHED_PATH in def_.module_path + assert str(def_.module_path).startswith(str(typeshed.TYPESHED_PATH)) def_, = script.infer() assert def_.name == 'int' @@ -133,7 +127,7 @@ def test_math(Script): assert value -def test_type_var(Script, skip_python2): +def test_type_var(Script): def_, = Script('import typing; T = typing.TypeVar("T1")').infer() assert def_.name == 'TypeVar' assert def_.description == 'class TypeVar' @@ -148,14 +142,14 @@ def test_type_var(Script, skip_python2): def test_math_is_stub(Script, code, full_name): s = Script(code) cos, = s.infer() - wanted = os.path.join('typeshed', 'stdlib', '2and3', 'math.pyi') - assert cos.module_path.endswith(wanted) + wanted = ('typeshed', 'stdlib', '2and3', 'math.pyi') + assert cos.module_path.parts[-4:] == wanted assert cos.is_stub() is True assert cos.goto(only_stubs=True) == [cos] assert cos.full_name == full_name cos, = s.goto() - assert cos.module_path.endswith(wanted) + assert cos.module_path.parts[-4:] == wanted assert cos.goto(only_stubs=True) == [cos] assert cos.is_stub() is True assert cos.full_name == full_name diff --git a/test/test_inference/test_implicit_namespace_package.py b/test/test_inference/test_implicit_namespace_package.py index 452375b3..5b242b66 100644 --- a/test/test_inference/test_implicit_namespace_package.py +++ b/test/test_inference/test_implicit_namespace_package.py @@ -1,17 +1,7 @@ -from os.path import dirname - -import pytest - from test.helpers import get_example_dir, example_dir from jedi import Project -@pytest.fixture(autouse=True) -def skip_not_supported_versions(environment): - if environment.version_info < (3, 5): - pytest.skip() - - def test_implicit_namespace_package(Script): sys_path = [get_example_dir('implicit_namespace_package', 'ns1'), get_example_dir('implicit_namespace_package', 'ns2')] diff --git a/test/test_inference/test_imports.py b/test/test_inference/test_imports.py index f5097f21..6cc7b051 100644 --- a/test/test_inference/test_imports.py +++ b/test/test_inference/test_imports.py @@ -4,46 +4,46 @@ Tests". """ import os +from pathlib import Path import pytest from jedi.file_io import FileIO -from jedi._compatibility import find_module_py33, find_module from jedi.inference import compiled from jedi.inference import imports from jedi.api.project import Project from jedi.inference.gradual.conversion import _stub_to_python_value_set from jedi.inference.references import get_module_contexts_containing_name from ..helpers import get_example_dir, test_dir, test_dir_project, root_dir +from jedi.inference.compiled.subprocess.functions import _find_module_py33, _find_module THIS_DIR = os.path.dirname(__file__) -@pytest.mark.skipif('sys.version_info < (3,3)') -def test_find_module_py33(): +def test_find_module_basic(): """Needs to work like the old find_module.""" - assert find_module_py33('_io') == (None, False) + assert _find_module_py33('_io') == (None, False) with pytest.raises(ImportError): - assert find_module_py33('_DOESNTEXIST_') == (None, None) + assert _find_module_py33('_DOESNTEXIST_') == (None, None) def test_find_module_package(): - file_io, is_package = find_module('json') + file_io, is_package = _find_module('json') assert file_io.path.endswith(os.path.join('json', '__init__.py')) assert is_package is True def test_find_module_not_package(): - file_io, is_package = find_module('io') + file_io, is_package = _find_module('io') assert file_io.path.endswith('io.py') assert is_package is False -pkg_zip_path = get_example_dir('zipped_imports', 'pkg.zip') +pkg_zip_path = Path(get_example_dir('zipped_imports', 'pkg.zip')) def test_find_module_package_zipped(Script, inference_state, environment): - sys_path = environment.get_sys_path() + [pkg_zip_path] + sys_path = environment.get_sys_path() + [str(pkg_zip_path)] project = Project('.', sys_path=sys_path) script = Script('import pkg; pkg.mod', project=project) @@ -51,8 +51,8 @@ def test_find_module_package_zipped(Script, inference_state, environment): file_io, is_package = inference_state.compiled_subprocess.get_module_info( sys_path=sys_path, - string=u'pkg', - full_name=u'pkg' + string='pkg', + full_name='pkg' ) assert file_io is not None assert file_io.path.endswith(os.path.join('pkg.zip', 'pkg', '__init__.py')) @@ -86,15 +86,15 @@ def test_find_module_package_zipped(Script, inference_state, environment): ) def test_correct_zip_package_behavior(Script, inference_state, environment, code, - file, package, path, skip_python2): - sys_path = environment.get_sys_path() + [pkg_zip_path] + file, package, path): + sys_path = environment.get_sys_path() + [str(pkg_zip_path)] pkg, = Script(code, project=Project('.', sys_path=sys_path)).infer() value, = pkg._name.infer() - assert value.py__file__() == os.path.join(pkg_zip_path, 'pkg', file) + assert value.py__file__() == pkg_zip_path.joinpath('pkg', file) assert '.'.join(value.py__package__()) == package assert value.is_package() is (path is not None) if path is not None: - assert value.py__path__() == [os.path.join(pkg_zip_path, path)] + assert value.py__path__() == [str(pkg_zip_path.joinpath(path))] def test_find_module_not_package_zipped(Script, inference_state, environment): @@ -104,9 +104,9 @@ def test_find_module_not_package_zipped(Script, inference_state, environment): assert len(script.complete()) == 1 file_io, is_package = inference_state.compiled_subprocess.get_module_info( - sys_path=sys_path, - string=u'not_pkg', - full_name=u'not_pkg' + sys_path=map(str, sys_path), + string='not_pkg', + full_name='not_pkg' ) assert file_io.path.endswith(os.path.join('not_pkg.zip', 'not_pkg.py')) assert is_package is False @@ -432,22 +432,16 @@ def test_import_name_calculation(Script): @pytest.mark.parametrize('name', ('builtins', 'typing')) def test_pre_defined_imports_module(Script, environment, name): - if environment.version_info.major < 3 and name == 'builtins': - name = '__builtin__' - path = os.path.join(root_dir, name + '.py') module = Script('', path=path)._get_module_context() assert module.string_names == (name,) - assert module.inference_state.builtins_module.py__file__() != path - assert module.inference_state.typing_module.py__file__() != path + assert str(module.inference_state.builtins_module.py__file__()) != path + assert str(module.inference_state.typing_module.py__file__()) != path @pytest.mark.parametrize('name', ('builtins', 'typing')) def test_import_needed_modules_by_jedi(Script, environment, tmpdir, name): - if environment.version_info.major < 3 and name == 'builtins': - name = '__builtin__' - module_path = tmpdir.join(name + '.py') module_path.write('int = ...') script = Script( @@ -456,8 +450,8 @@ def test_import_needed_modules_by_jedi(Script, environment, tmpdir, name): project=Project('.', sys_path=[tmpdir.strpath] + environment.get_sys_path()), ) module, = script.infer() - assert module._inference_state.builtins_module.py__file__() != module_path - assert module._inference_state.typing_module.py__file__() != module_path + assert str(module._inference_state.builtins_module.py__file__()) != module_path + assert str(module._inference_state.typing_module.py__file__()) != module_path def test_import_with_semicolon(Script): diff --git a/test/test_inference/test_literals.py b/test/test_inference/test_literals.py index 42305d72..f63b616f 100644 --- a/test/test_inference/test_literals.py +++ b/test/test_inference/test_literals.py @@ -26,7 +26,7 @@ def test_f_strings(Script, environment): assert _infer_literal(Script, 'rF"{asdf} "', is_fstring=True) == '' -def test_rb_strings(Script, environment, skip_python2): +def test_rb_strings(Script, environment): assert _infer_literal(Script, 'x = br"asdf"; x') == b'asdf' assert _infer_literal(Script, 'x = rb"asdf"; x') == b'asdf' diff --git a/test/test_inference/test_mixed.py b/test/test_inference/test_mixed.py index fce1929f..b4e6c65d 100644 --- a/test/test_inference/test_mixed.py +++ b/test/test_inference/test_mixed.py @@ -1,5 +1,4 @@ from typing import Generic, TypeVar, List -import sys import pytest @@ -86,7 +85,6 @@ def test_mixed_module_cache(): assert isinstance(jedi_module, ModuleValue) -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, EoL") def test_signature(): """ For performance reasons we use the signature of the compiled object and not @@ -104,10 +102,11 @@ def test_signature(): assert s.docstring() == 'some_signature(*, bar=1)' -@pytest.mark.skipif(sys.version_info[0:2] < (3, 5), reason="Typing was introduced in Python 3.5") def test_compiled_signature_annotation_string(): import typing - def func(x: typing.Type, y: typing.Union[typing.Type, int]): pass + + def func(x: typing.Type, y: typing.Union[typing.Type, int]): + pass func.__name__ = 'not_func' s, = jedi.Interpreter('func()', [locals()]).get_signatures(1, 5) diff --git a/test/test_inference/test_namespace_package.py b/test/test_inference/test_namespace_package.py index 67fa49b6..69e91354 100644 --- a/test/test_inference/test_namespace_package.py +++ b/test/test_inference/test_namespace_package.py @@ -40,7 +40,7 @@ def test_goto_assignment(Script, source, solution): def test_simple_completions(Script): # completion completions = script_with_path(Script, 'from pkg import ').complete() - names = [str(c.name) for c in completions] # str because of unicode + names = [c.name for c in completions] compare = ['foo', 'ns1_file', 'ns1_folder', 'ns2_folder', 'ns2_file', 'pkg_resources', 'pkgutil', '__name__', '__path__', '__package__', '__file__', '__doc__'] @@ -80,9 +80,6 @@ def test_relative_import(Script, environment, tmpdir): """ Attempt a relative import in a very simple namespace package. """ - if environment.version_info < (3, 5): - pytest.skip() - directory = get_example_dir('namespace_package_relative_import') # Need to copy the content in a directory where there's no __init__.py. py.path.local(directory).copy(tmpdir) diff --git a/test/test_inference/test_precedence.py b/test/test_inference/test_precedence.py index 9cb70a57..f92e2af5 100644 --- a/test/test_inference/test_precedence.py +++ b/test/test_inference/test_precedence.py @@ -10,8 +10,6 @@ import pytest pytest.param('... == ...', marks=pytest.mark.xfail), ]) def test_equals(Script, environment, source): - if environment.version_info.major < 3: - pytest.skip("Ellipsis does not exists in 2") script = Script(source) node = script._module_node.children[0] first, = script._get_module_context().infer_node(node) diff --git a/test/test_inference/test_pyc.py b/test/test_inference/test_pyc.py index fec4c958..f5ef8b29 100644 --- a/test/test_inference/test_pyc.py +++ b/test/test_inference/test_pyc.py @@ -39,16 +39,14 @@ def pyc_project_path(tmpdir): compileall.compile_file(dummy_path) os.remove(dummy_path) - if sys.version_info.major == 3: - # Python3 specific: - # To import pyc modules, we must move them out of the __pycache__ - # directory and rename them to remove ".cpython-%s%d" - # see: http://stackoverflow.com/questions/11648440/python-does-not-detect-pyc-files - pycache = os.path.join(dummy_package_path, "__pycache__") - for f in os.listdir(pycache): - dst = f.replace('.cpython-%s%s' % sys.version_info[:2], "") - dst = os.path.join(dummy_package_path, dst) - shutil.copy(os.path.join(pycache, f), dst) + # To import pyc modules, we must move them out of the __pycache__ + # directory and rename them to remove ".cpython-%s%d" + # see: http://stackoverflow.com/questions/11648440/python-does-not-detect-pyc-files + pycache = os.path.join(dummy_package_path, "__pycache__") + for f in os.listdir(pycache): + dst = f.replace('.cpython-%s%s' % sys.version_info[:2], "") + dst = os.path.join(dummy_package_path, dst) + shutil.copy(os.path.join(pycache, f), dst) try: yield path finally: @@ -56,7 +54,6 @@ def pyc_project_path(tmpdir): @pytest.mark.parametrize('load_unsafe_extensions', [False, True]) -@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL") def test_pyc(pyc_project_path, environment, load_unsafe_extensions): """ The list of completion must be greater than 2. diff --git a/test/test_inference/test_signature.py b/test/test_inference/test_signature.py index f771beec..500759bc 100644 --- a/test/test_inference/test_signature.py +++ b/test/test_inference/test_signature.py @@ -11,21 +11,21 @@ from ..helpers import get_example_dir @pytest.mark.parametrize( 'code, sig, names, op, version', [ - ('import math; math.cos', 'cos(x, /)', ['x'], ge, (2, 7)), + ('import math; math.cos', 'cos(x, /)', ['x'], ge, (3, 6)), - ('next', 'next(iterator, default=None, /)', ['iterator', 'default'], ge, (2, 7)), + ('next', 'next(iterator, default=None, /)', ['iterator', 'default'], ge, (3, 6)), - ('str', "str(object='', /) -> str", ['object'], ge, (2, 7)), + ('str', "str(object='', /) -> str", ['object'], ge, (3, 6)), - ('pow', 'pow(x, y, z=None, /) -> number', ['x', 'y', 'z'], lt, (3, 5)), + ('pow', 'pow(x, y, z=None, /) -> number', ['x', 'y', 'z'], lt, (3, 6)), ('pow', 'pow(base, exp, mod=None)', ['base', 'exp', 'mod'], ge, (3, 8)), ('bytes.partition', 'partition(self, sep, /) -> (head, sep, tail)', - ['self', 'sep'], lt, (3, 5)), - ('bytes.partition', 'partition(self, sep, /)', ['self', 'sep'], ge, (3, 5)), + ['self', 'sep'], lt, (3, 6)), + ('bytes.partition', 'partition(self, sep, /)', ['self', 'sep'], ge, (3, 6)), - ('bytes().partition', 'partition(sep, /) -> (head, sep, tail)', ['sep'], lt, (3, 5)), - ('bytes().partition', 'partition(sep, /)', ['sep'], ge, (3, 5)), + ('bytes().partition', 'partition(sep, /) -> (head, sep, tail)', ['sep'], lt, (3, 6)), + ('bytes().partition', 'partition(sep, /)', ['sep'], ge, (3, 6)), ] ) def test_compiled_signature(Script, environment, code, sig, names, op, version): @@ -175,7 +175,7 @@ def test_tree_signature(Script, environment, code, expected): ('no_redirect(simple)', '*args, **kwargs'), ] ) -def test_nested_signatures(Script, environment, combination, expected, skip_pre_python35): +def test_nested_signatures(Script, environment, combination, expected): code = dedent(''' def simple(a, b, *, c): ... def simple2(x): ... @@ -265,7 +265,7 @@ def test_pow_signature(Script): x(f)('''), 'f()'], ] ) -def test_wraps_signature(Script, code, signature, skip_pre_python35): +def test_wraps_signature(Script, code, signature): sigs = Script(code).get_signatures() assert {sig.to_string() for sig in sigs} == {signature} @@ -315,7 +315,7 @@ def test_dataclass_signature(Script, skip_pre_python37, start, start_params): ('kwargs = dict(b=3)', 'wrapped(b, /, **kwargs)'), ] ) -def test_param_resolving_to_static(Script, stmt, expected, skip_pre_python35): +def test_param_resolving_to_static(Script, stmt, expected): code = dedent('''\ def full_redirect(func): def wrapped(*args, **kwargs): diff --git a/test/test_inference/test_stdlib.py b/test/test_inference/test_stdlib.py index 0e910d7f..151fd8f6 100644 --- a/test/test_inference/test_stdlib.py +++ b/test/test_inference/test_stdlib.py @@ -90,14 +90,8 @@ def test_re_sub(Script, environment): return {d.name for d in defs} names = run("import re; re.sub('a', 'a', 'f')") - if environment.version_info.major == 2: - assert names == {'str'} - else: - assert names == {'str'} + assert names == {'str'} # This param is missing because of overloading. names = run("import re; re.sub('a', 'a')") - if environment.version_info.major == 2: - assert names == {'str', 'unicode'} - else: - assert names == {'str', 'bytes'} + assert names == {'str', 'bytes'} diff --git a/test/test_inference/test_sys_path.py b/test/test_inference/test_sys_path.py index 300d7935..2fa0e4df 100644 --- a/test/test_inference/test_sys_path.py +++ b/test/test_inference/test_sys_path.py @@ -2,6 +2,7 @@ import os from glob import glob import sys import shutil +from pathlib import Path import pytest @@ -17,9 +18,9 @@ def test_paths_from_assignment(Script): return set(sys_path._paths_from_assignment(script._get_module_context(), expr_stmt)) # Normalize paths for Windows. - path_a = os.path.abspath('/foo/a') - path_b = os.path.abspath('/foo/b') - path_c = os.path.abspath('/foo/c') + path_a = Path('/foo/a').absolute() + path_b = Path('/foo/b').absolute() + path_c = Path('/foo/c').absolute() assert paths('sys.path[0:0] = ["a"]') == {path_a} assert paths('sys.path = ["b", 1, x + 3, y, "c"]') == {path_b, path_c} @@ -105,5 +106,5 @@ def test_transform_path_to_dotted(sys_path_, module_path, expected, is_package): # transform_path_to_dotted expects normalized absolute paths. sys_path_ = [os.path.abspath(path) for path in sys_path_] module_path = os.path.abspath(module_path) - assert sys_path.transform_path_to_dotted(sys_path_, module_path) \ + assert sys_path.transform_path_to_dotted(sys_path_, Path(module_path)) \ == (expected, is_package) diff --git a/test/test_integration.py b/test/test_integration.py index b55aca77..2e5703aa 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -1,5 +1,4 @@ import os -import sys import pytest @@ -36,18 +35,12 @@ unspecified = %s """ % (case, sorted(d - a), sorted(a - d)) -def test_completion(case, monkeypatch, environment, has_typing, has_django): +def test_completion(case, monkeypatch, environment, has_django): skip_reason = case.get_skip_reason(environment) if skip_reason is not None: pytest.skip(skip_reason) - if 'pep0484_typing' in case.path and sys.version_info[0] == 2: - pytest.skip('ditch python 2 finally') - - _CONTAINS_TYPING = ('pep0484_typing', 'pep0484_comments', 'pep0526_variables') - if not has_typing and any(x in case.path for x in _CONTAINS_TYPING): - pytest.skip('Needs the typing module installed to run this test.') - if (not has_django or environment.version_info.major == 2) and case.path.endswith('django.py'): + if (not has_django) and case.path.endswith('django.py'): pytest.skip('Needs django to be installed to run this test.') repo_root = helpers.root_dir monkeypatch.chdir(os.path.join(repo_root, 'jedi')) @@ -62,15 +55,12 @@ def test_static_analysis(static_analysis_case, environment): static_analysis_case.run(assert_static_analysis, environment) -def test_refactor(refactor_case, skip_pre_python36, environment): +def test_refactor(refactor_case, environment): """ Run refactoring test case. :type refactor_case: :class:`.refactor.RefactoringCase` """ - if sys.version_info < (3, 6): - pytest.skip() - desired_result = refactor_case.get_desired_result() if refactor_case.type == 'error': with pytest.raises(RefactoringError) as e: diff --git a/test/test_parso_integration/test_basic.py b/test/test_parso_integration/test_basic.py index f2831dc5..cf174355 100644 --- a/test/test_parso_integration/test_basic.py +++ b/test/test_parso_integration/test_basic.py @@ -87,9 +87,6 @@ def test_tokenizer_with_string_literal_backslash(Script): def test_ellipsis_without_getitem(Script, environment): - if environment.version_info.major == 2: - pytest.skip('In 2.7 Ellipsis can only be used like x[...]') - def_, = Script('x=...;x').infer() assert def_.name == 'ellipsis' diff --git a/test/test_parso_integration/test_parser_utils.py b/test/test_parso_integration/test_parser_utils.py index b5a3167a..0a744639 100644 --- a/test/test_parso_integration/test_parser_utils.py +++ b/test/test_parso_integration/test_parser_utils.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- -from jedi._compatibility import is_py3 from jedi import parser_utils from parso import parse from parso.python import tree @@ -55,10 +53,7 @@ def test_hex_values_in_docstring(): ''' doc = parser_utils.clean_scope_docstring(next(parse(source).iter_funcdefs())) - if is_py3: - assert doc == '\xff' - else: - assert doc == u'�' + assert doc == '\xff' @pytest.mark.parametrize( @@ -68,7 +63,7 @@ def test_hex_values_in_docstring(): ('lambda x, y, z: x + y * z\n', '(x, y, z)') ]) def test_get_signature(code, signature): - node = parse(code, version='3.5').children[0] + node = parse(code, version='3.8').children[0] if node.type == 'simple_stmt': node = node.children[0] assert parser_utils.get_signature(node) == signature diff --git a/test/test_utils.py b/test/test_utils.py index 14ca802f..c6198401 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -2,11 +2,10 @@ try: import readline except ImportError: readline = False +import unittest from jedi import utils -from .helpers import unittest - @unittest.skipIf(not readline, "readline not found") class TestSetupReadline(unittest.TestCase): @@ -14,7 +13,7 @@ class TestSetupReadline(unittest.TestCase): pass def __init__(self, *args, **kwargs): - super(type(self), self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.namespace = self.NameSpace() utils.setup_readline(self.namespace) diff --git a/tox.ini b/tox.ini index a24e5c99..a8304353 100644 --- a/tox.ini +++ b/tox.ini @@ -1,14 +1,11 @@ [tox] -envlist = py27, py35, py36, py37, py38, qa +envlist = py36, py37, py38, qa [testenv] extras = testing deps = -# for testing the typing module - py27: typing # numpydoc for typing scipy stack numpydoc -# sphinx, a dependency of numpydoc, dropped Python 2 support in version 2.0 - sphinx < 2.0 + sphinx cov: coverage # Overwrite the parso version (only used sometimes). git+https://github.com/davidhalter/parso.git @@ -21,8 +18,6 @@ setenv = PYTHONWARNINGS=always # To test Jedi in different versions than the same Python version, set a # different test environment. - env27: JEDI_TEST_ENVIRONMENT=27 - env35: JEDI_TEST_ENVIRONMENT=35 env36: JEDI_TEST_ENVIRONMENT=36 env37: JEDI_TEST_ENVIRONMENT=37 env38: JEDI_TEST_ENVIRONMENT=38