1
0
forked from VimPlug/jedi

Start using pathlib.Path instead of all the os.path functions

This commit is contained in:
Dave Halter
2020-07-10 17:30:36 +02:00
parent 92af043906
commit db0e90763b
12 changed files with 97 additions and 104 deletions

View File

@@ -7,9 +7,9 @@ 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 debug messages to stdout, simply call :func:`set_debug_function` without
arguments. arguments.
""" """
import os
import sys import sys
import warnings import warnings
from pathlib import Path
import parso import parso
from parso.python import tree from parso.python import tree
@@ -96,7 +96,7 @@ class Script(object):
:type column: int :type column: int
:param path: The path of the file in the file system, or ``''`` if :param path: The path of the file in the file system, or ``''`` if
it hasn't been saved yet. it hasn't been saved yet.
:type path: str or None :type path: str or pathlib.Path or None
:param sys_path: Deprecated, use the project parameter. :param sys_path: Deprecated, use the project parameter.
:type sys_path: typing.List[str] :type sys_path: typing.List[str]
:param Environment environment: Provide a predefined :ref:`Environment <environments>` :param Environment environment: Provide a predefined :ref:`Environment <environments>`
@@ -109,7 +109,10 @@ class Script(object):
sys_path=None, environment=None, project=None, source=None): sys_path=None, environment=None, project=None, source=None):
self._orig_path = path self._orig_path = path
# An empty path (also empty string) should always result in no 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 line is not None: if line is not None:
warnings.warn( warnings.warn(
@@ -139,9 +142,7 @@ class Script(object):
if project is None: if project is None:
# Load the Python grammar of the current interpreter. # Load the Python grammar of the current interpreter.
project = get_default_project( project = get_default_project(self.path)
os.path.dirname(self.path) if path else None
)
# TODO deprecate and remove sys_path from the Script API. # TODO deprecate and remove sys_path from the Script API.
if sys_path is not None: if sys_path is not None:
project._sys_path = sys_path project._sys_path = sys_path
@@ -159,7 +160,7 @@ class Script(object):
self._module_node, code = self._inference_state.parse_and_get_code( self._module_node, code = self._inference_state.parse_and_get_code(
code=code, code=code,
path=self.path, path=self.path,
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. cache=False, # No disk cache, because the current script often changes.
diff_cache=settings.fast_parser, diff_cache=settings.fast_parser,
cache_path=settings.cache_directory, cache_path=settings.cache_directory,
@@ -191,7 +192,7 @@ class Script(object):
file_io = None file_io = None
else: else:
file_io = KnownContentFileIO(cast_path(self.path), self._code) 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. # We are in a stub file. Try to load the stub properly.
stub_module = load_proper_stub_module( stub_module = load_proper_stub_module(
self._inference_state, self._inference_state,
@@ -798,7 +799,7 @@ class Interpreter(Script):
raise TypeError("The environment needs to be an InterpreterEnvironment subclass.") raise TypeError("The environment needs to be an InterpreterEnvironment subclass.")
super().__init__(code, environment=environment, super().__init__(code, environment=environment,
project=Project(os.getcwd()), **kwds) project=Project(Path.cwd()), **kwds)
self.namespaces = namespaces self.namespaces = namespaces
self._inference_state.allow_descriptor_getattr = self._allow_descriptor_getattr_default self._inference_state.allow_descriptor_getattr = self._allow_descriptor_getattr_default
@@ -806,7 +807,7 @@ class Interpreter(Script):
def _get_module_context(self): def _get_module_context(self):
tree_module_value = ModuleValue( tree_module_value = ModuleValue(
self._inference_state, self._module_node, self._inference_state, self._module_node,
file_io=KnownContentFileIO(self.path, self._code), file_io=KnownContentFileIO(str(self.path), self._code),
string_names=('__main__',), string_names=('__main__',),
code_lines=self._code_lines, code_lines=self._code_lines,
) )
@@ -841,7 +842,7 @@ def preload_module(*modules):
""" """
for m in modules: for m in modules:
s = "import %s as x; x." % m 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, def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,

View File

@@ -44,7 +44,10 @@ def match(string, like_name, fuzzy=False):
def sorted_definitions(defs): def sorted_definitions(defs):
# Note: `or ''` below is required because `module_path` could be # 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): def get_on_completion_name(module_node, lines, position):

View File

@@ -7,9 +7,9 @@ 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 Projects can be saved to disk and loaded again, to allow project definitions to
be used across repositories. be used across repositories.
""" """
import os
import errno
import json import json
from pathlib import Path
from itertools import chain
from jedi import debug from jedi import debug
from jedi.api.environment import get_cached_default_environment, create_environment from jedi.api.environment import get_cached_default_environment, create_environment
@@ -22,7 +22,6 @@ from jedi.inference.sys_path import discover_buildout_paths
from jedi.inference.cache import inference_state_as_method_param_cache 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.inference.references import recurse_find_python_folders_and_files, search_in_file_ios
from jedi.file_io import FolderIO from jedi.file_io import FolderIO
from jedi.common import traverse_parents
_CONFIG_FOLDER = '.jedi' _CONFIG_FOLDER = '.jedi'
_CONTAINS_POTENTIAL_PROJECT = \ _CONTAINS_POTENTIAL_PROJECT = \
@@ -67,11 +66,11 @@ class Project(object):
@staticmethod @staticmethod
def _get_config_folder_path(base_path): def _get_config_folder_path(base_path):
return os.path.join(base_path, _CONFIG_FOLDER) return base_path.joinpath(_CONFIG_FOLDER)
@staticmethod @staticmethod
def _get_json_path(base_path): 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 @classmethod
def load(cls, path): def load(cls, path):
@@ -100,12 +99,7 @@ class Project(object):
data.pop('_django', None) # TODO make django setting public? data.pop('_django', None) # TODO make django setting public?
data = {k.lstrip('_'): v for k, v in data.items()} data = {k.lstrip('_'): v for k, v in data.items()}
# TODO when dropping Python 2 use pathlib.Path.mkdir(parents=True, exist_ok=True) self._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
with open(self._get_json_path(self._path), 'w') as f: with open(self._get_json_path(self._path), 'w') as f:
return json.dump((_SERIALIZER_VERSION, data), f) return json.dump((_SERIALIZER_VERSION, data), f)
@@ -130,7 +124,9 @@ class Project(object):
""" """
def py2_comp(path, environment_path=None, load_unsafe_extensions=False, def py2_comp(path, environment_path=None, load_unsafe_extensions=False,
sys_path=None, added_sys_path=(), smart_sys_path=True): 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 self._environment_path = environment_path
self._sys_path = sys_path self._sys_path = sys_path
@@ -174,23 +170,27 @@ class Project(object):
sys_path = list(self._sys_path) sys_path = list(self._sys_path)
if self._smart_sys_path: if self._smart_sys_path:
prefixed.append(self._path) prefixed.append(str(self._path))
if inference_state.script_path is not None: if inference_state.script_path is not None:
suffixed += discover_buildout_paths(inference_state, inference_state.script_path) suffixed += discover_buildout_paths(
inference_state,
inference_state.script_path
)
if add_parent_paths: if add_parent_paths:
# Collect directories in upward search by: # Collect directories in upward search by:
# 1. Skipping directories with __init__.py # 1. Skipping directories with __init__.py
# 2. Stopping immediately when above self._path # 2. Stopping immediately when above self._path
traversed = [] traversed = []
for parent_path in traverse_parents(inference_state.script_path): for parent_path in inference_state.script_path.parents:
if parent_path == self._path or not parent_path.startswith(self._path): if parent_path == self._path \
or self._path not in parent_path.parents:
break break
if not add_init_paths \ 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 continue
traversed.append(parent_path) traversed.append(str(parent_path))
# AFAIK some libraries have imports like `foo.foo.bar`, which # AFAIK some libraries have imports like `foo.foo.bar`, which
# leads to the conclusion to by default prefer longer paths # leads to the conclusion to by default prefer longer paths
@@ -198,7 +198,7 @@ class Project(object):
suffixed += reversed(traversed) suffixed += reversed(traversed)
if self._django: if self._django:
prefixed.append(self._path) prefixed.append(str(self._path))
path = prefixed + sys_path + suffixed path = prefixed + sys_path + suffixed
return list(_remove_duplicates_from_path(path)) return list(_remove_duplicates_from_path(path))
@@ -259,7 +259,7 @@ class Project(object):
name = wanted_names[0] name = wanted_names[0]
stub_folder_name = name + '-stubs' 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 = [] file_ios = []
# 1. Search for modules in the current project # 1. Search for modules in the current project
@@ -280,8 +280,7 @@ class Project(object):
continue continue
else: else:
file_ios.append(file_io) file_ios.append(file_io)
file_name = os.path.basename(file_io.path) if Path(file_io.path).name in (name + '.py', name + '.pyi'):
if file_name in (name + '.py', name + '.pyi'):
m = load_module_from_path(inference_state, file_io).as_context() m = load_module_from_path(inference_state, file_io).as_context()
else: else:
continue continue
@@ -318,7 +317,7 @@ class Project(object):
p for p in self._get_sys_path(inference_state) p for p in self._get_sys_path(inference_state)
# Exclude folders that are handled by recursing of the Python # Exclude folders that are handled by recursing of the Python
# folders. # 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)) names = list(iter_module_names(inference_state, empty_module_context, sys_path))
yield from search_in_module( yield from search_in_module(
@@ -337,7 +336,7 @@ class Project(object):
def _is_potential_project(path): def _is_potential_project(path):
for name in _CONTAINS_POTENTIAL_PROJECT: for name in _CONTAINS_POTENTIAL_PROJECT:
if os.path.exists(os.path.join(path, name)): if path.joinpath(name).exists():
return True return True
return False return False
@@ -345,7 +344,7 @@ def _is_potential_project(path):
def _is_django_path(directory): def _is_django_path(directory):
""" Detects the path of the very well known Django library (if used) """ """ Detects the path of the very well known Django library (if used) """
try: 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() return b"DJANGO_SETTINGS_MODULE" in f.read()
except (FileNotFoundError, IsADirectoryError, PermissionError): except (FileNotFoundError, IsADirectoryError, PermissionError):
return False return False
@@ -362,12 +361,12 @@ def get_default_project(path=None):
``requirements.txt`` and ``MANIFEST.in``. ``requirements.txt`` and ``MANIFEST.in``.
""" """
if path is None: if path is None:
path = os.getcwd() path = Path.cwd()
check = os.path.realpath(path) check = path.absolute()
probable_path = None probable_path = None
first_no_init_file = None first_no_init_file = None
for dir in traverse_parents(check, include_current=True): for dir in chain([check], check.parents):
try: try:
return Project.load(dir) return Project.load(dir)
except (FileNotFoundError, IsADirectoryError, PermissionError): except (FileNotFoundError, IsADirectoryError, PermissionError):
@@ -376,7 +375,7 @@ def get_default_project(path=None):
continue continue
if first_no_init_file is None: 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 # 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. # Python package and the project sits at least one level above.
continue continue
@@ -398,7 +397,7 @@ def get_default_project(path=None):
if first_no_init_file is not None: if first_no_init_file is not None:
return Project(first_no_init_file) 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) return Project(curdir)

View File

@@ -1,6 +1,3 @@
from os.path import dirname, basename, join, relpath
import os
import re
import difflib import difflib
from parso import split_lines from parso import split_lines
@@ -43,11 +40,11 @@ class ChangedFile(object):
if self._from_path is None: if self._from_path is None:
from_p = '' from_p = ''
else: else:
from_p = relpath(self._from_path, project_path) from_p = self._from_path.relative_to(project_path)
if self._to_path is None: if self._to_path is None:
to_p = '' to_p = ''
else: else:
to_p = relpath(self._to_path, project_path) to_p = self._to_path.relative_to(project_path)
diff = difflib.unified_diff( diff = difflib.unified_diff(
old_lines, new_lines, old_lines, new_lines,
fromfile=from_p, fromfile=from_p,
@@ -115,7 +112,7 @@ class Refactoring(object):
project_path = self._inference_state.project.path project_path = self._inference_state.project.path
for from_, to in self.get_renames(): for from_, to in self.get_renames():
text += 'rename from %s\nrename to %s\n' \ 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()) return text + ''.join(f.get_diff() for f in self.get_changed_files().values())
@@ -127,17 +124,14 @@ class Refactoring(object):
f.apply() f.apply()
for old, new in self.get_renames(): for old, new in self.get_renames():
os.rename(old, new) old.rename(new)
def _calculate_rename(path, new_name): def _calculate_rename(path, new_name):
name = basename(path) dir_ = path.parent
dir_ = dirname(path) if path.name in ('__init__.py', '__init__.pyi'):
if name in ('__init__.py', '__init__.pyi'): return dir_, dir_.parent.joinpath(new_name)
parent_dir = dirname(dir_) return path, dir_.joinpath(new_name + path.suffix)
return dir_, join(parent_dir, new_name)
ending = re.search(r'\.pyi?$', name).group(0)
return path, join(dir_, new_name + ending)
def rename(inference_state, definitions, new_name): def rename(inference_state, definitions, new_name):

View File

@@ -1,18 +1,6 @@
import os
from contextlib import contextmanager 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 @contextmanager
def monkeypatch(obj, attribute_name, new_value): def monkeypatch(obj, attribute_name, new_value):
""" """

View File

@@ -178,6 +178,8 @@ class InferenceState(object):
def parse_and_get_code(self, code=None, path=None, def parse_and_get_code(self, code=None, path=None,
use_latest_grammar=False, file_io=None, **kwargs): use_latest_grammar=False, file_io=None, **kwargs):
if path is not None:
path = str(path)
if code is None: if code is None:
if file_io is None: if file_io is None:
file_io = FileIO(path) file_io = FileIO(path)

View File

@@ -44,7 +44,7 @@ def create_simple_object(inference_state, obj):
Only allows creations of objects that are easily picklable across Python Only allows creations of objects that are easily picklable across Python
versions. versions.
""" """
assert type(obj) in (int, float, str, bytes, slice, complex, bool), obj assert type(obj) in (int, float, str, bytes, slice, complex, bool), repr(obj)
compiled_value = create_from_access_path( compiled_value = create_from_access_path(
inference_state, inference_state,
inference_state.compiled_subprocess.create_simple_object(obj) inference_state.compiled_subprocess.create_simple_object(obj)

View File

@@ -196,8 +196,8 @@ def _try_to_load_stub(inference_state, import_names, python_value_set,
file_paths = [] file_paths = []
if c.is_namespace(): if c.is_namespace():
file_paths = [os.path.join(p, '__init__.pyi') for p in c.py__path__()] 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'): elif file_path is not None and file_path.suffix == '.py':
file_paths = [file_path + 'i'] file_paths = [str(file_path) + 'i']
for file_path in file_paths: for file_path in file_paths:
m = _try_to_load_stub_from_file( m = _try_to_load_stub_from_file(

View File

@@ -12,11 +12,12 @@ def is_stdlib_path(path):
# Python standard library paths look like this: # Python standard library paths look like this:
# /usr/lib/python3.9/... # /usr/lib/python3.9/...
# TODO The implementation below is probably incorrect and not complete. # 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 return False
base_path = os.path.join(sys.prefix, 'lib', 'python') 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): def deep_ast_copy(obj):

View File

@@ -9,6 +9,7 @@ This module also supports import autocompletion, which means to complete
statements like ``from datetim`` (cursor at the end would return ``datetime``). statements like ``from datetim`` (cursor at the end would return ``datetime``).
""" """
import os import os
from pathlib import Path
from parso.python import tree from parso.python import tree
from parso.tree import search_ancestor from parso.tree import search_ancestor
@@ -237,7 +238,10 @@ class Importer(object):
# inference we want to show the user as much as possible. # inference we want to show the user as much as possible.
# See GH #1446. # See GH #1446.
self._inference_state.get_sys_path(add_init_paths=not is_completion) 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): def follow(self):
@@ -467,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 here to ensure that a random path is still properly loaded into the Jedi
module structure. module structure.
""" """
path = file_io.path path = Path(file_io.path)
if import_names is None: if import_names is None:
e_sys_path = inference_state.get_sys_path() e_sys_path = inference_state.get_sys_path()
import_names, is_package = sys_path.transform_path_to_dotted(e_sys_path, path) import_names, is_package = sys_path.transform_path_to_dotted(e_sys_path, path)
else: else:
assert isinstance(is_package, bool) assert isinstance(is_package, bool)
is_stub = file_io.path.endswith('.pyi') is_stub = path.suffix == '.pyi'
if is_stub: if is_stub:
folder_io = file_io.get_parent_folder() folder_io = file_io.get_parent_folder()
if folder_io.path.endswith('-stubs'): if folder_io.path.endswith('-stubs'):
folder_io = FolderIO(folder_io.path[:-6]) 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') python_file_io = folder_io.get_file_io('__init__.py')
else: else:
python_file_io = folder_io.get_file_io(import_names[-1] + '.py') python_file_io = folder_io.get_file_io(import_names[-1] + '.py')
@@ -510,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): def load_namespace_from_path(inference_state, folder_io):
import_names, is_package = sys_path.transform_path_to_dotted( import_names, is_package = sys_path.transform_path_to_dotted(
inference_state.get_sys_path(), inference_state.get_sys_path(),
folder_io.path Path(folder_io.path)
) )
from jedi.inference.value.namespace import ImplicitNamespaceValue from jedi.inference.value.namespace import ImplicitNamespaceValue
return ImplicitNamespaceValue(inference_state, import_names, [folder_io.path]) return ImplicitNamespaceValue(inference_state, import_names, [folder_io.path])

View File

@@ -1,11 +1,11 @@
import os import os
import re import re
from pathlib import Path, PurePath
from importlib.machinery import all_suffixes from importlib.machinery import all_suffixes
from jedi.inference.cache import inference_state_method_cache from jedi.inference.cache import inference_state_method_cache
from jedi.inference.base_value import ContextualizedNode from jedi.inference.base_value import ContextualizedNode
from jedi.inference.helpers import is_string, get_str_or_none 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.parser_utils import get_cached_code_lines
from jedi.file_io import FileIO from jedi.file_io import FileIO
from jedi import settings from jedi import settings
@@ -14,8 +14,9 @@ from jedi import debug
_BUILDOUT_PATH_INSERTION_LIMIT = 10 _BUILDOUT_PATH_INSERTION_LIMIT = 10
def _abs_path(module_context, path): def _abs_path(module_context, path: str):
if os.path.isabs(path): path = PurePath(path)
if path.is_absolute():
return path return path
module_path = module_context.py__file__() module_path = module_context.py__file__()
@@ -24,8 +25,8 @@ def _abs_path(module_context, path):
# system. # system.
return None return None
base_dir = os.path.dirname(module_path) base_dir = module_path.parent
return os.path.abspath(os.path.join(base_dir, path)) return base_dir.joinpath(path).absolute()
def _paths_from_assignment(module_context, expr_stmt): def _paths_from_assignment(module_context, expr_stmt):
@@ -169,14 +170,14 @@ def _get_paths_from_buildout_script(inference_state, buildout_script_path):
yield path yield path
def _get_parent_dir_with_file(path, filename): def _get_parent_dir_with_file(path: Path, filename):
for parent in traverse_parents(path): for parent in path.parents:
if os.path.isfile(os.path.join(parent, filename)): if parent.joinpath(filename).is_file():
return parent return parent
return None 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 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 given module it will return a list of all files in the buildout bin
@@ -188,13 +189,13 @@ def _get_buildout_script_paths(search_path):
project_root = _get_parent_dir_with_file(search_path, 'buildout.cfg') project_root = _get_parent_dir_with_file(search_path, 'buildout.cfg')
if not project_root: if not project_root:
return return
bin_path = os.path.join(project_root, 'bin') bin_path = project_root.joinpath('bin')
if not os.path.exists(bin_path): if not bin_path.exists():
return return
for filename in os.listdir(bin_path): for filename in os.listdir(bin_path):
try: try:
filepath = os.path.join(bin_path, filename) filepath = bin_path.joinpath(filename)
with open(filepath, 'r') as f: with open(filepath, 'r') as f:
firstline = f.readline() firstline = f.readline()
if firstline.startswith('#!') and 'python' in firstline: if firstline.startswith('#!') and 'python' in firstline:
@@ -208,8 +209,8 @@ def _get_buildout_script_paths(search_path):
def remove_python_path_suffix(path): def remove_python_path_suffix(path):
for suffix in all_suffixes() + ['.pyi']: for suffix in all_suffixes() + ['.pyi']:
if path.endswith(suffix): if path.suffix == suffix:
path = path[:-len(suffix)] path = path.with_name(path.stem)
break break
return path return path
@@ -232,16 +233,15 @@ def transform_path_to_dotted(sys_path, module_path):
# means that if someone uses an ending like .vim for a Python file, .vim # means that if someone uses an ending like .vim for a Python file, .vim
# will be part of the returned dotted part. # 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: if is_package:
# -1 to remove the separator module_path = module_path.parent
module_path = module_path[:-len('__init__') - 1]
def iter_potential_solutions(): def iter_potential_solutions():
for p in sys_path: for p in sys_path:
if module_path.startswith(p): if str(module_path).startswith(p):
# Strip the trailing slash/backslash # 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. # On Windows a path can also use a slash.
if rest.startswith(os.path.sep) or rest.startswith('/'): if rest.startswith(os.path.sep) or rest.startswith('/'):
# Remove a slash in cases it's still there. # Remove a slash in cases it's still there.

View File

@@ -1,4 +1,5 @@
import os import os
from pathlib import Path
from jedi.inference.cache import inference_state_method_cache from jedi.inference.cache import inference_state_method_cache
from jedi.inference.names import AbstractNameDefinition, ModuleName from jedi.inference.names import AbstractNameDefinition, ModuleName
@@ -89,9 +90,9 @@ class ModuleMixin(SubModuleDictMixin):
names = ['__package__', '__doc__', '__name__'] names = ['__package__', '__doc__', '__name__']
# All the additional module attributes are strings. # All the additional module attributes are strings.
dct = dict((n, _ModuleAttributeName(self, n)) for n in names) dct = dict((n, _ModuleAttributeName(self, n)) for n in names)
file = self.py__file__() path = self.py__file__()
if file is not None: if path is not None:
dct['__file__'] = _ModuleAttributeName(self, '__file__', file) dct['__file__'] = _ModuleAttributeName(self, '__file__', str(path))
return dct return dct
def iter_star_filters(self): def iter_star_filters(self):
@@ -147,13 +148,13 @@ class ModuleValue(ModuleMixin, TreeValue):
if file_io is None: if file_io is None:
self._path = None self._path = None
else: else:
self._path = file_io.path self._path = Path(file_io.path)
self.string_names = string_names # Optional[Tuple[str, ...]] self.string_names = string_names # Optional[Tuple[str, ...]]
self.code_lines = code_lines self.code_lines = code_lines
self._is_package = is_package self._is_package = is_package
def is_stub(self): 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 # 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 # used in them. This could be changed if stubs would be identified
# sooner and used as StubModuleValue. # sooner and used as StubModuleValue.
@@ -165,14 +166,14 @@ class ModuleValue(ModuleMixin, TreeValue):
return None return None
return '.'.join(self.string_names) return '.'.join(self.string_names)
def py__file__(self): def py__file__(self) -> Path:
""" """
In contrast to Python's __file__ can be None. In contrast to Python's __file__ can be None.
""" """
if self._path is None: if self._path is None:
return None return None
return os.path.abspath(self._path) return self._path.absolute()
def is_package(self): def is_package(self):
return self._is_package return self._is_package