diff --git a/python/helpers/python-skeletons/README.md b/python/helpers/python-skeletons/README.md new file mode 100644 index 000000000000..e557a519e22e --- /dev/null +++ b/python/helpers/python-skeletons/README.md @@ -0,0 +1,139 @@ +Python Skeletons +================ + +_This proposal is a draft._ + +Python skeletons are Python files that contain API definitions of existing +libraries extended for static analysis tools. + +Rationale +--------- + +Python is a dynamic language less suitable for static code analysis than static +languages like C or Java. Although Python static analysis tools can extract +some information from Python source code without executing it, this information +is often very shallow and incomplete. + +Dynamic features of Python are very useful for user code. But using these +features in APIs of third-party libraries and the standard library is not +always a good idea. Tools (and users, in fact) need clear definitions of APIs. +Often library API definitions are quite static and easy to grasp (defined +using `class`, `def`), but types of function parameters and return values +usually are not specified. Sometimes API definitions involve metaprogramming. + +As there is not enough information in API definition code of libraries, +developers of static analysis tools collect extended API data themselves and +store it in their own formats. For example, PyLint uses imperative AST +transformations of API modules in order to extend them with hard-coded data. +PyCharm extends APIs via its proprietary database of declarative type +annotations. The absence of a common extended API information format makes it +hard for developers and users of tools to collect and share data. + + +Proposal +-------- + +The proposal is to create a common database of extended API definitions as a +collection of Python files called skeletons. Static analysis tools already +understand Python code, so it should be easy to start extracting API +definitions from these Python skeleton files. Regular function and class +definitions can be extended with additional docstrings and decorators, e.g. for +providing types of function parameters and return values. Static analysis tools +may use a subset of information contained in skeleton files needed for their +operation. Using Python files instead of a custom API definition format will +also make it easier for users to populate the skeletons database. + +Declarative Python API definitions for static analysis tools cannot cover all +dynamic tricks used in real APIs of libraries: some of them still require +library-specific code analysis. Nevertheless the skeletons database is enough +for many libraries. + +The proposed [python-skeletons](https://github.com/JetBrains/python-skeletons) +repository is hosted on GitHub. + + +Conventions +----------- + +Skeletons should respect PEP-8 and PEP-257 style guides. + +The most simple way of specifying types in skeletons is Sphinx docstrings. +Function annotations could be used for specifying types, but they are +available only for Python 3. + +There is no standard notation for specifying types in Python code. We propose +the following notation: + + Foo # Class Foo visible in the current scope + x.y.Bar # Class Bar from x.y module + Foo | Bar # Foo or Bar + (Foo, Bar) # Tuple of Foo and Bar + list[Foo] # List of Foo elements + dict[Foo, Bar] # Dict from Foo to Bar + T # Generic type (T-Z are reserved for generics) + T <= Foo # Generic type with upper bound Foo + Foo[T] # Foo parameterized with T + (Foo, Bar) -> Baz # Function of Foo and Bar that returns Baz + +The formal syntax is defined in `pytypes` library (work in progress). + +There are several shortcuts available: + + unknown # Unknown type + None # type(None) + string # Py2: str | unicode, Py3: str + bytestring # Py2: str | unicode, Py3: bytes + bytes # Py2: str, Py3: bytes + unicode # Py2: unicode, Py3: str + +The syntax is a subject to change. It is almost compatible to Python (except +function types), but its semantics differs from Python (no `|`, no implicitly +visible names, no generic types). So you cannot use these expressions in +Python 3 function annotations. See also `python-righarrow`, `typeannotations`. + +The recommended way of checking the version of Python is: + + import sys + + if sys.version_info >= (2, 7) and sys.version_info < (3,): + def from_27_until_30(): + pass + + +PyCharm +------- + +PyCharm 3 can extract the following information from the skeletons: + +* Parameters of functions and methods +* Return types and parameter types of functions and methods +* Types of assignment targets +* Extra module members +* TODO + +PyCharm 3 comes with a snapshot of the Python skeletons repository. You +should not modify it, because it will be updated with the PyCharm +installation. If you want to change the skeletons, clone the skeletons GitHub +repository into your PyCharm config directory: + + cd + git clone https://github.com/JetBrains/python-skeletons.git + +where `` is: + +* Mac OS X: `~/Library/Preferences/PyCharmXX/config` +* Linux: `~/.PyCharmXX/config` +* Windows: `\.PyCharmXX\config` + +Please send your PyCharm-related bug reports and feature requests to +[PyCharm issue tracker](http://youtrack.jetbrains.com/issues/PY). + + +Feedback +-------- + +If you want to contribute, send your pull requests to the Python skeletons +repository on GitHub. Please make sure, that you follow the conventions above. + +Use [code-quality](http://mail.python.org/mailman/listinfo/code-quality) +mailing list to discuss Python skeletons. diff --git a/python/helpers/python-skeletons/__builtin__.py b/python/helpers/python-skeletons/__builtin__.py new file mode 100644 index 000000000000..682c491c7367 --- /dev/null +++ b/python/helpers/python-skeletons/__builtin__.py @@ -0,0 +1,377 @@ +"""Skeletons for built-in symbols.""" + +import sys as __sys + + +def abs(number): + """Return the absolute value of the argument. + + :type number: T + :rtype: T | unknown + """ + pass + + +def all(iterable): + """Return True if bool(x) is True for all values x in the iterable. + + :type iterable: collections.Iterable + :rtype: bool + """ + pass + + +def any(iterable): + """Return True if bool(x) is True for any x in the iterable. + + :type iterable: collections.Iterable + :rtype: bool + """ + pass + + +def bin(number): + """Return the binary representation of an integer or long integer. + + :type number: numbers.Number + :rtype: bytes + """ + pass + + +def callable(object): + """Return whether the object is callable (i.e., some kind of function). + Note that classes are callable, as are instances with a __call__() method. + + :rtype: bool + """ + pass + + +def chr(i): + """Return a string of one character with ordinal i; 0 <= i < 256. + + :type i: int + :rtype: string + """ + pass + + +def cmp(x, y): + """Return negative if xy. + + :rtype: int + """ + pass + + +def dir(object=None): + """If called without an argument, return the names in the current scope. + Else, return an alphabetized list of names comprising (some of) the attributes + of the given object, and of attributes reachable from it. + + :rtype: list[string] + """ + pass + + +def divmod(x, y): + """Return the tuple ((x-x%y)/y, x%y). + + :type x: numbers.Number + :type y: numbers.Number + :rtype: (int | long | float | unknown, int | long | float | unknown) + """ + pass + + +def filter(function_or_none, sequence): + """Return those items of sequence for which function(item) is true. If + function is None, return the items that are true. If sequence is a tuple + or string, return the same type, else return a list. + + :type function_or_none: collections.Callable | None + :type sequence: T <= list | collections.Iterable | bytes | unicode + :rtype: T + """ + pass + + +def getattr(object, name, default=None): + """Get a named attribute from an object; getattr(x, 'y') is equivalent to + x.y. When a default argument is given, it is returned when the attribute + doesn't exist; without it, an exception is raised in that case. + + :type name: string + :rtype: object | unknown + """ + pass + + +def globals(): + """Return the dictionary containing the current scope's global variables. + + :rtype: dict[string, unknown] + """ + pass + + +def hasattr(object, name): + """Return whether the object has an attribute with the given name. + + :type name: string + :rtype: bool + """ + pass + + +def hash(object): + """Return a hash value for the object. + + :rtype: int + """ + pass + + +def hex(number): + """Return the hexadecimal representation of an integer or long integer. + + :type number: numbers.Integral + :rtype: string + """ + pass + + +def id(object): + """Return the identity of an object. + + :rtype: int + """ + pass + + +def isinstance(object, class_or_type_or_tuple): + """Return whether an object is an instance of a class or of a subclass + thereof. + + :rtype: bool + """ + pass + + +def issubclass(C, B): + """Return whether class C is a subclass (i.e., a derived class) of class B. + + :rtype: bool + """ + pass + + +def iter(source, sentinel=None): + """Get an iterator from an object. In the first form, the argument must + supply its own iterator, or be a sequence. In the second form, the callable + is called until it returns the sentinel. + + :type source: collections.Iterable[T] + :rtype: collections.Iterator[T] + """ + pass + + +def len(object): + """Return the number of items of a sequence or mapping. + + :type object: collections.Sized + :rtype: int + """ + pass + + +def locals(): + """Update and return a dictionary containing the current scope's local + variables. + + :rtype: dict[string, unknown] + """ + pass + + +def map(function, sequence, *sequence_1): + """Return a list of the results of applying the function to the items of + the argument sequence(s). + + :type function: ((T) -> V) | None + :type sequence: collections.Iterable[T] + :rtype: list[V] | bytes | unicode + """ + pass + + +def next(iterator, default=None): + """Return the next item from the iterator. + + :type iterator: collections.Iterator[T] + :rtype: T + """ + pass + + +def oct(number): + """Return the octal representation of an integer or long integer. + + :type number: numbers.Integral + :rtype: string + """ + pass + + +def open(name, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=None, opener=None): + """Open a file, returns a file object. + + :type name: string + :type mode: string + :type buffering: int + :type encoding: string | None + :type errors: string | None + :rtype: file + """ + pass + + +def ord(c): + """Return the integer ordinal of a one-character string. + + :type c: string + :rtype: int + """ + pass + + +def pow(x, y, z=None): + """With two arguments, equivalent to x**y. With three arguments, + equivalent to (x**y) % z, but may be more efficient (e.g. for longs). + + :type x: numbers.Number + :type y: numbers.Number + :type z: numbers.Number | None + :rtype: int | long | float | complex + """ + pass + + +if __sys.version_info < (3,): + def range(start, stop=None, step=None): + """Return a list containing an arithmetic progression of integers. + + :type start: numbers.Integral + :type stop: numbers.Integral | None + :type step: numbers.Integral | None + :rtype: list[int] + """ + pass + + +def reduce(function, sequence, initial=None): + """Apply a function of two arguments cumulatively to the items of a sequence, + from left to right, so as to reduce the sequence to a single value. + + :type function: collections.Callable + :type sequence: collections.Iterable + :type initial: T + :rtype: T | unknown + """ + pass + + +def repr(object): + """ + Return the canonical string representation of the object. + + :rtype: string + """ + pass + + +def round(number, ndigits=None): + """Round a number to a given precision in decimal digits (default 0 digits). + + :type number: numbers.Real + :type ndigits: numbers.Real | None + :rtype: float + """ + pass + + +class slice(object): + def __init__(self, start, stop=None, step=None): + """Create a slice object. This is used for extended slicing (e.g. a[0:10:2]). + + :type start: numbers.Integral + :type stop: numbers.Integral | None + :type step: numbers.Integral | None + """ + return + + +def vars(object=None): + """Without arguments, equivalent to locals(). With an argument, equivalent + to object.__dict__. + + :rtype: dict[string, unknown] + """ + pass + + +class object: + """ The most base type.""" + + @staticmethod + def __new__(cls, *more): + """Create a new object. + + :type cls: T + :rtype: T + """ + pass + + +class enumerate(object): + """enumerate object.""" + + def __init__(self, iterable, start=0): + """Create an enumerate object. + + :type iterable: collections.Iterable[T] + :type start: int | long + :rtype: enumerate[int, T] + """ + pass + + def next(self): + """Return the next value, or raise StopIteration. + + :rtype: (int, T) + """ + pass + + def __iter__(self): + """x.__iter__() <==> iter(x). + + :rtype: enumerate[int, T] + """ + pass + + +if __sys.version_info < (3,): + class xrange(object): + """xrange object.""" + + def __init__(self, start, stop=None, step=None): + """Create an xrange object. + + :type start: numbers.Integral + :type stop: numbers.Integral | None + :type step: numbers.Integral | None + :rtype: xrange[int] + """ + pass diff --git a/python/helpers/python-skeletons/nose/__init__.py b/python/helpers/python-skeletons/nose/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/helpers/python-skeletons/nose/tools/__init__.py b/python/helpers/python-skeletons/nose/tools/__init__.py new file mode 100644 index 000000000000..143355fe3dbb --- /dev/null +++ b/python/helpers/python-skeletons/nose/tools/__init__.py @@ -0,0 +1,177 @@ +"""Skeleton for 'nose.tools' module.""" + + +import sys + + +def assert_equal(first, second, msg=None): + """Fail if the two objects are unequal as determined by the '==' operator. + """ + pass + + +def assert_not_equal(first, second, msg=None): + """Fail if the two objects are equal as determined by the '==' operator. + """ + pass + + +def assert_true(expr, msg=None): + """Check that the expression is true.""" + pass + + +def assert_false(expr, msg=None): + """Check that the expression is false.""" + pass + + +if sys.version_info >= (2, 7): + def assert_is(expr1, expr2, msg=None): + """Just like assert_true(a is b), but with a nicer default message.""" + pass + + def assert_is_not(expr1, expr2, msg=None): + """Just like assert_true(a is not b), but with a nicer default message. + """ + pass + + def assert_is_none(obj, msg=None): + """Same as assert_true(obj is None), with a nicer default message. + """ + pass + + def assert_is_not_none(obj, msg=None): + """Included for symmetry with assert_is_none.""" + pass + + def assert_in(member, container, msg=None): + """Just like assert_true(a in b), but with a nicer default message.""" + pass + + def assert_not_in(member, container, msg=None): + """Just like assert_true(a not in b), but with a nicer default message. + """ + pass + + def assert_is_instance(obj, cls, msg=None): + """Same as assert_true(isinstance(obj, cls)), with a nicer default + message. + """ + pass + + def assert_not_is_instance(obj, cls, msg=None): + """Included for symmetry with assert_is_instance.""" + pass + + +def assert_raises(excClass, callableObj=None, *args, **kwargs): + """Fail unless an exception of class excClass is thrown by callableObj when + invoked with arguments args and keyword arguments kwargs. + + If called with callableObj omitted or None, will return a + context object used like this:: + + with assert_raises(SomeException): + do_something() + + :rtype: unittest.case._AssertRaisesContext | None + """ + pass + + +if sys.version_info >= (2, 7): + def assert_raises_regexp(expected_exception, expected_regexp, + callable_obj=None, *args, **kwargs): + """Asserts that the message in a raised exception matches a regexp. + + :rtype: unittest.case._AssertRaisesContext | None + """ + pass + + +def assert_almost_equal(first, second, places=None, msg=None, delta=None): + """Fail if the two objects are unequal as determined by their difference + rounded to the given number of decimal places (default 7) and comparing to + zero, or by comparing that the between the two objects is more than the + given delta. + """ + pass + + +def assert_not_almost_equal(first, second, places=None, msg=None, delta=None): + """Fail if the two objects are equal as determined by their difference + rounded to the given number of decimal places (default 7) and comparing to + zero, or by comparing that the between the two objects is less than the + given delta. + """ + pass + + +if sys.version_info >= (2, 7): + def assert_greater(a, b, msg=None): + """Just like assert_true(a > b), but with a nicer default message.""" + pass + + def assert_greater_equal(a, b, msg=None): + """Just like assert_true(a >= b), but with a nicer default message.""" + pass + + def assert_less(a, b, msg=None): + """Just like assert_true(a < b), but with a nicer default message.""" + pass + + def assert_less_equal(a, b, msg=None): + """Just like self.assertTrue(a <= b), but with a nicer default + message. + """ + pass + + def assert_regexp_matches(text, expected_regexp, msg=None): + """Fail the test unless the text matches the regular expression.""" + pass + + def assert_not_regexp_matches(text, unexpected_regexp, msg=None): + """Fail the test if the text matches the regular expression.""" + pass + + def assert_items_equal(expected_seq, actual_seq, msg=None): + """An unordered sequence specific comparison. It asserts that + actual_seq and expected_seq have the same element counts. + """ + pass + + def assert_dict_contains_subset(expected, actual, msg=None): + """Checks whether actual is a superset of expected.""" + pass + + def assert_multi_line_equal(first, second, msg=None): + """Assert that two multi-line strings are equal.""" + pass + + def assert_sequence_equal(seq1, seq2, msg=None, seq_type=None): + """An equality assertion for ordered sequences (like lists and tuples). + """ + pass + + def assert_list_equal(list1, list2, msg=None): + """A list-specific equality assertion.""" + pass + + def assert_tuple_equal(tuple1, tuple2, msg=None): + """A tuple-specific equality assertion.""" + pass + + def assert_set_equal(set1, set2, msg=None): + """A set-specific equality assertion.""" + pass + + def assert_dict_equal(d1, d2, msg=None): + """A dict-specific equality assertion.""" + pass + + +assert_equals = assert_equal +assert_not_equals = assert_not_equal +assert_almost_equals = assert_almost_equal +assert_not_almost_equals = assert_not_almost_equal diff --git a/python/helpers/python-skeletons/re.py b/python/helpers/python-skeletons/re.py new file mode 100644 index 000000000000..151ddc9b7257 --- /dev/null +++ b/python/helpers/python-skeletons/re.py @@ -0,0 +1,277 @@ +"""Skeleton for 're' stdlib module.""" + + +def compile(pattern, flags=0): + """Compile a regular expression pattern, returning a pattern object. + + :type pattern: bytes | unicode + :type flags: int + :rtype: __Regex + """ + pass + + +def search(pattern, string, flags=0): + """Scan through string looking for a match, and return a corresponding + match instance. Return None if no position in the string matches. + + :type pattern: bytes | unicode | __Regex + :type string: T <= bytes | unicode + :type flags: int + :rtype: __Match[T] | None + """ + pass + + +def match(pattern, string, flags=0): + """Matches zero or more characters at the beginning of the string. + + :type pattern: bytes | unicode | __Regex + :type string: T <= bytes | unicode + :type flags: int + :rtype: __Match[T] | None + """ + pass + + +def split(pattern, string, maxsplit=0, flags=0): + """Split string by the occurrences of pattern. + + :type pattern: bytes | unicode | __Regex + :type string: T <= bytes | unicode + :type maxsplit: int + :type flags: int + :rtype: list[T] + """ + pass + + +def findall(pattern, string, flags=0): + """Return a list of all non-overlapping matches of pattern in string. + + :type pattern: bytes | unicode | __Regex + :type string: T <= bytes | unicode + :type flags: int + :rtype: list[T] + """ + pass + + +def finditer(pattern, string, flags=0): + """Return an iterator over all non-overlapping matches for the pattern in + string. For each match, the iterator returns a match object. + + :type pattern: bytes | unicode | __Regex + :type string: T <= bytes | unicode + :type flags: int + :rtype: collections.Iterable[__Match[T]] + """ + pass + + +def sub(pattern, repl, string, count=0, flags=0): + """Return the string obtained by replacing the leftmost non-overlapping + occurrences of pattern in string by the replacement repl. + + :type pattern: bytes | unicode | __Regex + :type repl: bytes | unicode | collections.Callable + :type string: T <= bytes | unicode + :type count: int + :type flags: int + :rtype: T + """ + pass + + +def subn(pattern, repl, string, count=0, flags=0): + """Return the tuple (new_string, number_of_subs_made) found by replacing + the leftmost non-overlapping occurrences of pattern with the + replacement repl. + + :type pattern: bytes | unicode | __Regex + :type repl: bytes | unicode | collections.Callable + :type string: T <= bytes | unicode + :type count: int + :type flags: int + :rtype: (T, int) + """ + pass + + +def escape(string): + """Escape all the characters in pattern except ASCII letters and numbers. + + :type string: T <= bytes | unicode + :type: T + """ + pass + + +class __Regex(object): + """Mock class for a regular expression pattern object.""" + + def __init__(self, flags, groups, groupindex, pattern): + """Create a new pattern object. + + :type flags: int + :type groups: int + :type groupindex: dict[bytes | unicode, int] + :type pattern: bytes | unicode + """ + self.flags = flags + self.groups = groups + self.groupindex = groupindex + self.pattern = pattern + + def search(self, string, pos=0, endpos=-1): + """Scan through string looking for a match, and return a corresponding + match instance. Return None if no position in the string matches. + + :type string: T <= bytes | unicode + :type pos: int + :type endpos: int + :rtype: __Match[T] | None + """ + pass + + def match(self, string, pos=0, endpos=-1): + """Matches zero | more characters at the beginning of the string. + + :type string: T <= bytes | unicode + :type pos: int + :type endpos: int + :rtype: __Match[T] | None + """ + pass + + def split(self, string, maxsplit=0): + """Split string by the occurrences of pattern. + + :type string: T <= bytes | unicode + :type maxsplit: int + :rtype: list[T] + """ + pass + + def findall(self, string, pos=0, endpos=-1): + """Return a list of all non-overlapping matches of pattern in string. + + :type string: T <= bytes | unicode + :type pos: int + :type endpos: int + :rtype: list[T] + """ + pass + + def finditer(self, string, pos=0, endpos=-1): + """Return an iterator over all non-overlapping matches for the + pattern in string. For each match, the iterator returns a + match object. + + :type string: T <= bytes | unicode + :type pos: int + :type endpos: int + :rtype: collections.Iterable[__Match[T]] + """ + pass + + def sub(self, repl, string, count=0): + """Return the string obtained by replacing the leftmost non-overlapping + occurrences of pattern in string by the replacement repl. + + :type repl: bytes | unicode | collections.Callable + :type string: T <= bytes | unicode + :type count: int + :rtype: T + """ + pass + + def subn(self, repl, string, count=0): + """Return the tuple (new_string, number_of_subs_made) found by replacing + the leftmost non-overlapping occurrences of pattern with the + replacement repl. + + :type repl: bytes | unicode | collections.Callable + :type string: T <= bytes | unicode + :type count: int + :rtype: (T, int) + """ + pass + + +class __Match(object): + """Mock class for a match object.""" + + def __init__(self, pos, endpos, lastindex, lastgroup, re, string): + """Create a new match object. + + :type pos: int + :type endpos: int + :type lastindex: int | None + :type lastgroup: int | bytes | unicode | None + :type re: __Regex + :type string: bytes | unicode + :rtype: __Match[T] + """ + self.pos = pos + self.endpos = endpos + self.lastindex = lastindex + self.lastgroup = lastgroup + self.re = re + self.string = string + + def expand(self, template): + """Return the string obtained by doing backslash substitution on the + template string template. + + :type template: T + :rtype: T + """ + pass + + def group(self, *args): + """Return one or more subgroups of the match. + + :rtype: T | tuple + """ + pass + + def groups(self, default=None): + """Return a tuple containing all the subgroups of the match, from 1 up + to however many groups are in the pattern. + + :rtype: tuple + """ + pass + + def groupdict(self, default=None): + """Return a dictionary containing all the named subgroups of the match, + keyed by the subgroup name. + + :rtype: dict[bytes | unicode, T] + """ + pass + + def start(self, group=0): + """Return the index of the start of the substring matched by group. + + :type group: int | bytes | unicode + :rtype: int + """ + pass + + def end(self, group=0): + """Return the index of the end of the substring matched by group. + + :type group: int | bytes | unicode + :rtype: int + """ + pass + + def span(self, group=0): + """Return a 2-tuple (start, end) for the substring matched by group. + + :type group: int | bytes | unicode + :rtype: (int, int) + """ + pass diff --git a/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java index 676b86f83ae6..6aa350224a38 100644 --- a/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/userSkeletons/PyUserSkeletonsUtil.java @@ -27,6 +27,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -34,21 +35,28 @@ import java.util.List; * @author vlan */ public class PyUserSkeletonsUtil { + public static final String USER_SKELETONS_DIR = "python-skeletons"; @Nullable private static VirtualFile ourUserSkeletonsDirectory; @NotNull - public static String getUserSkeletonsPath() { - if (ApplicationManager.getApplication().isInternal()) { - return StringUtil.join(new String[] {PathManager.getHomePath(), "python", "helpers", "user-skeletons"}, File.separator); - } - // TODO: Add the possibility to put skeletons into PathManager.getSystemPath() + "/user-skeletons" - return PythonHelpersLocator.getHelperPath("user-skeletons"); + private static List getPossibleUserSkeletonsPaths() { + final List result = new ArrayList(); + result.add(PathManager.getConfigPath() + File.separator + USER_SKELETONS_DIR); + result.add(ApplicationManager.getApplication().isInternal() + ? StringUtil.join(new String[]{PathManager.getHomePath(), "python", "helpers", USER_SKELETONS_DIR}, File.separator) + : PythonHelpersLocator.getHelperPath(USER_SKELETONS_DIR)); + return result; } @Nullable public static VirtualFile getUserSkeletonsDirectory() { if (ourUserSkeletonsDirectory == null) { - ourUserSkeletonsDirectory = LocalFileSystem.getInstance().findFileByPath(getUserSkeletonsPath()); + for (String path : getPossibleUserSkeletonsPaths()) { + ourUserSkeletonsDirectory = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); + if (ourUserSkeletonsDirectory != null) { + break; + } + } } return ourUserSkeletonsDirectory; } @@ -75,7 +83,7 @@ public class PyUserSkeletonsUtil { if (sdk != null) { final Project project = foothold.getProject(); final PythonSdkPathCache cache = PythonSdkPathCache.getInstance(project, sdk); - final PyQualifiedName cacheQName = PyQualifiedName.fromDottedString("user-skeletons." + qName); + final PyQualifiedName cacheQName = PyQualifiedName.fromDottedString(USER_SKELETONS_DIR + "." + qName); final List results = cache.get(cacheQName); if (results != null) { final PsiElement element = results.isEmpty() ? null : results.get(0); @@ -101,7 +109,7 @@ public class PyUserSkeletonsUtil { } public static void addUserSkeletonsRoot(@NotNull SdkModificator sdkModificator) { - final VirtualFile root = LocalFileSystem.getInstance().refreshAndFindFileByPath(getUserSkeletonsPath()); + final VirtualFile root = getUserSkeletonsDirectory(); if (root != null) { sdkModificator.addRoot(root, OrderRootType.CLASSES); } diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java b/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java index 69cfc3f42229..15dae82bf84f 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java @@ -126,7 +126,10 @@ public class PythonSdkUpdater implements StartupActivity { private static void updateSysPath(final Sdk sdk) throws InvalidSdkException { long start_time = System.currentTimeMillis(); final List sysPath = PythonSdkType.getSysPath(sdk.getHomePath()); - sysPath.add(PyUserSkeletonsUtil.getUserSkeletonsPath()); + final VirtualFile file = PyUserSkeletonsUtil.getUserSkeletonsDirectory(); + if (file != null) { + sysPath.add(file.getPath()); + } ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() {